mirror of
https://github.com/roleypoly/roleypoly-v1.git
synced 2025-04-25 12:19:10 +00:00
finish drag and drop system
This commit is contained in:
parent
e36be9e381
commit
7806219464
19 changed files with 465 additions and 90 deletions
|
@ -5,7 +5,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color": "^2.0.1",
|
"color": "^2.0.1",
|
||||||
"custom-react-scripts": "0.2.1",
|
"custom-react-scripts": "0.2.1",
|
||||||
"eslint": "^4.13.0",
|
"eslint": "^4.14.0",
|
||||||
"history": "^4.7.2",
|
"history": "^4.7.2",
|
||||||
"immutable": "^3.8.2",
|
"immutable": "^3.8.2",
|
||||||
"prop-types": "^15.6.0",
|
"prop-types": "^15.6.0",
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
"react-transition-group": "^2.2.1",
|
"react-transition-group": "^2.2.1",
|
||||||
"redux": "^3.7.2",
|
"redux": "^3.7.2",
|
||||||
"redux-devtools": "^3.4.1",
|
"redux-devtools": "^3.4.1",
|
||||||
"redux-devtools-dock-monitor": "^1.1.2",
|
"redux-devtools-dock-monitor": "^1.1.3",
|
||||||
"redux-devtools-log-monitor": "^1.4.0",
|
"redux-devtools-log-monitor": "^1.4.0",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-saga": "^0.16.0",
|
"redux-saga": "^0.16.0",
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
},
|
},
|
||||||
"proxy": "http://localhost:6769",
|
"proxy": "http://localhost:6769",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint-config-standard": "^10.2.1",
|
"eslint-config-standard": "^11.0.0-beta.0",
|
||||||
"eslint-plugin-import": "^2.8.0",
|
"eslint-plugin-import": "^2.8.0",
|
||||||
"eslint-plugin-node": "^5.2.1",
|
"eslint-plugin-node": "^5.2.1",
|
||||||
"eslint-plugin-promise": "^3.6.0",
|
"eslint-plugin-promise": "^3.6.0",
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import createHistory from 'history/createBrowserHistory'
|
|
||||||
import { ConnectedRouter } from 'react-router-redux'
|
import { ConnectedRouter } from 'react-router-redux'
|
||||||
|
import { DragDropContext } from 'react-dnd'
|
||||||
|
import HTML5Backend from 'react-dnd-html5-backend'
|
||||||
|
import createHistory from 'history/createBrowserHistory'
|
||||||
import configureStore from './store/configureStore'
|
import configureStore from './store/configureStore'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
|
import './generic.sass'
|
||||||
|
|
||||||
import Wrapper from './components/wrapper'
|
import Wrapper from './components/wrapper'
|
||||||
import AppRouter from './router'
|
import AppRouter from './router'
|
||||||
|
@ -15,6 +17,7 @@ const store = configureStore(undefined, history)
|
||||||
|
|
||||||
window.__APP_STORE__ = store
|
window.__APP_STORE__ = store
|
||||||
|
|
||||||
|
@DragDropContext(HTML5Backend)
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
store.dispatch(userInit)
|
store.dispatch(userInit)
|
||||||
|
|
|
@ -1,21 +1,41 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
|
import { DropTarget } from 'react-dnd'
|
||||||
|
|
||||||
import Role from '../role'
|
import Role from '../role/draggable'
|
||||||
|
import CategoryEditor from './CategoryEditor'
|
||||||
|
|
||||||
|
@DropTarget(Symbol.for('dnd: role'), {
|
||||||
|
drop (props, monitor, element) {
|
||||||
|
props.onDrop(monitor.getItem())
|
||||||
|
},
|
||||||
|
canDrop (props) {
|
||||||
|
return props.mode !== Symbol.for('edit')
|
||||||
|
}
|
||||||
|
}, (connect, monitor) => ({
|
||||||
|
connectDropTarget: connect.dropTarget(),
|
||||||
|
isOver: monitor.isOver(),
|
||||||
|
isOverCurrent: monitor.isOver({ shallow: true }),
|
||||||
|
canDrop: monitor.canDrop(),
|
||||||
|
itemType: monitor.getItemType()
|
||||||
|
}))
|
||||||
class Category extends Component {
|
class Category extends Component {
|
||||||
render () {
|
render () {
|
||||||
const { category, name } = this.props
|
const { category, name, isOver, connectDropTarget, mode, ...rest } = this.props
|
||||||
|
|
||||||
return <div key={name} className="role-picker__category">
|
if (mode === Symbol.for('edit')) {
|
||||||
|
return <CategoryEditor category={category} name={name} {...rest} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectDropTarget(<div key={name} className={`role-picker__category ${(isOver) ? 'is-over' : ''}`}>
|
||||||
<h4>{ name }</h4>
|
<h4>{ name }</h4>
|
||||||
{
|
{
|
||||||
category.get('roles_map')
|
category.get('roles_map')
|
||||||
.sortBy(r => r.get('position'))
|
.sortBy(r => r.get('position'))
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((r, k) => <Role key={k} role={r} type='drag' />)
|
.map((r, k) => <Role key={k} role={r} categoryId={name} />)
|
||||||
.toArray()
|
.toArray()
|
||||||
}
|
}
|
||||||
</div>
|
</div>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default Category
|
export default Category
|
||||||
|
|
41
UI/src/components/role-editor/CategoryEditor.js
Normal file
41
UI/src/components/role-editor/CategoryEditor.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
|
||||||
|
export default class CategoryEditor extends Component {
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
category
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return <div className="role-editor__category editor">
|
||||||
|
<form onSubmit={(e) => e.preventDefault()} className="uk-form-stacked uk-light">
|
||||||
|
<div>
|
||||||
|
<label className="uk-form-label">Category Name</label>
|
||||||
|
<div className="uk-form-controls">
|
||||||
|
<input type="text" className="uk-input" placeholder='' value={name} onChange={this.props.onEdit('name', Symbol.for('edit: text'))} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 10 }}>
|
||||||
|
<div className="uk-form-controls">
|
||||||
|
<label uk-tooltip="delay: 1s" title="Hides and disables roles in this category from being used.">
|
||||||
|
<input
|
||||||
|
style={{ marginRight: 5 }}
|
||||||
|
type="checkbox"
|
||||||
|
className="uk-checkbox"
|
||||||
|
checked={category.get('hidden')}
|
||||||
|
onChange={this.props.onEdit('hidden', Symbol.for('edit: bool'))}
|
||||||
|
/>
|
||||||
|
Hidden
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='role-editor__actions'>
|
||||||
|
<button className="uk-button rp-button secondary role-editor__actions_delete" onClick={this.props.onDelete}>
|
||||||
|
<i uk-icon="icon: trash" />
|
||||||
|
</button>
|
||||||
|
<button className="uk-button rp-button primary role-editor__actions_save" onClick={this.props.onSave}>Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,5 +2,45 @@
|
||||||
&__grid
|
&__grid
|
||||||
display: grid
|
display: grid
|
||||||
grid-template-areas: 'left right'
|
grid-template-areas: 'left right'
|
||||||
grid-template-columns: 1fr
|
grid-template-columns: 1fr 1fr
|
||||||
grid-template-rows: 1fr 1fr
|
grid-template-rows: 1fr
|
||||||
|
|
||||||
|
&__actions
|
||||||
|
display: flex
|
||||||
|
margin-top: 10px
|
||||||
|
|
||||||
|
button
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
&_delete
|
||||||
|
flex: 1
|
||||||
|
margin-right: 5px
|
||||||
|
|
||||||
|
&_save
|
||||||
|
flex: 4
|
||||||
|
|
||||||
|
.role-editor__category
|
||||||
|
box-sizing: border-box
|
||||||
|
background-color: var(--c-1)
|
||||||
|
padding: 15px
|
||||||
|
margin: 10px
|
||||||
|
min-width: 220px - 30px
|
||||||
|
|
||||||
|
&.add-button
|
||||||
|
height: 100px
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
text-align: center
|
||||||
|
color: var(--c-5)
|
||||||
|
font-size: 2em
|
||||||
|
|
||||||
|
i
|
||||||
|
transition: transform 0.15s ease-in-out, color 0.15s ease-in-out
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
i
|
||||||
|
transform: scale(1.1)
|
||||||
|
color: var(--c-7)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { Set } from 'immutable'
|
||||||
|
import * as UIActions from '../../actions/ui'
|
||||||
|
import { getViewMap } from '../role-picker/actions'
|
||||||
|
|
||||||
|
export const constructView = id => (dispatch, getState) => {
|
||||||
|
const server = getState().servers.get(id)
|
||||||
|
|
||||||
|
let { viewMap } = getViewMap(server)
|
||||||
|
viewMap = viewMap.map(c => c.set('mode', Symbol.for('drop')))
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: Symbol.for('re: setup'),
|
||||||
|
data: {
|
||||||
|
viewMap: viewMap
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch(UIActions.fadeIn)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addRoleToCategory = (name, oldName, role, flip = true) => (dispatch) => {
|
||||||
|
dispatch({
|
||||||
|
type: Symbol.for('re: add role to category'),
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
role
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (flip) {
|
||||||
|
dispatch(removeRoleFromCategory(oldName, name, role, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const removeRoleFromCategory = (name, oldName, role, flip = true) => (dispatch) => {
|
||||||
|
dispatch({
|
||||||
|
type: Symbol.for('re: remove role from category'),
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
role
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (flip) {
|
||||||
|
dispatch(addRoleToCategory(oldName, name, role, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const editCategory = (stuff) => dispatch => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const saveCategory = (name) => ({
|
||||||
|
type: Symbol.for('re: switch category mode'),
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
mode: Symbol.for('drop')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const deleteCategory = (name) => ({
|
||||||
|
type: Symbol.for('re: delete category'),
|
||||||
|
data: name
|
||||||
|
})
|
||||||
|
|
||||||
|
export const createCategory = (dispatch, getState) => {
|
||||||
|
const { roleEditor } = getState()
|
||||||
|
const vm = roleEditor.get('viewMap')
|
||||||
|
|
||||||
|
let name = 'New Category'
|
||||||
|
let idx = 1
|
||||||
|
while (vm.has(name)) {
|
||||||
|
idx++
|
||||||
|
name = `New Category ${idx}`
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: Symbol.for('re: set category'),
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
roles: Set([]),
|
||||||
|
roles_map: Set([]),
|
||||||
|
hidden: true,
|
||||||
|
mode: Symbol.for('edit')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,13 +1,15 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { Set } from 'immutable'
|
import { Set } from 'immutable'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
import { DropTarget } from 'react-dnd'
|
||||||
import * as Actions from './actions'
|
import * as Actions from './actions'
|
||||||
import * as PickerActions from '../role-picker/actions'
|
import * as PickerActions from '../role-picker/actions'
|
||||||
import * as UIActions from '../../actions/ui'
|
import * as UIActions from '../../actions/ui'
|
||||||
import './RoleEditor.sass'
|
import './RoleEditor.sass'
|
||||||
|
|
||||||
import Category from './Category'
|
import Category from './Category'
|
||||||
import Role from '../role'
|
import CategoryEditor from './CategoryEditor'
|
||||||
|
import Role from '../role/draggable'
|
||||||
import { Scrollbars } from 'react-custom-scrollbars';
|
import { Scrollbars } from 'react-custom-scrollbars';
|
||||||
|
|
||||||
const mapState = ({ rolePicker, roleEditor, servers }, ownProps) => ({
|
const mapState = ({ rolePicker, roleEditor, servers }, ownProps) => ({
|
||||||
|
@ -17,43 +19,110 @@ const mapState = ({ rolePicker, roleEditor, servers }, ownProps) => ({
|
||||||
})
|
})
|
||||||
|
|
||||||
@connect(mapState)
|
@connect(mapState)
|
||||||
|
@DropTarget(Symbol.for('dnd: role'), {
|
||||||
|
drop (props, monitor, element) {
|
||||||
|
element.dropRole({}, 'Uncategorized')(monitor.getItem())
|
||||||
|
}
|
||||||
|
}, (connect, monitor) => ({
|
||||||
|
connectDropTarget: connect.dropTarget(),
|
||||||
|
isOver: monitor.isOver(),
|
||||||
|
isOverCurrent: monitor.isOver({ shallow: true }),
|
||||||
|
canDrop: monitor.canDrop(),
|
||||||
|
itemType: monitor.getItemType()
|
||||||
|
}))
|
||||||
class RoleEditor extends Component {
|
class RoleEditor extends Component {
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
const { dispatch, match: { params: { server } } } = this.props
|
const { dispatch, match: { params: { server } } } = this.props
|
||||||
dispatch(PickerActions.setup(server))
|
dispatch(Actions.constructView(server))
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (this.props.match.params.server !== nextProps.match.params.server) {
|
if (this.props.match.params.server !== nextProps.match.params.server) {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
dispatch(UIActions.fadeOut(() => dispatch(PickerActions.setup(nextProps.match.params.server))))
|
dispatch(UIActions.fadeOut(() => dispatch(Actions.constructView(nextProps.match.params.server))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dropRole = (category, name) => ({role, category}) => {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
console.log(role)
|
||||||
|
dispatch(Actions.addRoleToCategory(name, category, role))
|
||||||
|
}
|
||||||
|
|
||||||
|
createCategory = () => {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
dispatch(Actions.createCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveCategory = (category, name) => () => {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
dispatch(Actions.saveCategory(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCategory = (category, name) => () => {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
dispatch(Actions.deleteCategory(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
editCategory = (category, name) => (key, type) => event => {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
let value
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case Symbol.for('edit: text'):
|
||||||
|
value = event.target.value
|
||||||
|
break
|
||||||
|
|
||||||
|
case Symbol.for('edit: bool'):
|
||||||
|
value = event.target.checked
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(Actions.editCategory({ category, name, key, type, value }))
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const vm = this.props.rp.get('viewMap')
|
const vm = this.props.editor.get('viewMap')
|
||||||
console.log(vm.toJS())
|
|
||||||
return <div className="inner role-editor">
|
return <div className="inner role-editor">
|
||||||
<div className="role-editor__grid">
|
<div className="role-editor__grid">
|
||||||
<div className="role-editor__grid__left">
|
<div className="role-editor__grid__left">
|
||||||
<Scrollbars autoHeight autoHeightMax='calc(100vh - 110px)'>
|
<Scrollbars autoHeight autoHeightMax='calc(100vh - 110px)'>
|
||||||
{
|
{
|
||||||
vm
|
vm
|
||||||
.filter((_, k) => k !== 'Unassigned')
|
.filter((_, k) => k !== 'Uncategorized')
|
||||||
.map((c, name) => <Category key={name} name={name} category={c} />)
|
.map((c, name) => <Category
|
||||||
.toArray()
|
key={name}
|
||||||
|
name={name}
|
||||||
|
category={c}
|
||||||
|
mode={c.get('mode')}
|
||||||
|
onDrop={this.dropRole(c, name)}
|
||||||
|
onEdit={this.editCategory(c, name)}
|
||||||
|
// onEditOpen={this.openEditor(c, name)}
|
||||||
|
onSave={this.saveCategory(c, name)}
|
||||||
|
onDelete={this.deleteCategory(c, name)}
|
||||||
|
/>)
|
||||||
|
.toArray()
|
||||||
}
|
}
|
||||||
|
<div onClick={this.createCategory} uk-tooltip="pos: bottom" title="Add new category" className="role-editor__category add-button">
|
||||||
|
<i uk-icon="icon: plus" />
|
||||||
|
</div>
|
||||||
</Scrollbars>
|
</Scrollbars>
|
||||||
</div>
|
</div>
|
||||||
<div className="role-editor__grid__right">
|
|
||||||
{
|
{
|
||||||
(vm.getIn(['Uncategorized', 'roles_map']) || Set())
|
this.props.connectDropTarget(
|
||||||
.sortBy(r => r.get('position'))
|
<div className="role-editor__grid__right">
|
||||||
.reverse()
|
{
|
||||||
.map((r, k) => <Role key={k} role={r} type='drag' />)
|
(vm.getIn(['Uncategorized', 'roles_map']) || Set())
|
||||||
.toArray()
|
.sortBy(r => r.get('position'))
|
||||||
|
.reverse()
|
||||||
|
.map((r, k) => <Role key={k} categoryId='Uncategorized' role={r} />)
|
||||||
|
.toArray()
|
||||||
|
}
|
||||||
|
</div>)
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,10 @@ class Category extends Component {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (category.get('roles').count() === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return <div key={name} className="role-picker__category">
|
return <div key={name} className="role-picker__category">
|
||||||
<h4>{ name }</h4>
|
<h4>{ name }</h4>
|
||||||
{
|
{
|
||||||
|
|
|
@ -50,39 +50,3 @@
|
||||||
&.hidden
|
&.hidden
|
||||||
opacity: 0
|
opacity: 0
|
||||||
// display: none
|
// display: none
|
||||||
|
|
||||||
.action__button
|
|
||||||
border: 0
|
|
||||||
border-radius: 2px
|
|
||||||
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out
|
|
||||||
position: relative
|
|
||||||
|
|
||||||
&::after
|
|
||||||
content: ""
|
|
||||||
position: absolute
|
|
||||||
top: 0
|
|
||||||
bottom: 0
|
|
||||||
right: 0
|
|
||||||
left: 0
|
|
||||||
background-color: rgba(0,0,0,0.1)
|
|
||||||
border-radius: 2px
|
|
||||||
opacity: 0
|
|
||||||
transition: opacity 0.15s ease-in-out
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
transform: translateY(-1px)
|
|
||||||
box-shadow: 0 1px 2px rgba(0,0,0,0.3)
|
|
||||||
&::after
|
|
||||||
opacity: 0.7
|
|
||||||
|
|
||||||
&:active
|
|
||||||
transform: translateY(0px)
|
|
||||||
box-shadow: none
|
|
||||||
&::after
|
|
||||||
opacity: 1
|
|
||||||
|
|
||||||
&.primary
|
|
||||||
background-color: var(--c-5)
|
|
||||||
|
|
||||||
&.secondary
|
|
||||||
background-color: var(--c-3)
|
|
|
@ -16,8 +16,7 @@ export const setup = id => async dispatch => {
|
||||||
dispatch(constructView(id))
|
dispatch(constructView(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const constructView = id => (dispatch, getState) => {
|
export const getViewMap = server => {
|
||||||
const server = getState().servers.get(id)
|
|
||||||
const roles = server.get('roles')
|
const roles = server.get('roles')
|
||||||
|
|
||||||
const categories = server.get('categories')
|
const categories = server.get('categories')
|
||||||
|
@ -28,7 +27,7 @@ export const constructView = id => (dispatch, getState) => {
|
||||||
|
|
||||||
// console.log('roles', allRoles.toJS(), accountedRoles.toJS(), unaccountedRoles.toJS())
|
// console.log('roles', allRoles.toJS(), accountedRoles.toJS(), unaccountedRoles.toJS())
|
||||||
|
|
||||||
const vm = categories.set('Uncategorized', fromJS({
|
const viewMap = categories.set('Uncategorized', fromJS({
|
||||||
roles: unaccountedRoles,
|
roles: unaccountedRoles,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
type: 'multi'
|
type: 'multi'
|
||||||
|
@ -43,11 +42,21 @@ export const constructView = id => (dispatch, getState) => {
|
||||||
|
|
||||||
const selected = roles.reduce((acc, r) => acc.set(r.get('id'), r.get('selected')), Map())
|
const selected = roles.reduce((acc, r) => acc.set(r.get('id'), r.get('selected')), Map())
|
||||||
|
|
||||||
console.log(categories, selected)
|
return {
|
||||||
|
viewMap,
|
||||||
|
selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const constructView = id => (dispatch, getState) => {
|
||||||
|
const server = getState().servers.get(id)
|
||||||
|
|
||||||
|
const { viewMap, selected } = getViewMap(server)
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: Symbol.for('setup role picker'),
|
type: Symbol.for('setup role picker'),
|
||||||
data: {
|
data: {
|
||||||
viewMap: vm,
|
viewMap: viewMap,
|
||||||
rolesSelected: selected,
|
rolesSelected: selected,
|
||||||
originalRolesSelected: selected,
|
originalRolesSelected: selected,
|
||||||
hidden: false
|
hidden: false
|
||||||
|
|
|
@ -77,10 +77,10 @@ class RolePicker extends Component {
|
||||||
<h3>Roles</h3>
|
<h3>Roles</h3>
|
||||||
<div className="role-picker__spacer"></div>
|
<div className="role-picker__spacer"></div>
|
||||||
<div className={`role-picker__actions ${(!this.rolesHaveChanged) ? 'hidden' : ''}`}>
|
<div className={`role-picker__actions ${(!this.rolesHaveChanged) ? 'hidden' : ''}`}>
|
||||||
<button disabled={!this.rolesHaveChanged} onClick={() => dispatch(Actions.resetSelected)} className="uk-button action__button secondary">
|
<button disabled={!this.rolesHaveChanged} onClick={() => dispatch(Actions.resetSelected)} className="uk-button rp-button secondary">
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
<button disabled={!this.rolesHaveChanged} onClick={() => dispatch(Actions.submitSelected(this.props.match.params.server))} className="uk-button action__button primary">
|
<button disabled={!this.rolesHaveChanged} onClick={() => dispatch(Actions.submitSelected(this.props.match.params.server))} className="uk-button rp-button primary">
|
||||||
Save Changes
|
Save Changes
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
border-radius: 32px
|
border-radius: 32px
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
height: 32px
|
height: 32px
|
||||||
margin: 0 6px 6px 0
|
|
||||||
padding: 4px
|
padding: 4px
|
||||||
display: inline-flex
|
display: inline-flex
|
||||||
font-weight: 600
|
font-weight: 600
|
||||||
|
@ -13,8 +12,11 @@
|
||||||
vertical-align: baseline
|
vertical-align: baseline
|
||||||
transition: background-color 0.15s ease-in-out, transform 0.15s ease-in-out, box-shadow 0.15s ease-in-out
|
transition: background-color 0.15s ease-in-out, transform 0.15s ease-in-out, box-shadow 0.15s ease-in-out
|
||||||
transform: translateZ(0)
|
transform: translateZ(0)
|
||||||
|
margin: 0 6px 6px 0
|
||||||
|
position: relative
|
||||||
|
|
||||||
&.role__button
|
&.role__button
|
||||||
|
|
||||||
.role__option
|
.role__option
|
||||||
&:hover
|
&:hover
|
||||||
cursor: inherit
|
cursor: inherit
|
||||||
|
@ -67,7 +69,7 @@
|
||||||
|
|
||||||
&.role__drag
|
&.role__drag
|
||||||
box-shadow: none
|
box-shadow: none
|
||||||
&:hover
|
&:hover, &.is-dragging
|
||||||
transform: translateZ(0) translateY(-1px)
|
transform: translateZ(0) translateY(-1px)
|
||||||
box-shadow: 0 1px 2px rgba(0,0,0,0.3)
|
box-shadow: 0 1px 2px rgba(0,0,0,0.3)
|
||||||
background-color: rgba(#ccc,0.03)
|
background-color: rgba(#ccc,0.03)
|
||||||
|
@ -75,5 +77,26 @@
|
||||||
&:active
|
&:active
|
||||||
cursor: grabbing
|
cursor: grabbing
|
||||||
|
|
||||||
|
&::after
|
||||||
|
position: absolute
|
||||||
|
top: -1px
|
||||||
|
bottom: -1px
|
||||||
|
left: -1px
|
||||||
|
right: -1px
|
||||||
|
border-radius: 32px
|
||||||
|
background-color: var(--c-3)
|
||||||
|
border: 2px dashed var(--c-7)
|
||||||
|
content: ""
|
||||||
|
opacity: 0
|
||||||
|
transition: opacity 0.15s ease-in-out
|
||||||
|
box-sizing: border-box
|
||||||
|
|
||||||
|
&.is-dragging
|
||||||
|
transition: border-color 0s ease-out 0.15s
|
||||||
|
border-color: transparent
|
||||||
|
&::after
|
||||||
|
opacity: 1
|
||||||
|
|
||||||
|
|
||||||
.role__option:active, .role:active .role__option:not(:active)
|
.role__option:active, .role:active .role__option:not(:active)
|
||||||
transform: translateY(0) translateZ(0px) !important
|
transform: translateY(0) translateZ(0px) !important
|
19
UI/src/components/role/draggable.js
Normal file
19
UI/src/components/role/draggable.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import React, { Component } from 'react'
|
||||||
|
import { DragSource } from 'react-dnd'
|
||||||
|
|
||||||
|
import Role from './index'
|
||||||
|
|
||||||
|
@DragSource(Symbol.for('dnd: role'), {
|
||||||
|
beginDrag ({ role, categoryId }) {
|
||||||
|
return { role, category: categoryId }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(connect, monitor) => ({
|
||||||
|
connectDragSource: connect.dragSource(),
|
||||||
|
isDragging: monitor.isDragging()
|
||||||
|
}))
|
||||||
|
export default class DraggableRole extends Component {
|
||||||
|
render () {
|
||||||
|
return <Role {...this.props} type='drag' />
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,9 +15,11 @@ class Role extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { role, selected, disabled, type, provided } = this.props
|
let { role, selected, disabled, type, isDragging } = this.props
|
||||||
type = type || 'button'
|
type = type || 'button'
|
||||||
|
|
||||||
|
// console.log(this.props)
|
||||||
|
|
||||||
let color = Color(role.get('color'))
|
let color = Color(role.get('color'))
|
||||||
|
|
||||||
if (color.rgbNumber() === 0) {
|
if (color.rgbNumber() === 0) {
|
||||||
|
@ -27,14 +29,14 @@ class Role extends Component {
|
||||||
const c = color
|
const c = color
|
||||||
let hc = color.lighten(0.1)
|
let hc = color.lighten(0.1)
|
||||||
|
|
||||||
return <div
|
const out = <div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!disabled && this.props.onToggle != null) {
|
if (!disabled && this.props.onToggle != null) {
|
||||||
this.props.onToggle(!selected, selected) }
|
this.props.onToggle(!selected, selected) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{...((disabled) ? { 'uk-tooltip': '', title: "I don't have permissions to grant this." } : {})}
|
{...((disabled) ? { 'uk-tooltip': '', title: "I don't have permissions to grant this." } : {})}
|
||||||
className={`role font-sans-serif ${(disabled) ? 'disabled' : ''} role__${type}`}
|
className={`role font-sans-serif ${(disabled) ? 'disabled' : ''} ${(isDragging) ? 'is-dragging' : ''} role__${type}`}
|
||||||
style={{
|
style={{
|
||||||
'--role-color-hex': c.string(),
|
'--role-color-hex': c.string(),
|
||||||
'--role-color-hover': hc.string(),
|
'--role-color-hover': hc.string(),
|
||||||
|
@ -45,6 +47,12 @@ class Role extends Component {
|
||||||
{role.get('name')}
|
{role.get('name')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
if (type === 'drag' && this.props.connectDragSource != null) {
|
||||||
|
return this.props.connectDragSource(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
37
UI/src/generic.sass
Normal file
37
UI/src/generic.sass
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
.rp-button
|
||||||
|
border: 0
|
||||||
|
border-radius: 2px
|
||||||
|
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
&::after
|
||||||
|
content: ""
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
bottom: 0
|
||||||
|
right: 0
|
||||||
|
left: 0
|
||||||
|
background-color: rgba(0,0,0,0.1)
|
||||||
|
border-radius: 2px
|
||||||
|
opacity: 0
|
||||||
|
transition: opacity 0.15s ease-in-out
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
transform: translateY(-1px)
|
||||||
|
box-shadow: 0 1px 2px rgba(0,0,0,0.3)
|
||||||
|
&::after
|
||||||
|
opacity: 0.7
|
||||||
|
|
||||||
|
&:active
|
||||||
|
transform: translateY(0px)
|
||||||
|
box-shadow: none
|
||||||
|
&::after
|
||||||
|
opacity: 1
|
||||||
|
|
||||||
|
&.primary
|
||||||
|
background-color: var(--c-5)
|
||||||
|
color: var(--c-9)
|
||||||
|
|
||||||
|
&.secondary
|
||||||
|
background-color: var(--c-3)
|
||||||
|
color: var(--c-7)
|
|
@ -3,6 +3,7 @@ import { combineReducers } from 'redux'
|
||||||
import servers from './servers'
|
import servers from './servers'
|
||||||
import user from './user'
|
import user from './user'
|
||||||
import rolePicker from './role-picker'
|
import rolePicker from './role-picker'
|
||||||
|
import roleEditor from './role-editor'
|
||||||
import { routerMiddleware } from 'react-router-redux'
|
import { routerMiddleware } from 'react-router-redux'
|
||||||
// import roles from './roles'
|
// import roles from './roles'
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ const appState = (state = initialState, { type, data }) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Symbol.for('app ready'):
|
case Symbol.for('app ready'):
|
||||||
return {
|
return {
|
||||||
|
...state,
|
||||||
ready: true,
|
ready: true,
|
||||||
fade: false
|
fade: false
|
||||||
}
|
}
|
||||||
|
@ -36,7 +38,8 @@ const rootReducer = combineReducers({
|
||||||
user,
|
user,
|
||||||
router: routerMiddleware,
|
router: routerMiddleware,
|
||||||
// roles,
|
// roles,
|
||||||
rolePicker
|
rolePicker,
|
||||||
|
roleEditor
|
||||||
})
|
})
|
||||||
|
|
||||||
export default rootReducer
|
export default rootReducer
|
||||||
|
|
43
UI/src/reducers/role-editor.js
Normal file
43
UI/src/reducers/role-editor.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import { Map, OrderedMap, fromJS } from 'immutable'
|
||||||
|
|
||||||
|
const initialState = Map({
|
||||||
|
viewMap: OrderedMap({})
|
||||||
|
})
|
||||||
|
|
||||||
|
const reducer = (state = initialState, { type, data }) => {
|
||||||
|
switch (type) {
|
||||||
|
case Symbol.for('re: setup'):
|
||||||
|
const { viewMap, ...rest } = data
|
||||||
|
return Map({ viewMap: OrderedMap(viewMap), ...rest })
|
||||||
|
|
||||||
|
case Symbol.for('re: set category'):
|
||||||
|
return state.setIn(['viewMap', data.name], Map(data))
|
||||||
|
|
||||||
|
case Symbol.for('re: delete category'):
|
||||||
|
return state.deleteIn(['viewMap', data])
|
||||||
|
|
||||||
|
case Symbol.for('re: switch category mode'):
|
||||||
|
return state.setIn(['viewMap', data.name, 'mode'], data.mode)
|
||||||
|
|
||||||
|
case Symbol.for('re: add role to category'):
|
||||||
|
const category = state.getIn(['viewMap', data.name])
|
||||||
|
return state.setIn(['viewMap', data.name],
|
||||||
|
category
|
||||||
|
.set('roles', category.get('roles').add(data.role.get('id')))
|
||||||
|
.set('roles_map', category.get('roles_map').add(data.role))
|
||||||
|
)
|
||||||
|
|
||||||
|
case Symbol.for('re: remove role from category'):
|
||||||
|
const rmCat = state.getIn(['viewMap', data.name])
|
||||||
|
return state.setIn(['viewMap', data.name],
|
||||||
|
rmCat
|
||||||
|
.set('roles', rmCat.get('roles').filterNot(r => r === data.role.get('id')))
|
||||||
|
.set('roles_map', rmCat.get('roles_map').filterNot(r => r.get('id') === data.role.get('id')))
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default reducer
|
|
@ -1,11 +1,11 @@
|
||||||
import React, { Component, Fragment } from 'react'
|
import React, { Component, Fragment } from 'react'
|
||||||
import { Route } from 'react-router-dom'
|
import { Route } from 'react-router-dom'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
|
import { withRouter } from 'react-router'
|
||||||
|
|
||||||
import Servers from '../components/servers'
|
import Servers from '../components/servers'
|
||||||
import OauthCallback from '../components/oauth-callback'
|
import OauthCallback from '../components/oauth-callback'
|
||||||
import OauthFlow from '../components/oauth-flow'
|
import OauthFlow from '../components/oauth-flow'
|
||||||
import { withRouter } from 'react-router';
|
|
||||||
|
|
||||||
const aaa = (props) => (<div>{ JSON.stringify(props) }</div>)
|
const aaa = (props) => (<div>{ JSON.stringify(props) }</div>)
|
||||||
|
|
||||||
|
|
28
UI/yarn.lock
28
UI/yarn.lock
|
@ -2140,7 +2140,7 @@ date-now@^0.1.4:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
|
||||||
|
|
||||||
debug@*, debug@^3.0.1, debug@^3.1.0:
|
debug@*, debug@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2623,9 +2623,9 @@ eslint-config-react-app@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-2.0.1.tgz#fd0503da01ae608f0c6ae8861de084975142230e"
|
resolved "https://registry.yarnpkg.com/eslint-config-react-app/-/eslint-config-react-app-2.0.1.tgz#fd0503da01ae608f0c6ae8861de084975142230e"
|
||||||
|
|
||||||
eslint-config-standard@^10.2.1:
|
eslint-config-standard@^11.0.0-beta.0:
|
||||||
version "10.2.1"
|
version "11.0.0-beta.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz#c061e4d066f379dc17cd562c64e819b4dd454591"
|
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-11.0.0-beta.0.tgz#f8afe69803d95c685a4b8392b8793188eb03cbb3"
|
||||||
|
|
||||||
eslint-import-resolver-node@^0.3.1:
|
eslint-import-resolver-node@^0.3.1:
|
||||||
version "0.3.1"
|
version "0.3.1"
|
||||||
|
@ -2740,6 +2740,10 @@ eslint-scope@^3.7.1:
|
||||||
esrecurse "^4.1.0"
|
esrecurse "^4.1.0"
|
||||||
estraverse "^4.1.1"
|
estraverse "^4.1.1"
|
||||||
|
|
||||||
|
eslint-visitor-keys@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
|
||||||
|
|
||||||
eslint@4.4.1:
|
eslint@4.4.1:
|
||||||
version "4.4.1"
|
version "4.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.4.1.tgz#99cd7eafcffca2ff99a5c8f5f2a474d6364b4bd3"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.4.1.tgz#99cd7eafcffca2ff99a5c8f5f2a474d6364b4bd3"
|
||||||
|
@ -2781,21 +2785,21 @@ eslint@4.4.1:
|
||||||
table "^4.0.1"
|
table "^4.0.1"
|
||||||
text-table "~0.2.0"
|
text-table "~0.2.0"
|
||||||
|
|
||||||
eslint@^4.13.0:
|
eslint@^4.14.0:
|
||||||
version "4.13.0"
|
version "4.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.13.0.tgz#1991aa359586af83877bde59de9d41f53e20826d"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.14.0.tgz#96609768d1dd23304faba2d94b7fefe5a5447a82"
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv "^5.3.0"
|
ajv "^5.3.0"
|
||||||
babel-code-frame "^6.22.0"
|
babel-code-frame "^6.22.0"
|
||||||
chalk "^2.1.0"
|
chalk "^2.1.0"
|
||||||
concat-stream "^1.6.0"
|
concat-stream "^1.6.0"
|
||||||
cross-spawn "^5.1.0"
|
cross-spawn "^5.1.0"
|
||||||
debug "^3.0.1"
|
debug "^3.1.0"
|
||||||
doctrine "^2.0.2"
|
doctrine "^2.0.2"
|
||||||
eslint-scope "^3.7.1"
|
eslint-scope "^3.7.1"
|
||||||
|
eslint-visitor-keys "^1.0.0"
|
||||||
espree "^3.5.2"
|
espree "^3.5.2"
|
||||||
esquery "^1.0.0"
|
esquery "^1.0.0"
|
||||||
estraverse "^4.2.0"
|
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
file-entry-cache "^2.0.0"
|
file-entry-cache "^2.0.0"
|
||||||
functional-red-black-tree "^1.0.1"
|
functional-red-black-tree "^1.0.1"
|
||||||
|
@ -6300,9 +6304,9 @@ reduce-function-call@^1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match "^0.4.2"
|
balanced-match "^0.4.2"
|
||||||
|
|
||||||
redux-devtools-dock-monitor@^1.1.2:
|
redux-devtools-dock-monitor@^1.1.3:
|
||||||
version "1.1.2"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/redux-devtools-dock-monitor/-/redux-devtools-dock-monitor-1.1.2.tgz#eb213a021f8c25b892f6c98bdb87368615e3d201"
|
resolved "https://registry.yarnpkg.com/redux-devtools-dock-monitor/-/redux-devtools-dock-monitor-1.1.3.tgz#1205e823c82536570aac8551a1c4b70972cba6aa"
|
||||||
dependencies:
|
dependencies:
|
||||||
babel-runtime "^6.2.0"
|
babel-runtime "^6.2.0"
|
||||||
parse-key "^0.2.1"
|
parse-key "^0.2.1"
|
||||||
|
|
Loading…
Add table
Reference in a new issue