diff --git a/Server/api/servers.js b/Server/api/servers.js index 357123c..0e6ef99 100644 --- a/Server/api/servers.js +++ b/Server/api/servers.js @@ -1,10 +1,14 @@ module.exports = (R, $) => { R.get('/api/servers', async (ctx) => { - const { userId } = ctx.session - const srv = $.discord.getRelevantServers(userId) - const presentable = await $.P.oldPresentableServers(srv, userId) + try { + const { userId } = ctx.session + const srv = $.discord.getRelevantServers(userId) + const presentable = await $.P.oldPresentableServers(srv, userId) - ctx.body = presentable + ctx.body = presentable + } catch (e) { + console.error(e.trace) + } }) R.get('/api/server/:id', async (ctx) => { @@ -33,15 +37,13 @@ module.exports = (R, $) => { const { added, removed } = ctx.request.body if (added.length > 0) { - gm = await gm.addRoles(added) + gm = await gm.addRoles(added.filter(r => $.discord.safeRole(server, r))) } if (removed.length > 0) { - gm = await gm.removeRoles(removed) + gm = await gm.removeRoles(removed.filter(r => $.discord.safeRole(server, r))) } - console.log(gm.roles) - ctx.body = { ok: true } }) } diff --git a/Server/services/discord.js b/Server/services/discord.js index d1ca09f..d039434 100644 --- a/Server/services/discord.js +++ b/Server/services/discord.js @@ -15,7 +15,10 @@ class DiscordService extends Service { this.client = new discord.Client() this.startBot() + } + ownGm (server) { + return this.gm(server, this.client.user.id) } async startBot () { @@ -45,6 +48,11 @@ class DiscordService extends Service { } } + safeRole (server, role) { + const r = this.getRoles(server).get(role) + return r.editable && !r.hasPermission('MANAGE_ROLES', false, true) + } + // oauth step 2 flow, grab the auth token via code async getAuthToken (code) { const url = 'https://discordapp.com/api/oauth2/token' diff --git a/Server/services/presentation.js b/Server/services/presentation.js index 9627ed0..f1232ec 100644 --- a/Server/services/presentation.js +++ b/Server/services/presentation.js @@ -42,13 +42,14 @@ class PresentationService extends Service { async rolesByServer (server) { return server.roles - .filter(r => r.id !== server.id) // get rid of @everyone - .map(r => ({ - id: r.id, - color: r.color, - name: r.name, - position: r.position - })) + .filter(r => r.id !== server.id) // get rid of @everyone + .map(r => ({ + id: r.id, + color: r.color, + name: r.name, + position: r.position, + safe: this.discord.safeRole(server.id, r.id) + })) } } diff --git a/UI/package.json b/UI/package.json index a77593c..9443e7d 100644 --- a/UI/package.json +++ b/UI/package.json @@ -11,12 +11,15 @@ "prop-types": "^15.6.0", "react": "^16.2.0", "react-custom-scrollbars": "^4.2.1", + "react-dnd": "^2.5.4", + "react-dnd-html5-backend": "^2.5.4", "react-dom": "^16.2.0", "react-immutable-proptypes": "^2.1.0", "react-redux": "^5.0.6", "react-router": "^4.2.0", "react-router-dom": "^4.2.2", "react-router-redux": "^5.0.0-alpha.8", + "react-transition-group": "^2.2.1", "redux": "^3.7.2", "redux-devtools": "^3.4.1", "redux-devtools-dock-monitor": "^1.1.2", diff --git a/UI/src/actions/ui.js b/UI/src/actions/ui.js new file mode 100644 index 0000000..2182bea --- /dev/null +++ b/UI/src/actions/ui.js @@ -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 +} diff --git a/UI/src/components/role-editor/Category.js b/UI/src/components/role-editor/Category.js new file mode 100644 index 0000000..592a565 --- /dev/null +++ b/UI/src/components/role-editor/Category.js @@ -0,0 +1,21 @@ +import React, { Component } from 'react' + +import Role from '../role' + +class Category extends Component { + render () { + const { category, name } = this.props + + return
+

{ name }

+ { + category.get('roles_map') + .sortBy(r => r.get('position')) + .reverse() + .map((r, k) => ) + .toArray() + } +
+ } +} +export default Category diff --git a/UI/src/components/role-editor/RoleEditor.sass b/UI/src/components/role-editor/RoleEditor.sass new file mode 100644 index 0000000..5922999 --- /dev/null +++ b/UI/src/components/role-editor/RoleEditor.sass @@ -0,0 +1,6 @@ +.role-editor + &__grid + display: grid + grid-template-areas: 'left right' + grid-template-columns: 1fr + grid-template-rows: 1fr 1fr \ No newline at end of file diff --git a/UI/src/components/role-editor/actions.js b/UI/src/components/role-editor/actions.js new file mode 100644 index 0000000..e69de29 diff --git a/UI/src/components/role-editor/index.js b/UI/src/components/role-editor/index.js new file mode 100644 index 0000000..3bb1f1d --- /dev/null +++ b/UI/src/components/role-editor/index.js @@ -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
+
+
+ + { + vm + .filter((_, k) => k !== 'Unassigned') + .map((c, name) => ) + .toArray() + } + +
+
+ { + (vm.getIn(['Uncategorized', 'roles_map']) || Set()) + .sortBy(r => r.get('position')) + .reverse() + .map((r, k) => ) + .toArray() + } +
+
+
+ } +} + +export default RoleEditor diff --git a/UI/src/components/role-picker/Category.js b/UI/src/components/role-picker/Category.js index fd9c77b..bd60b1a 100644 --- a/UI/src/components/role-picker/Category.js +++ b/UI/src/components/role-picker/Category.js @@ -32,10 +32,14 @@ class Category extends Component { return

{ name }

{ - 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 - }).toArray() + return + }) + .toArray() }
} diff --git a/UI/src/components/role-picker/RolePicker.sass b/UI/src/components/role-picker/RolePicker.sass index 0ccd55f..578e295 100644 --- a/UI/src/components/role-picker/RolePicker.sass +++ b/UI/src/components/role-picker/RolePicker.sass @@ -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 diff --git a/UI/src/components/role-picker/actions.js b/UI/src/components/role-picker/actions.js index 408d5c5..3e92ba6 100644 --- a/UI/src/components/role-picker/actions.js +++ b/UI/src/components/role-picker/actions.js @@ -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) => { diff --git a/UI/src/components/role-picker/index.js b/UI/src/components/role-picker/index.js index 4f8a261..ff2e430 100644 --- a/UI/src/components/role-picker/index.js +++ b/UI/src/components/role-picker/index.js @@ -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
+

Server Message

+

{msg}

+
+ } + + if (roleManager) { + return
+
+

Server Message

+ +
+

{msg || no server message}

+
+ } + + return null + } + render () { const { data, server, dispatch } = this.props const vm = data.get('viewMap') @@ -46,16 +71,9 @@ class RolePicker extends Component { } return
- {/* */} - { (server.get('message') !== '') - ?
-

Server Message

-

{server.get('message')}

-
- : null - } + { this.renderServerMessage(server) }
-
+

Roles

@@ -69,12 +87,10 @@ class RolePicker extends Component {
{ - vm.map((c, name) => dispatch(Actions.updateRoles(roles))} />).toArray() + vm.map((c, name) => dispatch(Actions.updateRoles(roles))} />).toArray() }
- {/*
*/} -
} } diff --git a/UI/src/components/role/Role.sass b/UI/src/components/role/Role.sass index 3f9498b..5c29529 100644 --- a/UI/src/components/role/Role.sass +++ b/UI/src/components/role/Role.sass @@ -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 \ No newline at end of file diff --git a/UI/src/components/role/index.js b/UI/src/components/role/index.js index 69ce9b5..a08055e 100644 --- a/UI/src/components/role/index.js +++ b/UI/src/components/role/index.js @@ -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
{ + 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(), diff --git a/UI/src/components/servers/ServerCard.sass b/UI/src/components/servers/ServerCard.sass index b49dbe0..005de99 100644 --- a/UI/src/components/servers/ServerCard.sass +++ b/UI/src/components/servers/ServerCard.sass @@ -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 diff --git a/UI/src/components/servers/index.js b/UI/src/components/servers/index.js index c56bd90..4d596c1 100644 --- a/UI/src/components/servers/index.js +++ b/UI/src/components/servers/index.js @@ -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
-
- - - - -
+
+ + + + +
} } diff --git a/UI/src/index.css b/UI/src/index.css index 29c0e0b..9b0e461 100644 --- a/UI/src/index.css +++ b/UI/src/index.css @@ -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; } \ No newline at end of file diff --git a/UI/src/reducers/index.js b/UI/src/reducers/index.js index 6c84228..7056466 100644 --- a/UI/src/reducers/index.js +++ b/UI/src/reducers/index.js @@ -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 } diff --git a/UI/yarn.lock b/UI/yarn.lock index bded731..d9d8c82 100644 --- a/UI/yarn.lock +++ b/UI/yarn.lock @@ -263,7 +263,7 @@ arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" -asap@~2.0.3: +asap@^2.0.6, asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -1542,6 +1542,10 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" +chain-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc" + chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -1600,6 +1604,10 @@ clap@^1.0.9: dependencies: chalk "^1.1.3" +classnames@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" + clean-css@4.1.x: version "4.1.9" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.9.tgz#35cee8ae7687a49b98034f70de00c4edd3826301" @@ -2272,6 +2280,19 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +disposables@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/disposables/-/disposables-1.0.1.tgz#064727a25b54f502bd82b89aa2dfb8df9f1b39e3" + +dnd-core@^2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-2.5.4.tgz#0c70a8dcbb609c0b222e275fcae9fa83e5897397" + dependencies: + asap "^2.0.6" + invariant "^2.0.0" + lodash "^4.2.0" + redux "^3.7.1" + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" @@ -2316,6 +2337,10 @@ dom-css@^2.0.0: prefix-style "2.0.1" to-camel-case "1.0.0" +dom-helpers@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6" + dom-serializer@0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" @@ -3574,7 +3599,7 @@ hoek@4.x.x: version "4.2.0" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" -hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0: +hoist-non-react-statics@^2.1.0, hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" @@ -3843,7 +3868,7 @@ interpret@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" -invariant@^2.0.0, invariant@^2.2.1, invariant@^2.2.2: +invariant@^2.0.0, invariant@^2.1.0, invariant@^2.2.1, invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" dependencies: @@ -6059,6 +6084,23 @@ react-dev-utils@^3.1.0: strip-ansi "3.0.1" text-table "0.2.0" +react-dnd-html5-backend@^2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-2.5.4.tgz#974ad083f67b12d56977a5b171f5ffeb29d78352" + dependencies: + lodash "^4.2.0" + +react-dnd@^2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-2.5.4.tgz#0b6dc5e9d0dfc2909f4f4fe736e5534f3afd1bd9" + dependencies: + disposables "^1.0.1" + dnd-core "^2.5.4" + hoist-non-react-statics "^2.1.0" + invariant "^2.1.0" + lodash "^4.2.0" + prop-types "^15.5.10" + react-dock@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/react-dock/-/react-dock-0.2.4.tgz#e727dc7550b3b73116635dcb9c0e04d0b7afe17c" @@ -6144,6 +6186,17 @@ react-router@^4.2.0: prop-types "^15.5.4" warning "^3.0.0" +react-transition-group@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.2.1.tgz#e9fb677b79e6455fd391b03823afe84849df4a10" + dependencies: + chain-function "^1.0.0" + classnames "^2.2.5" + dom-helpers "^3.2.0" + loose-envify "^1.3.1" + prop-types "^15.5.8" + warning "^3.0.0" + react@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba" @@ -6302,7 +6355,7 @@ redux-thunk@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5" -redux@^3.7.2: +redux@^3.7.1, redux@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" dependencies: