mirror of
https://github.com/roleypoly/roleypoly-v1.git
synced 2025-06-16 02:19:08 +00:00
fix unsafe roles being usable, begin work on role/server editor
This commit is contained in:
parent
d1f556b0f0
commit
bd15a812e5
20 changed files with 343 additions and 92 deletions
13
UI/src/actions/ui.js
Normal file
13
UI/src/actions/ui.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export const fadeOut = cb => dispatch => {
|
||||
dispatch({
|
||||
type: Symbol.for('app fade'),
|
||||
data: true
|
||||
})
|
||||
|
||||
setTimeout(cb, 300)
|
||||
}
|
||||
|
||||
export const fadeIn = {
|
||||
type: Symbol.for('app fade'),
|
||||
data: false
|
||||
}
|
21
UI/src/components/role-editor/Category.js
Normal file
21
UI/src/components/role-editor/Category.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import React, { Component } from 'react'
|
||||
|
||||
import Role from '../role'
|
||||
|
||||
class Category extends Component {
|
||||
render () {
|
||||
const { category, name } = this.props
|
||||
|
||||
return <div key={name} className="role-picker__category">
|
||||
<h4>{ name }</h4>
|
||||
{
|
||||
category.get('roles_map')
|
||||
.sortBy(r => r.get('position'))
|
||||
.reverse()
|
||||
.map((r, k) => <Role key={k} role={r} type='drag' />)
|
||||
.toArray()
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
export default Category
|
6
UI/src/components/role-editor/RoleEditor.sass
Normal file
6
UI/src/components/role-editor/RoleEditor.sass
Normal file
|
@ -0,0 +1,6 @@
|
|||
.role-editor
|
||||
&__grid
|
||||
display: grid
|
||||
grid-template-areas: 'left right'
|
||||
grid-template-columns: 1fr
|
||||
grid-template-rows: 1fr 1fr
|
0
UI/src/components/role-editor/actions.js
Normal file
0
UI/src/components/role-editor/actions.js
Normal file
62
UI/src/components/role-editor/index.js
Normal file
62
UI/src/components/role-editor/index.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Set } from 'immutable'
|
||||
import { connect } from 'react-redux'
|
||||
import * as Actions from './actions'
|
||||
import * as PickerActions from '../role-picker/actions'
|
||||
import * as UIActions from '../../actions/ui'
|
||||
import './RoleEditor.sass'
|
||||
|
||||
import Category from './Category'
|
||||
import Role from '../role'
|
||||
import { Scrollbars } from 'react-custom-scrollbars';
|
||||
|
||||
const mapState = ({ rolePicker, roleEditor, servers }, ownProps) => ({
|
||||
rp: rolePicker,
|
||||
editor: roleEditor,
|
||||
server: servers.get(ownProps.match.params.server)
|
||||
})
|
||||
|
||||
@connect(mapState)
|
||||
class RoleEditor extends Component {
|
||||
componentWillMount () {
|
||||
const { dispatch, match: { params: { server } } } = this.props
|
||||
dispatch(PickerActions.setup(server))
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.props.match.params.server !== nextProps.match.params.server) {
|
||||
const { dispatch } = this.props
|
||||
dispatch(UIActions.fadeOut(() => dispatch(PickerActions.setup(nextProps.match.params.server))))
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const vm = this.props.rp.get('viewMap')
|
||||
console.log(vm.toJS())
|
||||
return <div className="inner role-editor">
|
||||
<div className="role-editor__grid">
|
||||
<div className="role-editor__grid__left">
|
||||
<Scrollbars autoHeight autoHeightMax='calc(100vh - 110px)'>
|
||||
{
|
||||
vm
|
||||
.filter((_, k) => k !== 'Unassigned')
|
||||
.map((c, name) => <Category key={name} name={name} category={c} />)
|
||||
.toArray()
|
||||
}
|
||||
</Scrollbars>
|
||||
</div>
|
||||
<div className="role-editor__grid__right">
|
||||
{
|
||||
(vm.getIn(['Uncategorized', 'roles_map']) || Set())
|
||||
.sortBy(r => r.get('position'))
|
||||
.reverse()
|
||||
.map((r, k) => <Role key={k} role={r} type='drag' />)
|
||||
.toArray()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default RoleEditor
|
|
@ -32,10 +32,14 @@ class Category extends Component {
|
|||
return <div key={name} className="role-picker__category">
|
||||
<h4>{ name }</h4>
|
||||
{
|
||||
category.get('roles_map').map((r, k) => {
|
||||
category.get('roles_map')
|
||||
.sortBy(r => r.get('position'))
|
||||
.reverse()
|
||||
.map((r, k) => {
|
||||
const id = r.get('id')
|
||||
return <Role key={k} role={r} selected={isSelected(id)} onToggle={this.onRoleToggle(id)}/>
|
||||
}).toArray()
|
||||
return <Role key={k} role={r} disabled={!r.get('safe')} selected={isSelected(id)} onToggle={this.onRoleToggle(id)}/>
|
||||
})
|
||||
.toArray()
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -17,10 +17,25 @@
|
|||
background-color: var(--c-1)
|
||||
padding: 15px
|
||||
margin: 10px
|
||||
width: 220px - 30px
|
||||
min-width: 220px - 30px
|
||||
|
||||
&__roles-header
|
||||
&__header
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: left
|
||||
|
||||
color: var(--c-7)
|
||||
|
||||
svg
|
||||
transition: transform 0.05s ease-in-out
|
||||
transform: translateZ(0)
|
||||
&:hover
|
||||
transform: translateZ(0) scale(1.1)
|
||||
color: var(--c-9)
|
||||
|
||||
h3
|
||||
margin: 0
|
||||
margin-right: 10px
|
||||
|
||||
&__spacer
|
||||
flex: 1
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Map, Set, fromJS } from 'immutable'
|
||||
import superagent from 'superagent'
|
||||
|
||||
import * as UIActions from '../../actions/ui'
|
||||
|
||||
export const setup = id => async dispatch => {
|
||||
// const rsp = await superagent.get(`/api/server/${id}`)
|
||||
|
@ -22,7 +22,7 @@ export const constructView = id => (dispatch, getState) => {
|
|||
|
||||
const categories = server.get('categories')
|
||||
|
||||
const allRoles = server.get('roles').map(r => r.get('id')).toSet()
|
||||
const allRoles = server.get('roles').filter(v => v.get('safe')).map(r => r.get('id')).toSet()
|
||||
const accountedRoles = categories.map(c => c.get('roles')).toSet().flatten()
|
||||
const unaccountedRoles = allRoles.subtract(accountedRoles)
|
||||
|
||||
|
@ -53,6 +53,8 @@ export const constructView = id => (dispatch, getState) => {
|
|||
hidden: false
|
||||
}
|
||||
})
|
||||
|
||||
dispatch(UIActions.fadeIn)
|
||||
}
|
||||
|
||||
export const resetSelected = (dispatch) => {
|
||||
|
|
|
@ -2,10 +2,12 @@ import React, { Component, Fragment } from 'react'
|
|||
import { connect } from 'react-redux'
|
||||
import superagent from 'superagent'
|
||||
import * as Actions from './actions'
|
||||
import * as UIActions from '../../actions/ui'
|
||||
import './RolePicker.sass'
|
||||
|
||||
import Category from './Category'
|
||||
import { Scrollbars } from 'react-custom-scrollbars';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const mapState = ({ rolePicker, servers }, ownProps) => {
|
||||
return {
|
||||
|
@ -24,7 +26,7 @@ class RolePicker extends Component {
|
|||
componentWillReceiveProps (nextProps) {
|
||||
if (this.props.match.params.server !== nextProps.match.params.server) {
|
||||
const { dispatch } = this.props
|
||||
dispatch(Actions.setup(nextProps.match.params.server))
|
||||
dispatch(UIActions.fadeOut(() => dispatch(Actions.setup(nextProps.match.params.server))))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,6 +39,29 @@ class RolePicker extends Component {
|
|||
return !data.get('rolesSelected').equals(data.get('originalRolesSelected'))
|
||||
}
|
||||
|
||||
renderServerMessage (server) {
|
||||
const roleManager = server.getIn(['perms', 'canManageRoles'])
|
||||
const msg = server.get('message')
|
||||
if (!roleManager && msg !== '') {
|
||||
return <section>
|
||||
<h3>Server Message</h3>
|
||||
<p>{msg}</p>
|
||||
</section>
|
||||
}
|
||||
|
||||
if (roleManager) {
|
||||
return <section>
|
||||
<div className="role-picker__header">
|
||||
<h3>Server Message</h3>
|
||||
<Link to={`/s/${server.get('id')}/edit`} uk-tooltip='' title='Edit' uk-icon="icon: file-edit"></Link>
|
||||
</div>
|
||||
<p>{msg || <i>no server message</i>}</p>
|
||||
</section>
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
render () {
|
||||
const { data, server, dispatch } = this.props
|
||||
const vm = data.get('viewMap')
|
||||
|
@ -46,16 +71,9 @@ class RolePicker extends Component {
|
|||
}
|
||||
|
||||
return <div className={`inner role-picker ${(data.get('hidden')) ? 'hidden' : ''}`}>
|
||||
{/* <Scrollbars> */}
|
||||
{ (server.get('message') !== '')
|
||||
? <section>
|
||||
<h3>Server Message</h3>
|
||||
<p>{server.get('message')}</p>
|
||||
</section>
|
||||
: null
|
||||
}
|
||||
{ this.renderServerMessage(server) }
|
||||
<section>
|
||||
<div className="role-picker__roles-header">
|
||||
<div className="role-picker__header">
|
||||
<h3>Roles</h3>
|
||||
<div className="role-picker__spacer"></div>
|
||||
<div className={`role-picker__actions ${(!this.rolesHaveChanged) ? 'hidden' : ''}`}>
|
||||
|
@ -69,12 +87,10 @@ class RolePicker extends Component {
|
|||
</div>
|
||||
<div className="role-picker__categories">
|
||||
{
|
||||
vm.map((c, name) => <Category name={name} category={c} isSelected={this.isSelected} onChange={(roles) => dispatch(Actions.updateRoles(roles))} />).toArray()
|
||||
vm.map((c, name) => <Category key={name} name={name} category={c} isSelected={this.isSelected} onChange={(roles) => dispatch(Actions.updateRoles(roles))} />).toArray()
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
{/* </Scrollbars> */}
|
||||
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,42 +11,32 @@
|
|||
justify-content: center
|
||||
font-size: 16px
|
||||
vertical-align: baseline
|
||||
transition: border-color 0.2s 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)
|
||||
|
||||
&:hover
|
||||
&.role__button
|
||||
.role__option
|
||||
transform: translateY(-1px) translateZ(0px)
|
||||
box-shadow: 0 1px 1px var(--c-dark)
|
||||
border-color: var(--role-color-hover)
|
||||
background-color: transparent
|
||||
&:active
|
||||
&:hover
|
||||
cursor: inherit
|
||||
|
||||
&:hover:not(.disabled)
|
||||
.role__option
|
||||
transform: translateY(-1px) translateZ(0px)
|
||||
box-shadow: 0 1px 1px var(--c-dark)
|
||||
border-color: var(--role-color-hover)
|
||||
background-color: transparent
|
||||
&:active
|
||||
box-shadow: none
|
||||
&:active .role__option
|
||||
box-shadow: none
|
||||
|
||||
&:active .role__option
|
||||
box-shadow: none
|
||||
|
||||
|
||||
&__option
|
||||
border-radius: 50%
|
||||
height: 22px
|
||||
margin-right: 6px
|
||||
width: 22px
|
||||
box-sizing: border-box
|
||||
/* display: inline-block */
|
||||
background-color: transparent
|
||||
overflow: hidden
|
||||
transform: translateZ(0px)
|
||||
border: 1px solid var(--role-color-hex)
|
||||
transition: background-color 0.1s ease-in-out, border-left-width 0.3s ease-in-out, border-color 0.1s ease-in-out, transform 0.1s ease-in-out, box-shadow 0.1s ease-out
|
||||
|
||||
&.selected
|
||||
// background-color: var(--role-color-hex)
|
||||
|
||||
// This **must** be width-1, otherwise blink adds width to the boundaries.
|
||||
border-left-width: 21px
|
||||
|
||||
&:hover
|
||||
cursor: pointer
|
||||
&.disabled
|
||||
.role__option
|
||||
border-color: var(--c-7)
|
||||
transition: none
|
||||
transform: translateZ(0)
|
||||
&:hover
|
||||
cursor: default
|
||||
|
||||
|
||||
&__name
|
||||
|
@ -57,5 +47,33 @@
|
|||
white-space: nowrap
|
||||
user-select: none
|
||||
|
||||
&__option
|
||||
border-radius: 50%
|
||||
height: 22px
|
||||
margin-right: 6px
|
||||
width: 22px
|
||||
box-sizing: border-box
|
||||
/* display: inline-block */
|
||||
background-color: transparent
|
||||
overflow: hidden
|
||||
transform: translateZ(0)
|
||||
border: 1px solid var(--role-color-hex)
|
||||
transition: background-color 0.1s ease-in-out, border-left-width 0.3s ease-in-out, border-color 0.1s ease-in-out, transform 0.1s ease-in-out, box-shadow 0.1s ease-out
|
||||
&.selected
|
||||
// This **must** be width-1, otherwise blink adds width to the boundaries.
|
||||
border-left-width: 21px
|
||||
|
||||
|
||||
|
||||
&.role__drag
|
||||
box-shadow: none
|
||||
&:hover
|
||||
transform: translateZ(0) translateY(-1px)
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.3)
|
||||
background-color: rgba(#ccc,0.03)
|
||||
cursor: grab
|
||||
&:active
|
||||
cursor: grabbing
|
||||
|
||||
.role__option:active, .role:active .role__option:not(:active)
|
||||
transform: translateY(0) translateZ(0px) !important
|
|
@ -10,11 +10,13 @@ class Role extends Component {
|
|||
role: PropTypes.object.isRequired,
|
||||
onToggle: PropTypes.func,
|
||||
type: PropTypes.string,
|
||||
selected: PropTypes.bool.isRequired
|
||||
selected: PropTypes.bool,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
render () {
|
||||
let { role, selected } = this.props
|
||||
let { role, selected, disabled, type, provided } = this.props
|
||||
type = type || 'button'
|
||||
|
||||
let color = Color(role.get('color'))
|
||||
|
||||
|
@ -26,8 +28,13 @@ class Role extends Component {
|
|||
let hc = color.lighten(0.1)
|
||||
|
||||
return <div
|
||||
onClick={this.props.onToggle.bind(null, !selected, selected)}
|
||||
className='role font-sans-serif'
|
||||
onClick={() => {
|
||||
if (!disabled && this.props.onToggle != null) {
|
||||
this.props.onToggle(!selected, selected) }
|
||||
}
|
||||
}
|
||||
{...((disabled) ? { 'uk-tooltip': '', title: "I don't have permissions to grant this." } : {})}
|
||||
className={`role font-sans-serif ${(disabled) ? 'disabled' : ''} role__${type}`}
|
||||
style={{
|
||||
'--role-color-hex': c.string(),
|
||||
'--role-color-hover': hc.string(),
|
||||
|
|
|
@ -25,9 +25,9 @@
|
|||
|
||||
&__icon img
|
||||
border-radius: 100%
|
||||
width: 50px
|
||||
height: 50px
|
||||
border: 2px solid transparent
|
||||
max-width: 50px
|
||||
max-height: 50px
|
||||
border: 2px solid transparent
|
||||
|
||||
&__info
|
||||
padding: 0 10px
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Route } from 'react-router-dom'
|
||||
import { CSSTransition, TransitionGroup } from 'react-transition-group'
|
||||
import { Scrollbars } from 'react-custom-scrollbars'
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router'
|
||||
import './index.sass'
|
||||
|
||||
import Navigation from './Navigation'
|
||||
import RolePicker from '../role-picker'
|
||||
import { withRouter } from 'react-router';
|
||||
import RoleEditor from '../role-editor'
|
||||
|
||||
// import mockData from './mockData'
|
||||
|
||||
const mapState = ({ servers, user }) => {
|
||||
const mapState = ({ servers, user, appState }) => {
|
||||
return {
|
||||
servers,
|
||||
user
|
||||
user,
|
||||
fade: appState.fade
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,12 +25,12 @@ class Servers extends Component {
|
|||
render () {
|
||||
return <div className="servers">
|
||||
<Navigation className="servers__nav" servers={this.props.servers} user={this.props.user} />
|
||||
<div className="servers__content">
|
||||
<Scrollbars autoHeight autoHeightMax='calc(100vh - 80px)'>
|
||||
<Route path='/s/:server' component={RolePicker} />
|
||||
<Route path='/s/:server/edit' component={RolePicker} />
|
||||
</Scrollbars>
|
||||
</div>
|
||||
<div className='servers__content'>
|
||||
<Scrollbars className={`fade-element ${(this.props.fade) ? 'fade' : ''}`} autoHeight autoHeightMax='calc(100vh - 80px)'>
|
||||
<Route path='/s/:server' component={RolePicker} exact />
|
||||
</Scrollbars>
|
||||
<Route path='/s/:server/edit' component={RoleEditor} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,4 +50,13 @@ h1,h2,h3,h4,h5,h6 {
|
|||
|
||||
.uk-navbar-nav>li>a {
|
||||
color: var(--c-7);
|
||||
}
|
||||
|
||||
.fade-element {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.fade {
|
||||
opacity: 0;
|
||||
}
|
|
@ -3,20 +3,28 @@ import { combineReducers } from 'redux'
|
|||
import servers from './servers'
|
||||
import user from './user'
|
||||
import rolePicker from './role-picker'
|
||||
import { routerMiddleware } from 'react-router-redux';
|
||||
import { routerMiddleware } from 'react-router-redux'
|
||||
// import roles from './roles'
|
||||
|
||||
const initialState = {
|
||||
ready: false
|
||||
ready: false,
|
||||
fade: true
|
||||
}
|
||||
|
||||
const appState = (state = initialState, { type }) => {
|
||||
const appState = (state = initialState, { type, data }) => {
|
||||
switch (type) {
|
||||
case Symbol.for('app ready'):
|
||||
return {
|
||||
ready: true
|
||||
ready: true,
|
||||
fade: false
|
||||
}
|
||||
|
||||
|
||||
case Symbol.for('app fade'):
|
||||
return {
|
||||
...state,
|
||||
fade: data
|
||||
}
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue