mirror of
https://github.com/roleypoly/roleypoly-v1.git
synced 2025-04-25 12:19:10 +00:00
finish MVP
This commit is contained in:
parent
7806219464
commit
eaa1167f16
22 changed files with 486 additions and 116 deletions
|
@ -3,7 +3,7 @@ module.exports = (R, $) => {
|
||||||
try {
|
try {
|
||||||
const { userId } = ctx.session
|
const { userId } = ctx.session
|
||||||
const srv = $.discord.getRelevantServers(userId)
|
const srv = $.discord.getRelevantServers(userId)
|
||||||
const presentable = await $.P.oldPresentableServers(srv, userId)
|
const presentable = await $.P.presentableServers(srv, userId)
|
||||||
|
|
||||||
ctx.body = presentable
|
ctx.body = presentable
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -23,12 +23,35 @@ module.exports = (R, $) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const gm = srv.members.get(userId)
|
const gm = $.discord.gm(id, userId)
|
||||||
const server = $.discord.presentableRoles(id, gm)
|
const server = await $.P.presentableServer(srv, gm)
|
||||||
|
|
||||||
ctx.body = server
|
ctx.body = server
|
||||||
})
|
})
|
||||||
|
|
||||||
|
R.patch('/api/server/:id', async (ctx) => {
|
||||||
|
const { userId } = ctx.session
|
||||||
|
const { id } = ctx.params
|
||||||
|
let gm = $.discord.gm(id, userId)
|
||||||
|
|
||||||
|
// check perms
|
||||||
|
if (!$.discord.getPermissions(gm).canManageRoles) {
|
||||||
|
ctx.status = 403
|
||||||
|
ctx.body = { err: 'cannot_manage_roles' }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { message = null, categories = null } = ctx.request.body
|
||||||
|
|
||||||
|
// todo make less nasty
|
||||||
|
await $.server.update(id, {
|
||||||
|
...((message != null) ? { message } : {}),
|
||||||
|
...((categories != null) ? { categories } : {})
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.body = { ok: true }
|
||||||
|
})
|
||||||
|
|
||||||
R.patch('/api/servers/:server/roles', async ctx => {
|
R.patch('/api/servers/:server/roles', async ctx => {
|
||||||
const { userId } = ctx.session
|
const { userId } = ctx.session
|
||||||
const { server } = ctx.params
|
const { server } = ctx.params
|
||||||
|
@ -36,12 +59,16 @@ module.exports = (R, $) => {
|
||||||
|
|
||||||
const { added, removed } = ctx.request.body
|
const { added, removed } = ctx.request.body
|
||||||
|
|
||||||
|
const allowedRoles = await $.server.getAllowedRoles(server)
|
||||||
|
|
||||||
|
const pred = r => $.discord.safeRole(server, r) && allowedRoles.indexOf(r) !== -1
|
||||||
|
|
||||||
if (added.length > 0) {
|
if (added.length > 0) {
|
||||||
gm = await gm.addRoles(added.filter(r => $.discord.safeRole(server, r)))
|
gm = await gm.addRoles(added.filter(pred))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removed.length > 0) {
|
if (removed.length > 0) {
|
||||||
gm = await gm.removeRoles(removed.filter(r => $.discord.safeRole(server, r)))
|
gm = await gm.removeRoles(removed.filter(pred))
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.body = { ok: true }
|
ctx.body = { ok: true }
|
||||||
|
|
|
@ -8,6 +8,15 @@ const _io = require('socket.io')
|
||||||
const router = require('koa-better-router')().loadMethods()
|
const router = require('koa-better-router')().loadMethods()
|
||||||
const Roleypoly = require('./Roleypoly')
|
const Roleypoly = require('./Roleypoly')
|
||||||
|
|
||||||
|
// monkey patch async-reduce because F U T U R E
|
||||||
|
Array.prototype.areduce = async function (predicate, acc = []) { // eslint-disable-line
|
||||||
|
for (let i of this) {
|
||||||
|
acc = await predicate(acc, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
// Create the server and socket.io server
|
// Create the server and socket.io server
|
||||||
const server = http.createServer(app.callback())
|
const server = http.createServer(app.callback())
|
||||||
const io = _io(server, { transports: ['websocket'], path: '/api/socket.io', wsEngine: 'uws' })
|
const io = _io(server, { transports: ['websocket'], path: '/api/socket.io', wsEngine: 'uws' })
|
||||||
|
|
|
@ -11,6 +11,7 @@ class DiscordService extends Service {
|
||||||
this.clientSecret = process.env.DISCORD_CLIENT_SECRET
|
this.clientSecret = process.env.DISCORD_CLIENT_SECRET
|
||||||
this.oauthCallback = process.env.OAUTH_AUTH_CALLBACK
|
this.oauthCallback = process.env.OAUTH_AUTH_CALLBACK
|
||||||
this.botCallback = `${ctx.config.appUrl}/api/oauth/bot/callback`
|
this.botCallback = `${ctx.config.appUrl}/api/oauth/bot/callback`
|
||||||
|
this.appUrl = process.env.APP_URL
|
||||||
|
|
||||||
this.client = new discord.Client()
|
this.client = new discord.Client()
|
||||||
|
|
||||||
|
@ -24,6 +25,8 @@ class DiscordService extends Service {
|
||||||
async startBot () {
|
async startBot () {
|
||||||
await this.client.login(this.botToken)
|
await this.client.login(this.botToken)
|
||||||
|
|
||||||
|
this.client.on('message', this.handleMessage.bind(this))
|
||||||
|
|
||||||
for (let server of this.client.guilds.array()) {
|
for (let server of this.client.guilds.array()) {
|
||||||
await this.ctx.server.ensure(server)
|
await this.ctx.server.ensure(server)
|
||||||
}
|
}
|
||||||
|
@ -127,6 +130,20 @@ class DiscordService extends Service {
|
||||||
getBotJoinUrl () {
|
getBotJoinUrl () {
|
||||||
return `https://discordapp.com/oauth2/authorize?client_id=${this.clientId}&scope=bot&permissions=268435456&redirect_uri=${this.botCallback}`
|
return `https://discordapp.com/oauth2/authorize?client_id=${this.clientId}&scope=bot&permissions=268435456&redirect_uri=${this.botCallback}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mentionResponse (message) {
|
||||||
|
message.channel.send(`🔰 Assign your roles here! <${this.appUrl}/s/${message.guild.id}>`, { disableEveryone: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMessage (message) {
|
||||||
|
if (message.author.bot && message.channel.type !== 'text') { // drop bot messages and dms
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.mentions.users.has(this.client.user.id)) {
|
||||||
|
this.mentionResponse(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = DiscordService
|
module.exports = DiscordService
|
||||||
|
|
|
@ -14,32 +14,44 @@ class PresentationService extends Service {
|
||||||
let servers = []
|
let servers = []
|
||||||
|
|
||||||
for (let server of collection.array()) {
|
for (let server of collection.array()) {
|
||||||
const sd = await this.ctx.server.get(server.id)
|
|
||||||
console.log(sd.categories)
|
|
||||||
const gm = server.members.get(userId)
|
const gm = server.members.get(userId)
|
||||||
|
|
||||||
servers.push({
|
servers.push(await this.presentableServer(server, gm))
|
||||||
id: server.id,
|
|
||||||
gm: {
|
|
||||||
nickname: gm.nickname,
|
|
||||||
color: gm.displayHexColor
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
id: server.id,
|
|
||||||
name: server.name,
|
|
||||||
ownerID: server.ownerID,
|
|
||||||
icon: server.icon
|
|
||||||
},
|
|
||||||
roles: (await this.rolesByServer(server, sd)).map(r => ({ ...r, selected: gm.roles.has(r.id) })),
|
|
||||||
message: sd.message,
|
|
||||||
categories: sd.categories,
|
|
||||||
perms: this.discord.getPermissions(gm)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers
|
return servers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async presentableServers (collection, userId) {
|
||||||
|
return collection.array().areduce(async (acc, server) => {
|
||||||
|
const gm = server.members.get(userId)
|
||||||
|
acc.push(await this.presentableServer(server, gm))
|
||||||
|
return acc
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async presentableServer (server, gm) {
|
||||||
|
const sd = await this.ctx.server.get(server.id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: server.id,
|
||||||
|
gm: {
|
||||||
|
nickname: gm.nickname,
|
||||||
|
color: gm.displayHexColor
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
id: server.id,
|
||||||
|
name: server.name,
|
||||||
|
ownerID: server.ownerID,
|
||||||
|
icon: server.icon
|
||||||
|
},
|
||||||
|
roles: (await this.rolesByServer(server, sd)).map(r => ({ ...r, selected: gm.roles.has(r.id) })),
|
||||||
|
message: sd.message,
|
||||||
|
categories: sd.categories,
|
||||||
|
perms: this.discord.getPermissions(gm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async rolesByServer (server) {
|
async rolesByServer (server) {
|
||||||
return server.roles
|
return server.roles
|
||||||
.filter(r => r.id !== server.id) // get rid of @everyone
|
.filter(r => r.id !== server.id) // get rid of @everyone
|
||||||
|
|
|
@ -30,18 +30,36 @@ class ServerService extends Service {
|
||||||
return srv.save()
|
return srv.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
update (id, newData) {
|
async update (id, newData) {
|
||||||
const srv = this.get(id)
|
const srv = await this.get(id, false)
|
||||||
|
|
||||||
return srv.update(newData)
|
return srv.update(newData)
|
||||||
}
|
}
|
||||||
|
|
||||||
async get (id) {
|
async get (id, plain = true) {
|
||||||
return (await this.Server.findOne({
|
const s = await this.Server.findOne({
|
||||||
where: {
|
where: {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
})).get({ plain: true })
|
})
|
||||||
|
|
||||||
|
if (!plain) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.get({ plain: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllowedRoles (id) {
|
||||||
|
const server = await this.get(id)
|
||||||
|
|
||||||
|
return Object.values(server.categories).reduce((acc, c) => {
|
||||||
|
if (c.hidden !== true) {
|
||||||
|
return acc.concat(c.roles)
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"eslint": "^4.14.0",
|
"eslint": "^4.14.0",
|
||||||
"history": "^4.7.2",
|
"history": "^4.7.2",
|
||||||
"immutable": "^3.8.2",
|
"immutable": "^3.8.2",
|
||||||
|
"ksuid": "^0.4.0",
|
||||||
"prop-types": "^15.6.0",
|
"prop-types": "^15.6.0",
|
||||||
"react": "^16.2.0",
|
"react": "^16.2.0",
|
||||||
"react-custom-scrollbars": "^4.2.1",
|
"react-custom-scrollbars": "^4.2.1",
|
||||||
|
|
|
@ -8,8 +8,8 @@ import CategoryEditor from './CategoryEditor'
|
||||||
drop (props, monitor, element) {
|
drop (props, monitor, element) {
|
||||||
props.onDrop(monitor.getItem())
|
props.onDrop(monitor.getItem())
|
||||||
},
|
},
|
||||||
canDrop (props) {
|
canDrop (props, monitor) {
|
||||||
return props.mode !== Symbol.for('edit')
|
return (props.mode !== Symbol.for('edit') && monitor.getItem().category !== props.name)
|
||||||
}
|
}
|
||||||
}, (connect, monitor) => ({
|
}, (connect, monitor) => ({
|
||||||
connectDropTarget: connect.dropTarget(),
|
connectDropTarget: connect.dropTarget(),
|
||||||
|
@ -20,14 +20,17 @@ import CategoryEditor from './CategoryEditor'
|
||||||
}))
|
}))
|
||||||
class Category extends Component {
|
class Category extends Component {
|
||||||
render () {
|
render () {
|
||||||
const { category, name, isOver, connectDropTarget, mode, ...rest } = this.props
|
const { category, name, isOver, canDrop, connectDropTarget, mode, onEditOpen, ...rest } = this.props
|
||||||
|
|
||||||
if (mode === Symbol.for('edit')) {
|
if (mode === Symbol.for('edit')) {
|
||||||
return <CategoryEditor category={category} name={name} {...rest} />
|
return <CategoryEditor category={category} name={name} {...rest} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return connectDropTarget(<div key={name} className={`role-picker__category ${(isOver) ? 'is-over' : ''}`}>
|
return connectDropTarget(<div key={name} className={`role-editor__category drop-zone ${(canDrop) ? 'can-drop' : ''} ${(isOver && canDrop) ? 'is-over' : ''}`}>
|
||||||
<h4>{ name }</h4>
|
<div className='role-editor__category-header'>
|
||||||
|
<h4>{ category.get('name') }</h4>
|
||||||
|
<div uk-tooltip='' title='Edit' uk-icon="icon: file-edit" onClick={onEditOpen} />
|
||||||
|
</div>
|
||||||
{
|
{
|
||||||
category.get('roles_map')
|
category.get('roles_map')
|
||||||
.sortBy(r => r.get('position'))
|
.sortBy(r => r.get('position'))
|
||||||
|
|
|
@ -1,18 +1,28 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
|
|
||||||
export default class CategoryEditor extends Component {
|
export default class CategoryEditor extends Component {
|
||||||
|
|
||||||
|
onKeyPress = (e) => {
|
||||||
|
const { onSave } = this.props
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case 'Enter':
|
||||||
|
case 'Escape':
|
||||||
|
return onSave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
name,
|
|
||||||
category
|
category
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
return <div className="role-editor__category editor">
|
return <div className="role-editor__category editor" onKeyDown={this.onKeyPress}>
|
||||||
<form onSubmit={(e) => e.preventDefault()} className="uk-form-stacked uk-light">
|
<div className="uk-form-stacked uk-light">
|
||||||
<div>
|
<div>
|
||||||
<label className="uk-form-label">Category Name</label>
|
<label className="uk-form-label">Category Name</label>
|
||||||
<div className="uk-form-controls">
|
<div className="uk-form-controls">
|
||||||
<input type="text" className="uk-input" placeholder='' value={name} onChange={this.props.onEdit('name', Symbol.for('edit: text'))} />
|
<input type="text" className="uk-input" placeholder='' value={category.get('name')} onChange={this.props.onEdit('name', Symbol.for('edit: text'))} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 10 }}>
|
<div style={{ marginTop: 10 }}>
|
||||||
|
@ -21,7 +31,7 @@ export default class CategoryEditor extends Component {
|
||||||
<input
|
<input
|
||||||
style={{ marginRight: 5 }}
|
style={{ marginRight: 5 }}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="uk-checkbox"
|
className="uk-checkbox uk-light"
|
||||||
checked={category.get('hidden')}
|
checked={category.get('hidden')}
|
||||||
onChange={this.props.onEdit('hidden', Symbol.for('edit: bool'))}
|
onChange={this.props.onEdit('hidden', Symbol.for('edit: bool'))}
|
||||||
/>
|
/>
|
||||||
|
@ -29,13 +39,26 @@ export default class CategoryEditor extends Component {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ marginTop: 10 }}>
|
||||||
|
<div className="uk-form-label">Type <i uk-icon="icon: info; ratio: 0.7" uk-tooltip="" title="Single mode only lets a user pick one role in this category." /></div>
|
||||||
|
<div className="uk-form-controls">
|
||||||
|
<select
|
||||||
|
className="uk-select"
|
||||||
|
value={category.get('type')}
|
||||||
|
onChange={this.props.onEdit('type', Symbol.for('edit: select'))}
|
||||||
|
>
|
||||||
|
<option value='multi'>Multiple</option>
|
||||||
|
<option value='single'>Single</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className='role-editor__actions'>
|
<div className='role-editor__actions'>
|
||||||
<button className="uk-button rp-button secondary role-editor__actions_delete" onClick={this.props.onDelete}>
|
<button className="uk-button rp-button secondary role-editor__actions_delete" onClick={this.props.onDelete}>
|
||||||
<i uk-icon="icon: trash" />
|
<i uk-icon="icon: trash" />
|
||||||
</button>
|
</button>
|
||||||
<button className="uk-button rp-button primary role-editor__actions_save" onClick={this.props.onSave}>Save</button>
|
<button className="uk-button rp-button primary role-editor__actions_save" onClick={this.props.onSave}>Save</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
&__actions
|
&__actions
|
||||||
display: flex
|
display: flex
|
||||||
margin-top: 10px
|
margin: 10px 0
|
||||||
|
|
||||||
button
|
button
|
||||||
padding: 0
|
padding: 0
|
||||||
|
@ -19,12 +19,16 @@
|
||||||
&_save
|
&_save
|
||||||
flex: 4
|
flex: 4
|
||||||
|
|
||||||
|
&__uncat-zone
|
||||||
|
padding: 10px
|
||||||
|
|
||||||
.role-editor__category
|
.role-editor__category
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
background-color: var(--c-1)
|
background-color: var(--c-1)
|
||||||
padding: 15px
|
padding: 15px
|
||||||
margin: 10px
|
margin: 10px
|
||||||
min-width: 220px - 30px
|
min-width: 220px - 30px
|
||||||
|
position: relative
|
||||||
|
|
||||||
&.add-button
|
&.add-button
|
||||||
height: 100px
|
height: 100px
|
||||||
|
@ -43,4 +47,44 @@
|
||||||
transform: scale(1.1)
|
transform: scale(1.1)
|
||||||
color: var(--c-7)
|
color: var(--c-7)
|
||||||
|
|
||||||
|
&-header
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: left
|
||||||
|
margin-bottom: 10px
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
h4
|
||||||
|
margin: 0
|
||||||
|
flex: 1
|
||||||
|
margin-right: 10px
|
||||||
|
|
||||||
|
.drop-zone
|
||||||
|
position: relative
|
||||||
|
&::after
|
||||||
|
content: ""
|
||||||
|
background-color: var(--c-7)
|
||||||
|
box-sizing: border-box
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
right: 0
|
||||||
|
left: 0
|
||||||
|
bottom: 0
|
||||||
|
border: 5px dashed var(--c-3)
|
||||||
|
transition: opacity 0.15s ease-in-out
|
||||||
|
opacity: 0
|
||||||
|
pointer-events: none
|
||||||
|
|
||||||
|
&.is-over::after
|
||||||
|
opacity: 0.5
|
||||||
|
|
||||||
|
&.can-drop::after
|
||||||
|
opacity: 0.2
|
|
@ -1,6 +1,8 @@
|
||||||
import { Set } from 'immutable'
|
import { Set } from 'immutable'
|
||||||
import * as UIActions from '../../actions/ui'
|
import * as UIActions from '../../actions/ui'
|
||||||
import { getViewMap } from '../role-picker/actions'
|
import { getViewMap } from '../role-picker/actions'
|
||||||
|
import ksuid from 'ksuid'
|
||||||
|
import superagent from 'superagent'
|
||||||
|
|
||||||
export const constructView = id => (dispatch, getState) => {
|
export const constructView = id => (dispatch, getState) => {
|
||||||
const server = getState().servers.get(id)
|
const server = getState().servers.get(id)
|
||||||
|
@ -11,78 +13,133 @@ export const constructView = id => (dispatch, getState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: Symbol.for('re: setup'),
|
type: Symbol.for('re: setup'),
|
||||||
data: {
|
data: {
|
||||||
viewMap: viewMap
|
viewMap: viewMap,
|
||||||
|
originalSnapshot: viewMap
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
dispatch(UIActions.fadeIn)
|
dispatch(UIActions.fadeIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addRoleToCategory = (name, oldName, role, flip = true) => (dispatch) => {
|
export const addRoleToCategory = (id, oldId, role, flip = true) => (dispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: Symbol.for('re: add role to category'),
|
type: Symbol.for('re: add role to category'),
|
||||||
data: {
|
data: {
|
||||||
name,
|
id,
|
||||||
role
|
role
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (flip) {
|
if (flip) {
|
||||||
dispatch(removeRoleFromCategory(oldName, name, role, false))
|
dispatch(removeRoleFromCategory(oldId, id, role, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removeRoleFromCategory = (name, oldName, role, flip = true) => (dispatch) => {
|
export const removeRoleFromCategory = (id, oldId, role, flip = true) => (dispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: Symbol.for('re: remove role from category'),
|
type: Symbol.for('re: remove role from category'),
|
||||||
data: {
|
data: {
|
||||||
name,
|
id,
|
||||||
role
|
role
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (flip) {
|
if (flip) {
|
||||||
dispatch(addRoleToCategory(oldName, name, role, false))
|
dispatch(addRoleToCategory(oldId, id, role, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const editCategory = ({ id, key, value }) => dispatch => {
|
||||||
|
dispatch({
|
||||||
|
type: Symbol.for('re: edit category'),
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
key,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const editCategory = (stuff) => dispatch => {
|
export const saveCategory = (id, category) => (dispatch) => {
|
||||||
|
if (category.get('name') === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: Symbol.for('re: switch category mode'),
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
mode: Symbol.for('drop')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const saveCategory = (name) => ({
|
export const openEditor = (id) => ({
|
||||||
type: Symbol.for('re: switch category mode'),
|
type: Symbol.for('re: switch category mode'),
|
||||||
data: {
|
data: {
|
||||||
name,
|
id,
|
||||||
mode: Symbol.for('drop')
|
mode: Symbol.for('edit')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const deleteCategory = (name) => ({
|
export const deleteCategory = (id, category) => (dispatch, getState) => {
|
||||||
type: Symbol.for('re: delete category'),
|
const roles = category.get('roles')
|
||||||
data: name
|
const rolesMap = category.get('roles_map')
|
||||||
})
|
|
||||||
|
|
||||||
export const createCategory = (dispatch, getState) => {
|
let uncategorized = getState().roleEditor.getIn(['viewMap', 'Uncategorized'])
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: Symbol.for('re: set category'),
|
||||||
|
data: {
|
||||||
|
id: 'Uncategorized',
|
||||||
|
name: '',
|
||||||
|
roles: uncategorized.get('roles').union(roles),
|
||||||
|
roles_map: uncategorized.get('roles_map').union(rolesMap),
|
||||||
|
hidden: true,
|
||||||
|
type: 'multi',
|
||||||
|
mode: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: Symbol.for('re: delete category'),
|
||||||
|
data: id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createCategory = async (dispatch, getState) => {
|
||||||
const { roleEditor } = getState()
|
const { roleEditor } = getState()
|
||||||
const vm = roleEditor.get('viewMap')
|
const vm = roleEditor.get('viewMap')
|
||||||
|
|
||||||
let name = 'New Category'
|
let name = 'New Category'
|
||||||
let idx = 1
|
let idx = 1
|
||||||
while (vm.has(name)) {
|
while (vm.find(c => c.get('name') === name) !== undefined) {
|
||||||
idx++
|
idx++
|
||||||
name = `New Category ${idx}`
|
name = `New Category ${idx}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const id = (await ksuid.random()).string
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: Symbol.for('re: set category'),
|
type: Symbol.for('re: set category'),
|
||||||
data: {
|
data: {
|
||||||
|
id,
|
||||||
name,
|
name,
|
||||||
roles: Set([]),
|
roles: Set([]),
|
||||||
roles_map: Set([]),
|
roles_map: Set([]),
|
||||||
hidden: true,
|
hidden: true,
|
||||||
|
type: 'multi',
|
||||||
mode: Symbol.for('edit')
|
mode: Symbol.for('edit')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const saveServer = id => async (dispatch, getState) => {
|
||||||
|
const viewMap = getState().roleEditor.get('viewMap')
|
||||||
|
.filterNot((_, k) => k === 'Uncategorized')
|
||||||
|
.map(v => v.delete('roles_map').delete('mode').delete('id'))
|
||||||
|
|
||||||
|
await superagent.patch(`/api/server/${id}`).send({ categories: viewMap.toJS() })
|
||||||
|
dispatch({ type: Symbol.for('re: swap original state') })
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,9 @@ const mapState = ({ rolePicker, roleEditor, servers }, ownProps) => ({
|
||||||
@DropTarget(Symbol.for('dnd: role'), {
|
@DropTarget(Symbol.for('dnd: role'), {
|
||||||
drop (props, monitor, element) {
|
drop (props, monitor, element) {
|
||||||
element.dropRole({}, 'Uncategorized')(monitor.getItem())
|
element.dropRole({}, 'Uncategorized')(monitor.getItem())
|
||||||
|
},
|
||||||
|
canDrop (props, monitor) {
|
||||||
|
return (monitor.getItem().category !== 'Uncategorized')
|
||||||
}
|
}
|
||||||
}, (connect, monitor) => ({
|
}, (connect, monitor) => ({
|
||||||
connectDropTarget: connect.dropTarget(),
|
connectDropTarget: connect.dropTarget(),
|
||||||
|
@ -56,15 +59,20 @@ class RoleEditor extends Component {
|
||||||
|
|
||||||
saveCategory = (category, name) => () => {
|
saveCategory = (category, name) => () => {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
dispatch(Actions.saveCategory(name))
|
dispatch(Actions.saveCategory(name, category))
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteCategory = (category, name) => () => {
|
deleteCategory = (category, id) => () => {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
dispatch(Actions.deleteCategory(name))
|
dispatch(Actions.deleteCategory(id, category))
|
||||||
}
|
}
|
||||||
|
|
||||||
editCategory = (category, name) => (key, type) => event => {
|
openEditor = (category, name) => () => {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
dispatch(Actions.openEditor(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
editCategory = (category, id) => (key, type) => event => {
|
||||||
const { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
let value
|
let value
|
||||||
|
|
||||||
|
@ -77,16 +85,46 @@ class RoleEditor extends Component {
|
||||||
value = event.target.checked
|
value = event.target.checked
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case Symbol.for('edit: select'):
|
||||||
|
value = event.target.value
|
||||||
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
value = null
|
value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(Actions.editCategory({ category, name, key, type, value }))
|
dispatch(Actions.editCategory({ category, id, key, type, value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
resetServer = () => {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
dispatch({ type: Symbol.for('re: reset') })
|
||||||
|
}
|
||||||
|
|
||||||
|
saveServer = () => {
|
||||||
|
const { dispatch, match: { params: { server } } } = this.props
|
||||||
|
dispatch(Actions.saveServer(server))
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasChanged () {
|
||||||
|
return this.props.editor.get('originalSnapshot').hashCode() !== this.props.editor.get('viewMap').hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const vm = this.props.editor.get('viewMap')
|
const vm = this.props.editor.get('viewMap')
|
||||||
return <div className="inner role-editor">
|
return <div className="inner role-editor">
|
||||||
|
<div className="role-picker__header" style={{ marginBottom: 10 }}>
|
||||||
|
<h3>{this.props.server.getIn(['server','name'])}</h3>
|
||||||
|
<div className="role-picker__spacer"></div>
|
||||||
|
<div className={`role-picker__actions ${(!this.hasChanged) ? 'hidden' : ''}`}>
|
||||||
|
<button onClick={this.resetServer} disabled={!this.hasChanged} className="uk-button rp-button secondary">
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
<button onClick={this.saveServer} disabled={!this.hasChanged} className="uk-button rp-button primary">
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<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)'>
|
||||||
|
@ -100,7 +138,7 @@ class RoleEditor extends Component {
|
||||||
mode={c.get('mode')}
|
mode={c.get('mode')}
|
||||||
onDrop={this.dropRole(c, name)}
|
onDrop={this.dropRole(c, name)}
|
||||||
onEdit={this.editCategory(c, name)}
|
onEdit={this.editCategory(c, name)}
|
||||||
// onEditOpen={this.openEditor(c, name)}
|
onEditOpen={this.openEditor(c, name)}
|
||||||
onSave={this.saveCategory(c, name)}
|
onSave={this.saveCategory(c, name)}
|
||||||
onDelete={this.deleteCategory(c, name)}
|
onDelete={this.deleteCategory(c, name)}
|
||||||
/>)
|
/>)
|
||||||
|
@ -113,14 +151,18 @@ class RoleEditor extends Component {
|
||||||
</div>
|
</div>
|
||||||
{
|
{
|
||||||
this.props.connectDropTarget(
|
this.props.connectDropTarget(
|
||||||
<div className="role-editor__grid__right">
|
<div className={`role-editor__grid__right drop-zone ${(this.props.canDrop) ? 'can-drop' : ''} ${(this.props.isOver && this.props.canDrop) ? 'is-over' : ''}`}>
|
||||||
{
|
<Scrollbars autoHeight autoHeightMax='calc(100vh - 145px)'>
|
||||||
(vm.getIn(['Uncategorized', 'roles_map']) || Set())
|
<div className="role-editor__uncat-zone">
|
||||||
.sortBy(r => r.get('position'))
|
{
|
||||||
.reverse()
|
(vm.getIn(['Uncategorized', 'roles_map']) || Set())
|
||||||
.map((r, k) => <Role key={k} categoryId='Uncategorized' role={r} />)
|
.sortBy(r => r.get('position'))
|
||||||
.toArray()
|
.reverse()
|
||||||
}
|
.map((r, k) => <Role key={k} categoryId='Uncategorized' role={r} />)
|
||||||
|
.toArray()
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Scrollbars>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,9 +17,11 @@ class Category extends Component {
|
||||||
const type = this.props.category.get('type')
|
const type = this.props.category.get('type')
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'multi': return this.toggleRoleMulti(id, next)
|
|
||||||
case 'single': return this.toggleRoleSingle(id, next)
|
case 'single': return this.toggleRoleSingle(id, next)
|
||||||
default: console.warn('NOT SURE')
|
case 'multi': return this.toggleRoleMulti(id, next)
|
||||||
|
default:
|
||||||
|
console.warn('DEFAULTING TO MULTI', id, next, old)
|
||||||
|
return this.toggleRoleMulti(id, next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +36,7 @@ class Category extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div key={name} className="role-picker__category">
|
return <div key={name} className="role-picker__category">
|
||||||
<h4>{ name }</h4>
|
<h4>{ category.get('name') }</h4>
|
||||||
{
|
{
|
||||||
category.get('roles_map')
|
category.get('roles_map')
|
||||||
.sortBy(r => r.get('position'))
|
.sortBy(r => r.get('position'))
|
||||||
|
|
|
@ -50,3 +50,14 @@
|
||||||
&.hidden
|
&.hidden
|
||||||
opacity: 0
|
opacity: 0
|
||||||
// display: none
|
// display: none
|
||||||
|
|
||||||
|
&__msg-editor
|
||||||
|
background-color: rgba(0,0,0,0.2)
|
||||||
|
border-color: rgba(0,0,0,0.1)
|
||||||
|
color: var(--c-white)
|
||||||
|
margin: 10px 0
|
||||||
|
|
||||||
|
&:focus, &:active
|
||||||
|
color: var(--c-white)
|
||||||
|
background-color: rgba(0,0,0,0.2)
|
||||||
|
border-color: var(--c-7)
|
|
@ -3,16 +3,18 @@ import superagent from 'superagent'
|
||||||
import * as UIActions from '../../actions/ui'
|
import * as UIActions from '../../actions/ui'
|
||||||
|
|
||||||
export const setup = id => async dispatch => {
|
export const setup = id => async dispatch => {
|
||||||
// const rsp = await superagent.get(`/api/server/${id}`)
|
const rsp = await superagent.get(`/api/server/${id}`)
|
||||||
// const data = rsp.body
|
const data = rsp.body
|
||||||
|
|
||||||
// dispatch({
|
console.log(data)
|
||||||
// type: Symbol.for('update server roles'),
|
|
||||||
// data: {
|
dispatch({
|
||||||
// id,
|
type: Symbol.for('server: set'),
|
||||||
// roles: data
|
data: {
|
||||||
// }
|
id,
|
||||||
// })
|
...data
|
||||||
|
}
|
||||||
|
})
|
||||||
dispatch(constructView(id))
|
dispatch(constructView(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,15 +31,19 @@ export const getViewMap = server => {
|
||||||
|
|
||||||
const viewMap = categories.set('Uncategorized', fromJS({
|
const viewMap = categories.set('Uncategorized', fromJS({
|
||||||
roles: unaccountedRoles,
|
roles: unaccountedRoles,
|
||||||
hidden: false,
|
hidden: true,
|
||||||
type: 'multi'
|
type: 'multi',
|
||||||
|
name: 'Uncategorized'
|
||||||
})).map(c => {
|
})).map(c => {
|
||||||
const roles = c.get('roles')
|
const roles = c.get('roles')
|
||||||
|
// fill in roles_map
|
||||||
.map(r =>
|
.map(r =>
|
||||||
server.get('roles').find(sr => sr.get('id') === r)
|
server.get('roles').find(sr => sr.get('id') === r)
|
||||||
)
|
)
|
||||||
|
// sort by server position, backwards.
|
||||||
.sort((a, b) => a.position > b.position)
|
.sort((a, b) => a.position > b.position)
|
||||||
return c.set('roles_map', roles)
|
// force data to sets
|
||||||
|
return c.set('roles_map', Set(roles)).set('roles', Set(c.get('roles')))
|
||||||
})
|
})
|
||||||
|
|
||||||
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())
|
||||||
|
@ -54,12 +60,13 @@ export const constructView = id => (dispatch, getState) => {
|
||||||
const { viewMap, selected } = getViewMap(server)
|
const { viewMap, selected } = getViewMap(server)
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: Symbol.for('setup role picker'),
|
type: Symbol.for('rp: setup role picker'),
|
||||||
data: {
|
data: {
|
||||||
viewMap: viewMap,
|
viewMap: viewMap,
|
||||||
rolesSelected: selected,
|
rolesSelected: selected,
|
||||||
originalRolesSelected: selected,
|
originalRolesSelected: selected,
|
||||||
hidden: false
|
hidden: false,
|
||||||
|
isEditingMessage: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -68,7 +75,7 @@ export const constructView = id => (dispatch, getState) => {
|
||||||
|
|
||||||
export const resetSelected = (dispatch) => {
|
export const resetSelected = (dispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: Symbol.for('reset selected')
|
type: Symbol.for('rp: reset selected')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,11 +100,35 @@ export const submitSelected = serverId => async (dispatch, getState) => {
|
||||||
await superagent.patch(`/api/servers/${serverId}/roles`).send(diff.toJS())
|
await superagent.patch(`/api/servers/${serverId}/roles`).send(diff.toJS())
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: Symbol.for('sync selected roles')
|
type: Symbol.for('rp: sync selected roles')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateRoles = roles => ({
|
export const updateRoles = roles => ({
|
||||||
type: Symbol.for('update selected roles'),
|
type: Symbol.for('rp: update selected roles'),
|
||||||
data: roles
|
data: roles
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const openMessageEditor = ({
|
||||||
|
type: Symbol.for('rp: set message editor state'),
|
||||||
|
data: true
|
||||||
|
})
|
||||||
|
|
||||||
|
export const saveServerMessage = id => async (dispatch, getState) => {
|
||||||
|
const message = getState().servers.getIn([id, 'message'])
|
||||||
|
|
||||||
|
await superagent.patch(`/api/server/${id}`).send({ message })
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: Symbol.for('rp: set message editor state'),
|
||||||
|
data: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const editServerMessage = (id, message) => ({
|
||||||
|
type: Symbol.for('server: edit message'),
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
@ -39,9 +39,28 @@ class RolePicker extends Component {
|
||||||
return !data.get('rolesSelected').equals(data.get('originalRolesSelected'))
|
return !data.get('rolesSelected').equals(data.get('originalRolesSelected'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editServerMessage = (e) => {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
dispatch(Actions.editServerMessage(this.props.server.get('id'), e.target.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
saveServerMessage = (e) => {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
dispatch(Actions.saveServerMessage(this.props.server.get('id')))
|
||||||
|
}
|
||||||
|
|
||||||
|
openMessageEditor = () => {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
dispatch(Actions.openMessageEditor)
|
||||||
|
}
|
||||||
|
|
||||||
renderServerMessage (server) {
|
renderServerMessage (server) {
|
||||||
|
const isEditing = this.props.data.get('isEditingMessage')
|
||||||
const roleManager = server.getIn(['perms', 'canManageRoles'])
|
const roleManager = server.getIn(['perms', 'canManageRoles'])
|
||||||
const msg = server.get('message')
|
const msg = server.get('message')
|
||||||
|
|
||||||
|
console.log(msg, roleManager, isEditing, this.props.data.toJS())
|
||||||
|
|
||||||
if (!roleManager && msg !== '') {
|
if (!roleManager && msg !== '') {
|
||||||
return <section>
|
return <section>
|
||||||
<h3>Server Message</h3>
|
<h3>Server Message</h3>
|
||||||
|
@ -49,16 +68,26 @@ class RolePicker extends Component {
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roleManager) {
|
if (roleManager && !isEditing) {
|
||||||
return <section>
|
return <section>
|
||||||
<div className="role-picker__header">
|
<div className="role-picker__header">
|
||||||
<h3>Server Message</h3>
|
<h3>Server Message</h3>
|
||||||
<Link to={`/s/${server.get('id')}/edit`} uk-tooltip='' title='Edit' uk-icon="icon: file-edit"></Link>
|
<div uk-tooltip='' title='Edit Server Message' uk-icon="icon: pencil" onClick={this.openMessageEditor} />
|
||||||
</div>
|
</div>
|
||||||
<p>{msg || <i>no server message</i>}</p>
|
<p>{msg || <i>no server message</i>}</p>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (roleManager && isEditing) {
|
||||||
|
return <section>
|
||||||
|
<div className="role-picker__header">
|
||||||
|
<h3>Server Message</h3>
|
||||||
|
<div uk-tooltip='' title='Save Server Message' onClick={this.saveServerMessage} style={{color: 'var(--c-green)'}} uk-icon="icon: check; scale: 1.1" />
|
||||||
|
</div>
|
||||||
|
<textarea className="uk-width-1-2 uk-textarea role-picker__msg-editor" rows="3" onChange={this.editServerMessage} value={msg} />
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +104,7 @@ class RolePicker extends Component {
|
||||||
<section>
|
<section>
|
||||||
<div className="role-picker__header">
|
<div className="role-picker__header">
|
||||||
<h3>Roles</h3>
|
<h3>Roles</h3>
|
||||||
|
<Link to={`/s/${server.get('id')}/edit`} uk-tooltip='' title='Edit Categories' uk-icon="icon: file-edit"></Link>
|
||||||
<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 rp-button secondary">
|
<button disabled={!this.rolesHaveChanged} onClick={() => dispatch(Actions.resetSelected)} className="uk-button rp-button secondary">
|
||||||
|
|
|
@ -21,12 +21,13 @@ class ServerCard extends Component {
|
||||||
const s = server.get('server')
|
const s = server.get('server')
|
||||||
const gm = server.get('gm')
|
const gm = server.get('gm')
|
||||||
const perms = server.get('perms')
|
const perms = server.get('perms')
|
||||||
|
|
||||||
if (perms.get('canManageRoles')) {
|
if (perms.get('canManageRoles')) {
|
||||||
icon = <span title='Role Manager' uk-tooltip='' role='img' aria-label='Role Manager'>🔰</span>
|
icon = <span title='Role Manager' uk-tooltip='' role='img' aria-label='Role Manager' className="server-list__item__tag" uk-icon="icon: bolt; ratio: 0.7" />
|
||||||
}
|
}
|
||||||
|
|
||||||
if (perms.get('isAdmin')) {
|
if (perms.get('isAdmin')) {
|
||||||
icon = <span title='Server Admin' uk-tooltip='' role='img' aria-label='Server Admin'>🔰⭐️</span>
|
icon = <span title='Server Admin' uk-tooltip='' role='img' aria-label='Server Admin' className="server-list__item__tag" uk-icon="icon: star; ratio: 0.7" />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <NavLink className='server-list__item' activeClassName='active' to={`/s/${s.get('id')}`}>
|
return <NavLink className='server-list__item' activeClassName='active' to={`/s/${s.get('id')}`}>
|
||||||
|
|
|
@ -32,6 +32,12 @@
|
||||||
&__info
|
&__info
|
||||||
padding: 0 10px
|
padding: 0 10px
|
||||||
|
|
||||||
|
&__tag
|
||||||
|
color: var(--c-9)
|
||||||
|
position: relative
|
||||||
|
top: -2px
|
||||||
|
// position: inline-block
|
||||||
|
|
||||||
a.server-list__item
|
a.server-list__item
|
||||||
color: var(--c-white)
|
color: var(--c-white)
|
||||||
text-decoration: none
|
text-decoration: none
|
||||||
|
|
|
@ -19,6 +19,7 @@ body {
|
||||||
--c-3: #5d5352;
|
--c-3: #5d5352;
|
||||||
--c-1: #453e3d;
|
--c-1: #453e3d;
|
||||||
--c-dark: #332d2d;
|
--c-dark: #332d2d;
|
||||||
|
--c-green: #46b646;
|
||||||
}
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
|
|
|
@ -1,40 +1,50 @@
|
||||||
import { Map, OrderedMap, fromJS } from 'immutable'
|
import { Map, OrderedMap, fromJS } from 'immutable'
|
||||||
|
|
||||||
const initialState = Map({
|
const initialState = Map({
|
||||||
viewMap: OrderedMap({})
|
viewMap: OrderedMap({}),
|
||||||
|
originalSnapshot: OrderedMap({})
|
||||||
})
|
})
|
||||||
|
|
||||||
const reducer = (state = initialState, { type, data }) => {
|
const reducer = (state = initialState, { type, data }) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Symbol.for('re: setup'):
|
case Symbol.for('re: setup'):
|
||||||
const { viewMap, ...rest } = data
|
const { viewMap, originalSnapshot, ...rest } = data
|
||||||
return Map({ viewMap: OrderedMap(viewMap), ...rest })
|
return Map({ viewMap: OrderedMap(viewMap), originalSnapshot: OrderedMap(originalSnapshot), ...rest })
|
||||||
|
|
||||||
case Symbol.for('re: set category'):
|
case Symbol.for('re: set category'):
|
||||||
return state.setIn(['viewMap', data.name], Map(data))
|
return state.setIn(['viewMap', data.id], Map(data))
|
||||||
|
|
||||||
|
case Symbol.for('re: edit category'):
|
||||||
|
return state.setIn(['viewMap', data.id, data.key], data.value)
|
||||||
|
|
||||||
case Symbol.for('re: delete category'):
|
case Symbol.for('re: delete category'):
|
||||||
return state.deleteIn(['viewMap', data])
|
return state.deleteIn(['viewMap', data])
|
||||||
|
|
||||||
case Symbol.for('re: switch category mode'):
|
case Symbol.for('re: switch category mode'):
|
||||||
return state.setIn(['viewMap', data.name, 'mode'], data.mode)
|
return state.setIn(['viewMap', data.id, 'mode'], data.mode)
|
||||||
|
|
||||||
case Symbol.for('re: add role to category'):
|
case Symbol.for('re: add role to category'):
|
||||||
const category = state.getIn(['viewMap', data.name])
|
const category = state.getIn(['viewMap', data.id])
|
||||||
return state.setIn(['viewMap', data.name],
|
return state.setIn(['viewMap', data.id],
|
||||||
category
|
category
|
||||||
.set('roles', category.get('roles').add(data.role.get('id')))
|
.set('roles', category.get('roles').add(data.role.get('id')))
|
||||||
.set('roles_map', category.get('roles_map').add(data.role))
|
.set('roles_map', category.get('roles_map').add(data.role))
|
||||||
)
|
)
|
||||||
|
|
||||||
case Symbol.for('re: remove role from category'):
|
case Symbol.for('re: remove role from category'):
|
||||||
const rmCat = state.getIn(['viewMap', data.name])
|
const rmCat = state.getIn(['viewMap', data.id])
|
||||||
return state.setIn(['viewMap', data.name],
|
return state.setIn(['viewMap', data.id],
|
||||||
rmCat
|
rmCat
|
||||||
.set('roles', rmCat.get('roles').filterNot(r => r === data.role.get('id')))
|
.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')))
|
.set('roles_map', rmCat.get('roles_map').filterNot(r => r.get('id') === data.role.get('id')))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case Symbol.for('re: reset'):
|
||||||
|
return state.set('viewMap', state.get('originalSnapshot'))
|
||||||
|
|
||||||
|
case Symbol.for('re: swap original state'):
|
||||||
|
return state.set('originalSnapshot', state.get('viewMap'))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Map, OrderedMap } from 'immutable'
|
||||||
const initialState = Map({
|
const initialState = Map({
|
||||||
hidden: true, // should the view be hidden?
|
hidden: true, // should the view be hidden?
|
||||||
// emptyRoles: true, // helps derender roles so there's no visible element state change
|
// emptyRoles: true, // helps derender roles so there's no visible element state change
|
||||||
|
isEditingMessage: false,
|
||||||
viewMap: OrderedMap({}), // roles in categories
|
viewMap: OrderedMap({}), // roles in categories
|
||||||
originalRolesSelected: Map({}), // Map<role id, bool> -- original roles for diffing against selected
|
originalRolesSelected: Map({}), // Map<role id, bool> -- original roles for diffing against selected
|
||||||
rolesSelected: Map({}) // Map<role id, bool> -- new roles for diffing
|
rolesSelected: Map({}) // Map<role id, bool> -- new roles for diffing
|
||||||
|
@ -10,25 +11,29 @@ const initialState = Map({
|
||||||
|
|
||||||
export default (state = initialState, { type, data }) => {
|
export default (state = initialState, { type, data }) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case Symbol.for('setup role picker'):
|
case Symbol.for('rp: setup role picker'):
|
||||||
return Map(data)
|
return Map(data)
|
||||||
|
|
||||||
case Symbol.for('hide role picker ui'):
|
case Symbol.for('rp: hide role picker ui'):
|
||||||
return state.set('hidden', data)
|
return state.set('hidden', data)
|
||||||
|
|
||||||
case Symbol.for('reset role picker ui'):
|
case Symbol.for('rp: reset role picker ui'):
|
||||||
return state.set('emptyRoles', data)
|
return state.set('emptyRoles', data)
|
||||||
|
|
||||||
case Symbol.for('update selected roles'):
|
case Symbol.for('rp: update selected roles'):
|
||||||
return state.mergeIn(['rolesSelected'], data)
|
return state.mergeIn(['rolesSelected'], data)
|
||||||
|
|
||||||
case Symbol.for('sync selected roles'):
|
case Symbol.for('rp: sync selected roles'):
|
||||||
return state.set('originalRolesSelected', state.get('rolesSelected'))
|
return state.set('originalRolesSelected', state.get('rolesSelected'))
|
||||||
|
|
||||||
case Symbol.for('reset selected'):
|
case Symbol.for('rp: reset selected'):
|
||||||
return state.set('rolesSelected', state.get('originalRolesSelected'))
|
return state.set('rolesSelected', state.get('originalRolesSelected'))
|
||||||
|
|
||||||
// case Symbol.for('zero role picker'):
|
case Symbol.for('rp: set message editor state'):
|
||||||
|
console.log(state.set('isEditingMessage', data))
|
||||||
|
return state.set('isEditingMessage', data)
|
||||||
|
|
||||||
|
// case Symbol.for('rp: zero role picker'):
|
||||||
// return initialState
|
// return initialState
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -33,6 +33,12 @@ export default (state = initialState, { type, data }) => {
|
||||||
// state.get(data.id).set('roles', Set(data.roles))
|
// state.get(data.id).set('roles', Set(data.roles))
|
||||||
// )
|
// )
|
||||||
|
|
||||||
|
case Symbol.for('server: set'):
|
||||||
|
return state.set(data.id, fromJS(data))
|
||||||
|
|
||||||
|
case Symbol.for('server: edit message'):
|
||||||
|
return state.setIn([data.id, 'message'], data.message)
|
||||||
|
|
||||||
case Symbol.for('add debug server'):
|
case Symbol.for('add debug server'):
|
||||||
return state.set('0', blankServer)
|
return state.set('0', blankServer)
|
||||||
|
|
||||||
|
|
16
UI/yarn.lock
16
UI/yarn.lock
|
@ -2517,7 +2517,7 @@ error-ex@^1.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-arrayish "^0.2.1"
|
is-arrayish "^0.2.1"
|
||||||
|
|
||||||
es-abstract@^1.7.0:
|
es-abstract@^1.4.3, es-abstract@^1.7.0:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864"
|
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -4573,6 +4573,12 @@ klaw@^1.0.0:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.9"
|
graceful-fs "^4.1.9"
|
||||||
|
|
||||||
|
ksuid@^0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ksuid/-/ksuid-0.4.0.tgz#e41f626b9f4dfcf2673f33a7ef69edc4fab99e1b"
|
||||||
|
dependencies:
|
||||||
|
string.prototype.padstart "^3.0.0"
|
||||||
|
|
||||||
latest-version@^2.0.0:
|
latest-version@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b"
|
resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b"
|
||||||
|
@ -7069,6 +7075,14 @@ string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1:
|
||||||
is-fullwidth-code-point "^2.0.0"
|
is-fullwidth-code-point "^2.0.0"
|
||||||
strip-ansi "^4.0.0"
|
strip-ansi "^4.0.0"
|
||||||
|
|
||||||
|
string.prototype.padstart@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/string.prototype.padstart/-/string.prototype.padstart-3.0.0.tgz#5bcfad39f4649bb2d031292e19bcf0b510d4b242"
|
||||||
|
dependencies:
|
||||||
|
define-properties "^1.1.2"
|
||||||
|
es-abstract "^1.4.3"
|
||||||
|
function-bind "^1.0.2"
|
||||||
|
|
||||||
string_decoder@^1.0.0, string_decoder@~1.0.3:
|
string_decoder@^1.0.0, string_decoder@~1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
|
||||||
|
|
Loading…
Add table
Reference in a new issue