v2: init -- UI is nuked from orbit, major app restructuring
|
@ -1,7 +1,3 @@
|
|||
Server/.env
|
||||
Server/public/
|
||||
.env
|
||||
.git/
|
||||
|
||||
*/node_modules/
|
||||
UI/node_modules/
|
||||
Server/node_modules/
|
||||
node_modules/
|
||||
|
|
30
.env.example
Normal file
|
@ -0,0 +1,30 @@
|
|||
# database url, we mostly use postgres. others may work, but who knows.
|
||||
# this is set up for the docker-compose.yml file included.
|
||||
DB_URL=postgres://roleypoly:19216801@localhost:5432/roleypoly
|
||||
|
||||
# debug=true will increase logging
|
||||
DEBUG=true
|
||||
|
||||
# development vs production changes how the UI does it's rendering.
|
||||
# do NOT leave this on development for prod. that's dumb.
|
||||
NODE_ENV=development
|
||||
|
||||
# discord settings.
|
||||
DISCORD_CLIENT_ID=36391647431320739
|
||||
DISCORD_CLIENT_SECRET=PZd3u4RkokhnB8MVdALPz5
|
||||
DISCORD_BOT_TOKEN=qgTk4wm9Q7ECmMCovpmJVNCBltzJhL
|
||||
|
||||
# URL to the app for OAuth callbacks
|
||||
APP_URL=http://localhost:6769
|
||||
|
||||
# CHANGE THIS ALWAYS. BUT EXACTLY ONCE.
|
||||
# CHANGE THIS ALWAYS. BUT EXACTLY ONCE.
|
||||
# CHANGE THIS ALWAYS. BUT EXACTLY ONCE.
|
||||
# signing key for sessions. changing this will invalidate all sessions.
|
||||
APP_KEY=PJoayPGqi8vfYVFYBDgSeJSDYUpzBX
|
||||
|
||||
# does this instance start a bot?
|
||||
IS_BOT=true
|
||||
|
||||
# your and your friend's user ID for using admin tools.
|
||||
ROOT_USERS=62601275618889721
|
9
.gitignore
vendored
|
@ -1,8 +1,9 @@
|
|||
|
||||
Server/\.env
|
||||
.env
|
||||
*.env
|
||||
|
||||
/docker-compose.test.yml
|
||||
Server/prod\.env
|
||||
|
||||
Server/test\.env
|
||||
UI~
|
||||
node_modules
|
||||
.vscode
|
||||
.data
|
||||
|
|
57
README.md
|
@ -1 +1,56 @@
|
|||
roleypoly
|
||||
# roleypoly
|
||||
|
||||
a discord bot & web ui for managing self-assignable roles.
|
||||
|
||||
**Most likely, you'll want to go here: https://rp.kat.cafe**. This app is already hosted, you don't need to deal with deploying it or anything, I've already done it for you.
|
||||
|
||||
If you're here to report a bug or develop on Roleypoly, the rest of this document is for you.
|
||||
|
||||
## developing/running your own
|
||||
|
||||
you'll need
|
||||
|
||||
- a discord app and bot token
|
||||
- a node environment (maybe?)
|
||||
- a docker environment
|
||||
- a hard hat because it's time to go building!
|
||||
|
||||
Check `.env.example` for all the various possible configuration values. Roleypoly is configured entirely over environment variables. In development, you might want to copy `.env.example` to `.env` so you don't need to set this up in your shell.
|
||||
|
||||
### for developers
|
||||
|
||||
```
|
||||
docker-compose up -d
|
||||
yarn
|
||||
yarn dev
|
||||
```
|
||||
|
||||
### for production
|
||||
|
||||
If you want an unedited latest version of roleypoly, it is available on the Docker Hub ([katie/roleypoly](https://hub.docker.com/r/katie/roleypoly)) for your using pleasure. An example docker-compose.yml is provided in `docker-compose.example.yml`, and all relevant environment variables (see `.env.example`) may be set there.
|
||||
|
||||
If you're not into Docker and/or want to deploy your own, simply run
|
||||
```
|
||||
yarn start
|
||||
```
|
||||
and you're off to the production races (sort of, you'll want to set up a `.env` file.)
|
||||
|
||||
The relevant `Dockerfile` is also included, so `docker build` is a useful way to deploy this too.
|
||||
|
||||
## scope & goal of project
|
||||
|
||||
I wanted to create a bot that let servers fully express theirselves through roles. the primary goal is clear in that regard, and it started with a (desktop-only) web experience. originally, a command-based bot wasn't on the menu, but i've likened up to the idea; but the single requirement is it *must* work in a fuzzy-match situation.
|
||||
|
||||
One of the biggest problems I set out to solve was the problem of emojis. Discord supports them and bots try to, but the fact of the matter is, not every bot treats emojis as first class citizens; and users can't really remember roles either. The problem is fine with 10 roles that are easy to remember and explain. This is impossible to manage with 250, Discord's cap on roles.
|
||||
|
||||
The primary goal, all-in-all, is to provide the single, best, end-all user experience for a bot that manages roles; until Discord gives us this theirselves.
|
||||
|
||||
— kayteh
|
||||
|
||||
## need help? wanna help us?
|
||||
|
||||
If you need any help, [please join our discord](https://discord.gg/m4GpWYY). That is the best way to contact the developers.
|
||||
|
||||
If your server needs something in particular to accomodate your server's requirements of user-assignable roles, please reach out to me over DMs on Discord, or email at [roleypoly@kat.cafe](mailto:roleypoly@kat.cafe)
|
||||
|
||||
If you'd like to give us incentive to continue developing and hosting this bot, please consider supporting it through [Patreon](https://patreon.com/kata), or via [PayPal](https://paypal.me/kayteh). All support is extremely appreciated, and not required for the use of the service.
|
||||
|
|
|
@ -2,6 +2,8 @@ const log = new (require('./logger'))('Roleypoly')
|
|||
const Sequelize = require('sequelize')
|
||||
const fetchModels = require('./models')
|
||||
const fetchApis = require('./api')
|
||||
const Next = require('next')
|
||||
|
||||
|
||||
class Roleypoly {
|
||||
constructor (router, io, app) {
|
||||
|
@ -18,6 +20,10 @@ class Roleypoly {
|
|||
|
||||
if (log.debugOn) log.warn('debug mode is on')
|
||||
|
||||
const dev = process.env.NODE_ENV !== 'production'
|
||||
this.ctx.ui = Next({ dev, dir: './ui' })
|
||||
this.ctx.uiHandler = this.ctx.ui.getRequestHandler()
|
||||
|
||||
this.__initialized = this._mountServices()
|
||||
}
|
||||
|
||||
|
@ -47,7 +53,16 @@ class Roleypoly {
|
|||
}
|
||||
|
||||
async mountRoutes () {
|
||||
await this.ctx.ui.prepare()
|
||||
|
||||
fetchApis(this.router, this.ctx)
|
||||
|
||||
// after routing, add the * for ui handler
|
||||
this.router.get('*', async ctx => {
|
||||
await this.ctx.uiHandler(ctx.req, ctx.res)
|
||||
ctx.respond = false
|
||||
})
|
||||
|
||||
this.__app.use(this.router.middleware())
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
DB_URL=postgres://roleypoly:19216801@localhost:5432/roleypoly
|
||||
DEBUG=true
|
||||
NODE_ENV=development
|
||||
DISCORD_CLIENT_ID=36391647431320739
|
||||
DISCORD_CLIENT_SECRET=PZd3u4RkokhnB8MVdALPz5
|
||||
DISCORD_BOT_TOKEN=qgTk4wm9Q7ECmMCovpmJVNCBltzJhL
|
||||
APP_URL=http://localhost:6769
|
||||
APP_KEY=PJoayPGqi8vfYVFYBDgSeJSDYUpzBX
|
||||
IS_BOT=true
|
||||
ROOT_USERS=62601275618889721
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
"extends": "standard"
|
||||
};
|
4
Server/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
|||
node_modules
|
||||
.data
|
||||
.env
|
||||
public
|
|
@ -1,38 +0,0 @@
|
|||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "standard && node index.js",
|
||||
"fix": "standard --fix",
|
||||
"dev": "pm2 start index.js --watch",
|
||||
"pm2": "pm2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/uws": "^11.149.1",
|
||||
"chalk": "^2.4.1",
|
||||
"discord.js": "^11.4.2",
|
||||
"dotenv": "^6.1.0",
|
||||
"erlpack": "github:discordapp/erlpack",
|
||||
"eslint": "^5.8.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"glob": "^7.1.3",
|
||||
"immutable": "^3.8.2",
|
||||
"koa": "^2.6.2",
|
||||
"koa-better-router": "^2.1.1",
|
||||
"koa-bodyparser": "^4.2.1",
|
||||
"koa-compress": "^3.0.0",
|
||||
"koa-send": "latest-2",
|
||||
"koa-session": "^5.10.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"ksuid": "^1.1.3",
|
||||
"lru-cache": "^4.1.3",
|
||||
"pg": "^7.6.1",
|
||||
"pg-hstore": "^2.3.2",
|
||||
"pm2": "^2.10.4",
|
||||
"sequelize": "^4.41.2",
|
||||
"socket.io": "^2.2.0",
|
||||
"superagent": "^4.0.0",
|
||||
"uuid": "^3.3.2"
|
||||
}
|
||||
}
|
3485
Server/yarn.lock
12
UI/.gitignore
vendored
|
@ -1,23 +1,19 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
/dist
|
||||
/.next
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
.env
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
|
44
UI/README.md
|
@ -1,44 +0,0 @@
|
|||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.<br>
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br>
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.<br>
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.<br>
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br>
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
44
UI/components/head.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import React from 'react'
|
||||
import NextHead from 'next/head'
|
||||
import { string } from 'prop-types'
|
||||
|
||||
const defaultDescription = ''
|
||||
const defaultOGURL = ''
|
||||
const defaultOGImage = ''
|
||||
|
||||
const Head = props => (
|
||||
<NextHead>
|
||||
<meta charSet="UTF-8" />
|
||||
<title>{props.title || ''}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={props.description || defaultDescription}
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" sizes="192x192" href="/static/touch-icon.png" />
|
||||
<link rel="apple-touch-icon" href="/static/touch-icon.png" />
|
||||
<link rel="mask-icon" href="/static/favicon-mask.svg" color="#49B882" />
|
||||
<link rel="icon" href="/static/favicon.ico" />
|
||||
<meta property="og:url" content={props.url || defaultOGURL} />
|
||||
<meta property="og:title" content={props.title || ''} />
|
||||
<meta
|
||||
property="og:description"
|
||||
content={props.description || defaultDescription}
|
||||
/>
|
||||
<meta name="twitter:site" content={props.url || defaultOGURL} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:image" content={props.ogImage || defaultOGImage} />
|
||||
<meta property="og:image" content={props.ogImage || defaultOGImage} />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
</NextHead>
|
||||
)
|
||||
|
||||
Head.propTypes = {
|
||||
title: string,
|
||||
description: string,
|
||||
url: string,
|
||||
ogImage: string
|
||||
}
|
||||
|
||||
export default Head
|
59
UI/components/nav.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
const links = [
|
||||
{ href: 'https://github.com/segmentio/create-next-app', label: 'Github' }
|
||||
].map(link => {
|
||||
link.key = `nav-link-${link.href}-${link.label}`
|
||||
return link
|
||||
})
|
||||
|
||||
const Nav = () => (
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<Link prefetch href="/">
|
||||
<a>Home</a>
|
||||
</Link>
|
||||
</li>
|
||||
<ul>
|
||||
{links.map(({ key, href, label }) => (
|
||||
<li key={key}>
|
||||
<Link href={href}>
|
||||
<a>{label}</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<style jsx>{`
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Avenir Next, Avenir,
|
||||
Helvetica, sans-serif;
|
||||
}
|
||||
nav {
|
||||
text-align: center;
|
||||
}
|
||||
ul {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
nav > ul {
|
||||
padding: 4px 16px;
|
||||
}
|
||||
li {
|
||||
display: flex;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
a {
|
||||
color: #067df7;
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
`}</style>
|
||||
</nav>
|
||||
)
|
||||
|
||||
export default Nav
|
|
@ -1,4 +0,0 @@
|
|||
const { override, addDecoratorsLegacy } = require('customize-cra')
|
||||
module.exports = override(
|
||||
addDecoratorsLegacy()
|
||||
)
|
|
@ -1,62 +0,0 @@
|
|||
{
|
||||
"name": "crav2",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"color": "^3.1.0",
|
||||
"history": "^4.7.2",
|
||||
"immutable": "^3.8.2",
|
||||
"moment": "^2.22.2",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.6.3",
|
||||
"react-custom-scrollbars": "^4.2.1",
|
||||
"react-dnd": "^7.0.0",
|
||||
"react-dnd-html5-backend": "^7.0.0",
|
||||
"react-dom": "^16.6.3",
|
||||
"react-immutable-proptypes": "^2.1.0",
|
||||
"react-redux": "^5.1.1",
|
||||
"react-router": "^4.3.1",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-router-redux": "^5.0.0-alpha.8",
|
||||
"react-scripts": "2.1.1",
|
||||
"react-typist": "^2.0.4",
|
||||
"react-typist-cycle": "^0.1.2",
|
||||
"redux": "^4.0.1",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"superagent": "^4.0.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-app-rewired start",
|
||||
"build": "react-app-rewired build",
|
||||
"test": "react-app-rewired test",
|
||||
"eject": "react-app-rewired eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
],
|
||||
"proxy": "http://localhost:6769",
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-decorators": "^7.1.2",
|
||||
"customize-cra": "^0.2.1",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-node": "^8.0.0",
|
||||
"eslint-plugin-promise": "^4.0.1",
|
||||
"eslint-plugin-react": "^7.11.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"node-sass-chokidar": "^1.3.4",
|
||||
"react-app-rewire-scss": "^1.0.2",
|
||||
"react-app-rewired": "^1.6.2",
|
||||
"redux-devtools": "^3.4.1",
|
||||
"redux-devtools-dock-monitor": "^1.1.3",
|
||||
"redux-devtools-log-monitor": "^1.4.0"
|
||||
}
|
||||
}
|
91
UI/pages/index.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import Head from '../components/head'
|
||||
import Nav from '../components/nav'
|
||||
|
||||
const Home = () => (
|
||||
<div>
|
||||
<Head title="Home" />
|
||||
<Nav />
|
||||
|
||||
<div className="hero">
|
||||
<h1 className="title">Welcome to Next!</h1>
|
||||
<p className="description">
|
||||
To get started, edit <code>pages/index.js</code> and save to reload.
|
||||
</p>
|
||||
|
||||
<div className="row">
|
||||
<Link href="https://github.com/zeit/next.js#getting-started">
|
||||
<a className="card">
|
||||
<h3>Getting Started →</h3>
|
||||
<p>Learn more about Next on Github and in their examples</p>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="https://open.segment.com/create-next-app">
|
||||
<a className="card">
|
||||
<h3>Examples →</h3>
|
||||
<p>
|
||||
Find other example boilerplates on the{' '}
|
||||
<code>create-next-app</code> site
|
||||
</p>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="https://github.com/segmentio/create-next-app">
|
||||
<a className="card">
|
||||
<h3>Create Next App →</h3>
|
||||
<p>Was this tool helpful? Let us know how we can improve it</p>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style jsx>{`
|
||||
.hero {
|
||||
width: 100%;
|
||||
color: #333;
|
||||
}
|
||||
.title {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
padding-top: 80px;
|
||||
line-height: 1.15;
|
||||
font-size: 48px;
|
||||
}
|
||||
.title,
|
||||
.description {
|
||||
text-align: center;
|
||||
}
|
||||
.row {
|
||||
max-width: 880px;
|
||||
margin: 80px auto 40px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.card {
|
||||
padding: 18px 18px 24px;
|
||||
width: 220px;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
color: #434343;
|
||||
border: 1px solid #9b9b9b;
|
||||
}
|
||||
.card:hover {
|
||||
border-color: #067df7;
|
||||
}
|
||||
.card h3 {
|
||||
margin: 0;
|
||||
color: #067df7;
|
||||
font-size: 18px;
|
||||
}
|
||||
.card p {
|
||||
margin: 0;
|
||||
padding: 12px 0 0;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Home
|
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 33 KiB |
|
@ -1,28 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/png" sizes="any" href="%PUBLIC_URL%/favicon.png"/>
|
||||
<title>Roleypoly</title>
|
||||
<!-- <script src="https://use.typekit.net/bck0pci.js"></script>
|
||||
<script>try{Typekit.load({ async: true });}catch(e){}</script> -->
|
||||
<link rel="stylesheet" defer href="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-rc.24/css/uikit.min.css" />
|
||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-rc.24/js/uikit.min.js"></script>
|
||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/uikit/3.0.0-rc.24/js/uikit-icons.min.js"></script>
|
||||
<style>body{ background-color: #453F3E; }</style>
|
||||
</head>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root" class=""></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"short_name": "roleypoly",
|
||||
"name": "roleypoly",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.png",
|
||||
"sizes": "128x128 64x64 32x32 24x24 16x16",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"theme_color": "#ab9b9a",
|
||||
"background_color": "#453e3d"
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #222;
|
||||
height: 150px;
|
||||
padding: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-title {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.App-intro {
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Provider } from 'react-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 './App.css'
|
||||
import './generic.sass'
|
||||
|
||||
import Wrapper from './components/wrapper'
|
||||
import AppRouter from './router'
|
||||
import { userInit } from './actions'
|
||||
|
||||
const history = createHistory()
|
||||
const store = configureStore(undefined, history)
|
||||
|
||||
window.__APP_STORE__ = store
|
||||
|
||||
@DragDropContext(HTML5Backend)
|
||||
class App extends Component {
|
||||
componentWillMount () {
|
||||
store.dispatch(userInit)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<Wrapper>
|
||||
<AppRouter />
|
||||
</Wrapper>
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default App
|
|
@ -1,8 +0,0 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
});
|
|
@ -1,87 +0,0 @@
|
|||
import superagent from 'superagent'
|
||||
import { push } from 'react-router-redux'
|
||||
|
||||
export const fetchServers = async dispatch => {
|
||||
const rsp = await superagent.get('/api/servers')
|
||||
|
||||
dispatch({
|
||||
type: Symbol.for('update servers'),
|
||||
data: rsp.body
|
||||
})
|
||||
|
||||
dispatch({
|
||||
type: Symbol.for('app ready')
|
||||
})
|
||||
}
|
||||
|
||||
export const userInit = async dispatch => {
|
||||
if (!window.location.pathname.startsWith('/oauth')) {
|
||||
try {
|
||||
const rsp = await superagent.get('/api/auth/user')
|
||||
|
||||
dispatch({
|
||||
type: Symbol.for('set user'),
|
||||
data: rsp.body
|
||||
})
|
||||
|
||||
dispatch(fetchServers)
|
||||
} catch (e) {
|
||||
dispatch({
|
||||
type: Symbol.for('app ready')
|
||||
})
|
||||
// window.location.href = '/oauth/flow'
|
||||
}
|
||||
} else {
|
||||
dispatch({
|
||||
type: Symbol.for('app ready')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const userLogout = async dispatch => {
|
||||
try {
|
||||
await superagent.post('/api/auth/logout')
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: Symbol.for('reset user')
|
||||
})
|
||||
|
||||
window.location.href = '/'
|
||||
}
|
||||
|
||||
export const startServerPolling = dispatch => {
|
||||
return poll(window.__APP_STORE__.dispatch, window.__APP_STORE__.getState) // let's not cheat... :c
|
||||
}
|
||||
|
||||
const poll = (dispatch, getState) => {
|
||||
const { servers } = getState()
|
||||
let stop = false
|
||||
const stopPolling = () => { stop = true }
|
||||
const pollFunc = async () => {
|
||||
if (stop) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await fetchServers(dispatch)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
setTimeout(pollFunc, 5000)
|
||||
}
|
||||
|
||||
const newServers = getState().servers
|
||||
if (servers.size >= newServers.size) {
|
||||
setTimeout(pollFunc, 5000)
|
||||
} else {
|
||||
const old = servers.keySeq().toSet()
|
||||
const upd = newServers.keySeq().toSet()
|
||||
const newSrv = upd.subtract(old)
|
||||
stopPolling()
|
||||
dispatch(push(`/s/${newSrv.toJS()[0]}/edit`))
|
||||
}
|
||||
}
|
||||
|
||||
pollFunc()
|
||||
return stopPolling
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="350px" height="350px" viewBox="0 0 350 350" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 44.1 (41455) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Slice</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M95.5351562,425.050781 C180.587008,425.050781 249.535156,356.102633 249.535156,271.050781 C249.535156,185.99893 172.535156,117.050781 95.5351562,117.050781 L95.5351562,425.050781 Z M95.5351563,387.050781 C159.600187,387.050781 211.535156,334.668097 211.535156,270.050781 C211.535156,205.433466 153.535156,153.050781 95.5351563,153.050781 C95.5351562,203.905895 95.5351563,337.260095 95.5351563,387.050781 Z" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="Logomark" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group" transform="translate(35.000000, -46.000000)">
|
||||
<text id="R" font-family="HelveticaNeue-Medium, Helvetica Neue" font-size="288" font-weight="400" fill="#000000">
|
||||
<tspan x="0" y="281">R</tspan>
|
||||
</text>
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<use id="Oval" fill="#000000" transform="translate(172.535156, 271.050781) rotate(45.000000) translate(-172.535156, -271.050781) " xlink:href="#path-1"></use>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.4 KiB |
|
@ -1,41 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import TypingDemo from '../demos/typing'
|
||||
import RoleypolyDemo from '../demos/roleypoly'
|
||||
import * as Actions from '../../actions'
|
||||
import './styles.sass'
|
||||
import discordLogo from '../../pages/images/discord-logo.svg'
|
||||
|
||||
export default class AddServer extends Component {
|
||||
polling = null
|
||||
|
||||
componentDidMount () {
|
||||
if (this.props.match.params.server !== undefined) {
|
||||
this.pollingStop = Actions.startServerPolling(this.props.dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.pollingStop != null) {
|
||||
this.pollingStop()
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return <div className="inner add-server">
|
||||
<h2>What is Roleypoly?</h2>
|
||||
<p className="add-server__header">
|
||||
Roleypoly is a helper bot to help server members assign themselves roles on Discord.
|
||||
</p>
|
||||
<div className="add-server__grid">
|
||||
<div><TypingDemo /></div>
|
||||
<div className="text">Could you easily remember 250 role names? You'd use images or bot commands to tell everyone what they can assign. This kinda limits how <i>many</i> roles you can reasonably have. And don't even start with emojis. <span alt="" role="img">💢</span></div>
|
||||
<div className="text right">Just click. <span role="img">🌈 💖</span></div>
|
||||
<div className="add-server__darkbg"><RoleypolyDemo /></div>
|
||||
</div>
|
||||
<div className="add-server__header">
|
||||
<a href="/oauth/bot/flow" target="_blank" className="uk-button rp-button discord"><img src={discordLogo} className="rp-button-logo" alt=""/> Authorize via Discord</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
.add-server
|
||||
text-align: center
|
||||
.paper
|
||||
background-color: hsla(0,0%,100%,0.05)
|
||||
padding: 30px
|
||||
|
||||
.text
|
||||
font-size: 0.9rem
|
||||
text-align: left
|
||||
|
||||
&.right
|
||||
text-align: right
|
||||
font-size: 1.1em
|
||||
|
||||
&__header
|
||||
margin: 45px 0
|
||||
|
||||
&__grid
|
||||
display: grid
|
||||
grid-template-columns: 1fr 1fr
|
||||
grid-template-rows: 1fr 1fr
|
||||
grid-gap: 10px
|
||||
|
||||
>div
|
||||
align-self: center
|
||||
|
||||
&__darkbg
|
||||
background-color: var(--c-1)
|
||||
padding: 20px
|
|
@ -1,12 +0,0 @@
|
|||
import React from 'react'
|
||||
import RoleDemo from '../role/demo'
|
||||
|
||||
const RoleypolyDemo = () => <div className="demo__roleypoly">
|
||||
<RoleDemo name="a cute role ♡" color="#3EC1BF" />
|
||||
<RoleDemo name="a vanity role ♡" color="#F7335B" />
|
||||
<RoleDemo name="a brave role ♡" color="#A8D0B8" />
|
||||
<RoleDemo name="a proud role ♡" color="#5C8B88" />
|
||||
<RoleDemo name="a wonderful role ♡" color="#D6B3C7" />
|
||||
</div>
|
||||
|
||||
export default RoleypolyDemo
|
|
@ -1,29 +0,0 @@
|
|||
import React from 'react'
|
||||
import moment from 'moment'
|
||||
import Typist from 'react-typist'
|
||||
import './typing.sass'
|
||||
|
||||
const Typing = () => <div className="demo__discord rp-discord">
|
||||
<div className="discord__chat">
|
||||
<span className="timestamp">{moment().format('LT')}</span>
|
||||
<span className="username">Kata カタ</span>
|
||||
<span className="text">Hey, I want some roles!</span>
|
||||
</div>
|
||||
<div className="discord__textarea">
|
||||
<Typist cursor={{ blink: true }}>
|
||||
<span>.iam a cute role ♡</span>
|
||||
<Typist.Backspace count={30} delay={1500} />
|
||||
<span>.iam a vanity role ♡</span>
|
||||
<Typist.Backspace count={30} delay={1500} />
|
||||
<span>.iam a brave role ♡</span>
|
||||
<Typist.Backspace count={30} delay={1500} />
|
||||
<span>.iam a proud role ♡</span>
|
||||
<Typist.Backspace count={30} delay={1500} />
|
||||
<span>.iam a wonderful role ♡</span>
|
||||
<Typist.Backspace count={30} delay={1500} />
|
||||
<span>i have too many roles.</span>
|
||||
</Typist>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
export default Typing
|
|
@ -1,48 +0,0 @@
|
|||
.demo__discord
|
||||
--not-quite-black: #23272A
|
||||
--dark-but-not-black: #2C2F33
|
||||
--greyple: #99AAB5
|
||||
--blurple: var(--c-discord)
|
||||
|
||||
background-color: var(--dark-but-not-black)
|
||||
padding: 10px
|
||||
text-align: left
|
||||
color: var(--c-white)
|
||||
|
||||
.Typist .Cursor
|
||||
display: inline-block
|
||||
color: transparent
|
||||
border-left: 1px solid var(--c-white)
|
||||
user-select: none
|
||||
|
||||
&--blinking
|
||||
opacity: 1
|
||||
animation: blink 2s ease-in-out infinite
|
||||
|
||||
@keyframes blink
|
||||
0%
|
||||
opacity: 1
|
||||
50%
|
||||
opacity: 0
|
||||
100%
|
||||
opacity: 1
|
||||
|
||||
.discord
|
||||
&__chat
|
||||
padding: 10px 0
|
||||
|
||||
span
|
||||
display: inline-block
|
||||
margin-left: 5px
|
||||
|
||||
.timestamp
|
||||
font-size: 0.7em
|
||||
color: hsla(0,0%,100%,.2)
|
||||
|
||||
.username
|
||||
font-weight: bold
|
||||
|
||||
&__textarea
|
||||
background-color: hsla(218,5%,47%,.3)
|
||||
border-radius: 5px
|
||||
padding: 10px
|
|
@ -1,11 +0,0 @@
|
|||
import React from 'react'
|
||||
import { createDevTools } from 'redux-devtools'
|
||||
import LogMonitor from 'redux-devtools-log-monitor'
|
||||
import DockMonitor from 'redux-devtools-dock-monitor'
|
||||
|
||||
export default createDevTools(
|
||||
<DockMonitor toggleVisibilityKey="`"
|
||||
changePositionKey="ctrl-w">
|
||||
<LogMonitor />
|
||||
</DockMonitor>
|
||||
)
|
|
@ -1,18 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1566px" height="298px" viewBox="0 0 1566 298" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 44.1 (41455) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Untitled</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M86.5351562,318.050781 C171.587008,318.050781 240.535156,249.102633 240.535156,164.050781 C240.535156,78.9989298 163.535156,10.0507812 86.5351562,10.0507812 L86.5351562,318.050781 Z M86.5351563,280.050781 C150.600187,280.050781 202.535156,227.668097 202.535156,163.050781 C202.535156,98.4334655 144.535156,46.0507812 86.5351563,46.0507812 C86.5351562,96.9058949 86.5351563,230.260095 86.5351563,280.050781 Z" id="path-1"></path>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<text id="Roleypoly" font-family="HelveticaNeue-Medium, Helvetica Neue" font-size="288" font-weight="400" fill="#AB9C9A">
|
||||
<tspan x="249" y="238">Roleypoly</tspan>
|
||||
</text>
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<use id="Oval" fill="#AB9C9A" transform="translate(163.535156, 164.050781) rotate(45.000000) translate(-163.535156, -164.050781) " xlink:href="#path-1"></use>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1,27 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Redirect } from 'react-router-dom'
|
||||
import superagent from 'superagent'
|
||||
import { connect } from 'react-redux'
|
||||
import { push } from 'react-router-redux'
|
||||
import { fetchServers } from '../../actions'
|
||||
|
||||
@connect()
|
||||
class OauthCallback extends Component {
|
||||
state = {
|
||||
notReady: true,
|
||||
message: 'chotto matte kudasai...',
|
||||
url: null
|
||||
}
|
||||
|
||||
async componentDidMount () {
|
||||
const { body: { url } } = await superagent.get('/api/oauth/bot?url=✔️')
|
||||
this.setState({ url, notReady: false })
|
||||
window.location.href = url
|
||||
}
|
||||
|
||||
render () {
|
||||
return (this.state.notReady) ? this.state.message : <a style={{zIndex: 10000}} href={this.state.url}>Something oopsed, click me to get to where you meant.</a>
|
||||
}
|
||||
}
|
||||
|
||||
export default OauthCallback
|
|
@ -1,83 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Redirect } from 'react-router-dom'
|
||||
import superagent from 'superagent'
|
||||
import { connect } from 'react-redux'
|
||||
import { fetchServers } from '../../actions'
|
||||
|
||||
@connect()
|
||||
class OauthCallback extends Component {
|
||||
state = {
|
||||
notReady: true,
|
||||
message: 'chotto matte kudasai...',
|
||||
redirect: '/s'
|
||||
}
|
||||
|
||||
stopped = false
|
||||
|
||||
componentDidUnmount () {
|
||||
this.stopped = true
|
||||
}
|
||||
|
||||
async componentDidMount () {
|
||||
// handle stuff in the url
|
||||
const sp = new URLSearchParams(this.props.location.search)
|
||||
const token = sp.get('code')
|
||||
|
||||
if (token === '' || token == null) {
|
||||
this.setState({ message: 'token missing, what are you trying to do?!' })
|
||||
return
|
||||
}
|
||||
|
||||
const stateToken = sp.get('state')
|
||||
const state = JSON.parse(window.sessionStorage.getItem('state') || 'null')
|
||||
|
||||
if (state !== null && state.state === stateToken && state.redirect != null) {
|
||||
this.setState({ redirect: state.redirect })
|
||||
}
|
||||
|
||||
this.props.history.replace(this.props.location.pathname)
|
||||
|
||||
let counter = 0
|
||||
const retry = async () => {
|
||||
if (this.stopped) return
|
||||
try {
|
||||
const rsp = await superagent.get('/api/auth/user')
|
||||
this.props.dispatch({
|
||||
type: Symbol.for('set user'),
|
||||
data: rsp.body
|
||||
})
|
||||
this.props.dispatch(fetchServers)
|
||||
this.setState({ notReady: false })
|
||||
} catch (e) {
|
||||
counter++
|
||||
if (counter > 10) {
|
||||
this.setState({ message: "i couldn't log you in. :c" })
|
||||
} else {
|
||||
setTimeout(() => { retry() }, 250)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pass token to backend, await it to finish it's business.
|
||||
try {
|
||||
await superagent.post('/api/auth/token').send({ token })
|
||||
// this.props.onLogin(rsp.body)
|
||||
|
||||
retry()
|
||||
|
||||
} catch (e) {
|
||||
console.error('token pass error', e)
|
||||
this.setState({ message: 'g-gomen nasai... i broke it...' })
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// update user stuff here
|
||||
}
|
||||
|
||||
render () {
|
||||
return (this.state.notReady) ? this.state.message : <Redirect to={this.state.redirect} />
|
||||
}
|
||||
}
|
||||
|
||||
export default OauthCallback
|
|
@ -1,73 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Redirect } from 'react-router-dom'
|
||||
import superagent from 'superagent'
|
||||
import { connect } from 'react-redux'
|
||||
import uuidv4 from 'uuid/v4'
|
||||
import { fetchServers } from '../../actions'
|
||||
|
||||
|
||||
@connect()
|
||||
class OauthCallback extends Component {
|
||||
state = {
|
||||
notReady: true,
|
||||
message: 'chotto matte kudasai...',
|
||||
redirect: '/s',
|
||||
url: null
|
||||
}
|
||||
|
||||
async fetchUser () {
|
||||
const rsp = await superagent.get('/api/auth/user')
|
||||
sessionStorage.setItem('user', JSON.stringify(rsp.body))
|
||||
sessionStorage.setItem('user.update', JSON.stringify(Date.now()))
|
||||
this.props.dispatch({
|
||||
type: Symbol.for('set user'),
|
||||
data: rsp.body
|
||||
})
|
||||
}
|
||||
|
||||
setupUser () {
|
||||
const userUpdateTime = sessionStorage.getItem('user.update') || 0
|
||||
if (+userUpdateTime + (1000 * 60 * 10) > Date.now()) {
|
||||
const user = sessionStorage.getItem('user')
|
||||
if (user != null && user !== '') {
|
||||
this.props.dispatch({
|
||||
type: Symbol.for('set user'),
|
||||
data: JSON.parse(user)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return this.fetchUser()
|
||||
}
|
||||
|
||||
async componentDidMount () {
|
||||
const state = uuidv4()
|
||||
|
||||
const oUrl = new URL(window.location.href)
|
||||
if (oUrl.searchParams.has('r')) {
|
||||
this.setState({ redirect: oUrl.searchParams.get('r') })
|
||||
}
|
||||
|
||||
window.sessionStorage.setItem('state', JSON.stringify({ state, redirect: oUrl.searchParams.get('r') }))
|
||||
|
||||
try {
|
||||
await this.setupUser()
|
||||
|
||||
this.props.dispatch(fetchServers)
|
||||
this.setState({ notReady: false })
|
||||
} catch (e) {
|
||||
const { body: { url } } = await superagent.get('/api/auth/redirect?url=✔️')
|
||||
const nUrl = new URL(url)
|
||||
|
||||
nUrl.searchParams.set('state', state)
|
||||
this.setState({ url: nUrl.toString() })
|
||||
window.location.href = nUrl.toString()
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (this.state.notReady) ? this.state.message : <><Redirect to={this.state.redirect} /><a style={{zIndex: 10000}} href={this.state.url}>Something oopsed, click me to get to where you meant.</a></>
|
||||
}
|
||||
}
|
||||
|
||||
export default OauthCallback
|
|
@ -1,44 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { DropTarget } from 'react-dnd'
|
||||
|
||||
import Role from '../role/draggable'
|
||||
import CategoryEditor from './CategoryEditor'
|
||||
|
||||
@DropTarget(Symbol.for('dnd: role'), {
|
||||
drop (props, monitor, element) {
|
||||
props.onDrop(monitor.getItem())
|
||||
},
|
||||
canDrop (props, monitor) {
|
||||
return (props.mode !== Symbol.for('edit') && monitor.getItem().category !== props.name)
|
||||
}
|
||||
}, (connect, monitor) => ({
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver(),
|
||||
isOverCurrent: monitor.isOver({ shallow: true }),
|
||||
canDrop: monitor.canDrop(),
|
||||
itemType: monitor.getItemType()
|
||||
}))
|
||||
class Category extends Component {
|
||||
render () {
|
||||
const { category, name, isOver, canDrop, connectDropTarget, mode, onEditOpen, ...rest } = this.props
|
||||
|
||||
if (mode === Symbol.for('edit')) {
|
||||
return <CategoryEditor category={category} name={name} {...rest} />
|
||||
}
|
||||
|
||||
return connectDropTarget(<div key={name} className={`role-editor__category drop-zone ${(canDrop) ? 'can-drop' : ''} ${(isOver && canDrop) ? 'is-over' : ''}`}>
|
||||
<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')
|
||||
.sortBy(r => r.get('position'))
|
||||
.reverse()
|
||||
.map((r, k) => <Role key={k} role={r} categoryId={name} />)
|
||||
.toArray()
|
||||
}
|
||||
</div>)
|
||||
}
|
||||
}
|
||||
export default Category
|
|
@ -1,64 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
|
||||
export default class CategoryEditor extends Component {
|
||||
|
||||
onKeyPress = (e) => {
|
||||
const { onSave } = this.props
|
||||
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
case 'Escape':
|
||||
return onSave()
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
category
|
||||
} = this.props
|
||||
|
||||
return <div className="role-editor__category editor" onKeyDown={this.onKeyPress}>
|
||||
<div 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={category.get('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 uk-light"
|
||||
checked={category.get('hidden')}
|
||||
onChange={this.props.onEdit('hidden', Symbol.for('edit: bool'))}
|
||||
/>
|
||||
Hidden
|
||||
</label>
|
||||
</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'>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
.role-editor
|
||||
&__grid
|
||||
display: grid
|
||||
grid-template-areas: 'left right'
|
||||
grid-template-columns: 1fr 1fr
|
||||
grid-template-rows: 1fr
|
||||
|
||||
&__actions
|
||||
display: flex
|
||||
margin: 10px 0
|
||||
|
||||
button
|
||||
padding: 0
|
||||
|
||||
&_delete
|
||||
flex: 1
|
||||
margin-right: 5px
|
||||
|
||||
&_save
|
||||
flex: 4
|
||||
|
||||
&__uncat-zone
|
||||
padding: 10px
|
||||
|
||||
&__alert
|
||||
background-color: var(--c-5)
|
||||
padding: 15px
|
||||
|
||||
a
|
||||
color: var(--c-9)
|
||||
text-decoration: none
|
||||
font-style: italic
|
||||
transition: color 0.15s ease-in-out
|
||||
|
||||
&:hover
|
||||
color: var(--c-white)
|
||||
|
||||
|
||||
|
||||
|
||||
.role-editor__category
|
||||
box-sizing: border-box
|
||||
background-color: var(--c-1)
|
||||
padding: 15px
|
||||
margin: 10px
|
||||
min-width: 220px - 30px
|
||||
position: relative
|
||||
|
||||
&.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)
|
||||
|
||||
&-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,146 +0,0 @@
|
|||
import { Set } from 'immutable'
|
||||
import * as UIActions from '../../actions/ui'
|
||||
import { getViewMap, setup } from '../role-picker/actions'
|
||||
import uuidv4 from 'uuid/v4'
|
||||
import superagent from 'superagent'
|
||||
|
||||
export const constructView = id => async (dispatch, getState) => {
|
||||
await setup(id)(dispatch)
|
||||
const server = getState().servers.get(id)
|
||||
|
||||
let { viewMap, hasSafeRoles } = getViewMap(server)
|
||||
viewMap = viewMap.map(c => c.set('mode', Symbol.for('drop')))
|
||||
|
||||
dispatch({
|
||||
type: Symbol.for('re: setup'),
|
||||
data: {
|
||||
hasSafeRoles,
|
||||
viewMap,
|
||||
originalSnapshot: viewMap
|
||||
}
|
||||
})
|
||||
|
||||
dispatch(UIActions.fadeIn)
|
||||
}
|
||||
|
||||
export const addRoleToCategory = (id, oldId, role, flip = true) => (dispatch) => {
|
||||
dispatch({
|
||||
type: Symbol.for('re: add role to category'),
|
||||
data: {
|
||||
id,
|
||||
role
|
||||
}
|
||||
})
|
||||
|
||||
if (flip) {
|
||||
dispatch(removeRoleFromCategory(oldId, id, role, false))
|
||||
}
|
||||
}
|
||||
|
||||
export const removeRoleFromCategory = (id, oldId, role, flip = true) => (dispatch) => {
|
||||
dispatch({
|
||||
type: Symbol.for('re: remove role from category'),
|
||||
data: {
|
||||
id,
|
||||
role
|
||||
}
|
||||
})
|
||||
|
||||
if (flip) {
|
||||
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 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 openEditor = (id) => ({
|
||||
type: Symbol.for('re: switch category mode'),
|
||||
data: {
|
||||
id,
|
||||
mode: Symbol.for('edit')
|
||||
}
|
||||
})
|
||||
|
||||
export const deleteCategory = (id, category) => (dispatch, getState) => {
|
||||
const roles = category.get('roles')
|
||||
const rolesMap = category.get('roles_map')
|
||||
|
||||
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 = (dispatch, getState) => {
|
||||
const { roleEditor } = getState()
|
||||
const vm = roleEditor.get('viewMap')
|
||||
|
||||
let name = 'New Category'
|
||||
let idx = 1
|
||||
while (vm.find(c => c.get('name') === name) !== undefined) {
|
||||
idx++
|
||||
name = `New Category ${idx}`
|
||||
}
|
||||
|
||||
const id = uuidv4()
|
||||
|
||||
dispatch({
|
||||
type: Symbol.for('re: set category'),
|
||||
data: {
|
||||
id,
|
||||
name,
|
||||
roles: Set([]),
|
||||
roles_map: Set([]),
|
||||
hidden: true,
|
||||
type: 'multi',
|
||||
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') })
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Set } from 'immutable'
|
||||
import { connect } from 'react-redux'
|
||||
import { DropTarget } from 'react-dnd'
|
||||
import { Link, Prompt, Redirect } from 'react-router-dom'
|
||||
import { Scrollbars } from 'react-custom-scrollbars'
|
||||
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 CategoryEditor from './CategoryEditor'
|
||||
import Role from '../role/draggable'
|
||||
|
||||
const mapState = ({ rolePicker, roleEditor, servers }, ownProps) => ({
|
||||
rp: rolePicker,
|
||||
editor: roleEditor,
|
||||
server: servers.get(ownProps.match.params.server)
|
||||
})
|
||||
|
||||
@connect(mapState)
|
||||
@DropTarget(Symbol.for('dnd: role'), {
|
||||
drop (props, monitor, element) {
|
||||
element.dropRole({}, 'Uncategorized')(monitor.getItem())
|
||||
},
|
||||
canDrop (props, monitor) {
|
||||
return (monitor.getItem().category !== 'Uncategorized')
|
||||
}
|
||||
}, (connect, monitor) => ({
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver(),
|
||||
isOverCurrent: monitor.isOver({ shallow: true }),
|
||||
canDrop: monitor.canDrop(),
|
||||
itemType: monitor.getItemType()
|
||||
}))
|
||||
class RoleEditor extends Component {
|
||||
componentWillMount () {
|
||||
const { dispatch, match: { params: { server } } } = this.props
|
||||
dispatch(Actions.constructView(server))
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.props.match.params.server !== nextProps.match.params.server) {
|
||||
const { dispatch } = this.props
|
||||
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, category))
|
||||
}
|
||||
|
||||
deleteCategory = (category, id) => () => {
|
||||
const { dispatch } = this.props
|
||||
dispatch(Actions.deleteCategory(id, category))
|
||||
}
|
||||
|
||||
openEditor = (category, name) => () => {
|
||||
const { dispatch } = this.props
|
||||
dispatch(Actions.openEditor(name))
|
||||
}
|
||||
|
||||
editCategory = (category, id) => (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
|
||||
|
||||
case Symbol.for('edit: select'):
|
||||
value = event.target.value
|
||||
break
|
||||
|
||||
default:
|
||||
value = null
|
||||
}
|
||||
|
||||
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 () {
|
||||
const { server } = this.props
|
||||
|
||||
if (server == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (server.getIn(['perms', 'canManageRoles']) !== true) {
|
||||
return <Redirect to={`/s/${server.get('id')}`} />
|
||||
}
|
||||
|
||||
const vm = this.props.editor.get('viewMap')
|
||||
return <div className="inner role-editor">
|
||||
<Prompt when={this.hasChanged} message="Are you sure you want to leave? You have unsaved changes that will be lost." />
|
||||
<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__left">
|
||||
<Scrollbars autoHeight autoHeightMax='calc(100vh - 110px)'>
|
||||
{
|
||||
vm
|
||||
.filter((_, k) => k !== 'Uncategorized')
|
||||
.map((c, name) => <Category
|
||||
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>
|
||||
</div>
|
||||
{
|
||||
this.props.connectDropTarget(
|
||||
<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)'>
|
||||
<div className="role-editor__uncat-zone">
|
||||
{
|
||||
(vm.getIn(['Uncategorized', 'roles_map']) || Set())
|
||||
.sortBy(r => r.get('position'))
|
||||
.reverse()
|
||||
.map((r, k) => <Role key={k} categoryId='Uncategorized' role={r} />)
|
||||
.toArray()
|
||||
}
|
||||
{
|
||||
(this.props.editor.get('hasSafeRoles') !== true)
|
||||
? <div className="role-editor__alert">
|
||||
<Link to="/help/why-no-roles">Why are there no roles here? <i uk-icon="icon: info" /></Link>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</Scrollbars>
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default RoleEditor
|
|
@ -1,53 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
import Role from '../role'
|
||||
|
||||
class Category extends Component {
|
||||
|
||||
toggleRoleMulti (id, next) {
|
||||
this.props.onChange(Map({ [id]: next }))
|
||||
}
|
||||
|
||||
toggleRoleSingle (id, next) {
|
||||
this.props.onChange(this.props.category.get('roles').reduce((acc, i) => acc.set(i, false), Map()).set(id, next))
|
||||
}
|
||||
|
||||
onRoleToggle = id => (next, old) => {
|
||||
const type = this.props.category.get('type')
|
||||
|
||||
switch (type) {
|
||||
case 'single': return this.toggleRoleSingle(id, next)
|
||||
case 'multi': return this.toggleRoleMulti(id, next)
|
||||
default:
|
||||
console.warn('DEFAULTING TO MULTI', id, next, old)
|
||||
return this.toggleRoleMulti(id, next)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { category, name, isSelected } = this.props
|
||||
if (category.get('hidden')) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (category.get('roles').count() === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div key={name} className="role-picker__category">
|
||||
<h4>{ category.get('name') }</h4>
|
||||
{
|
||||
category.get('roles_map')
|
||||
.sortBy(r => r.get('position'))
|
||||
.reverse()
|
||||
.map((r, k) => {
|
||||
const id = r.get('id')
|
||||
return <Role key={k} role={r} disabled={!r.get('safe')} selected={isSelected(id)} onToggle={this.onRoleToggle(id)}/>
|
||||
})
|
||||
.toArray()
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
export default Category
|
|
@ -1,63 +0,0 @@
|
|||
.role-picker
|
||||
transition: opacity 0.3s ease-in-out
|
||||
opacity: 1
|
||||
|
||||
&.hidden
|
||||
opacity: 0
|
||||
|
||||
&__categories
|
||||
display: flex
|
||||
align-items: flex-start
|
||||
flex-wrap: wrap
|
||||
flex-direction: row
|
||||
|
||||
&__category
|
||||
// flex: 1 3 33%
|
||||
box-sizing: border-box
|
||||
background-color: var(--c-1)
|
||||
padding: 15px
|
||||
margin: 10px
|
||||
min-width: 220px - 30px
|
||||
|
||||
&__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
|
||||
|
||||
&__actions
|
||||
opacity: 1
|
||||
transition: opacity 0.3s ease-in-out
|
||||
|
||||
button
|
||||
margin-left: 5px
|
||||
|
||||
&.hidden
|
||||
opacity: 0
|
||||
// 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)
|
|
@ -1,146 +0,0 @@
|
|||
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}`)
|
||||
const data = rsp.body
|
||||
|
||||
dispatch({
|
||||
type: Symbol.for('server: set'),
|
||||
data: {
|
||||
id,
|
||||
...data
|
||||
}
|
||||
})
|
||||
dispatch(constructView(id))
|
||||
}
|
||||
|
||||
export const getViewMap = server => {
|
||||
const roles = server.get('roles')
|
||||
const categories = server.get('categories')
|
||||
|
||||
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)
|
||||
|
||||
// console.log('roles', allRoles.toJS(), accountedRoles.toJS(), unaccountedRoles.toJS())
|
||||
|
||||
const viewMap = categories.set('Uncategorized', fromJS({
|
||||
roles: unaccountedRoles,
|
||||
hidden: true,
|
||||
type: 'multi',
|
||||
name: 'Uncategorized'
|
||||
})).map(c => {
|
||||
const roles = c.get('roles')
|
||||
// fill in roles_map
|
||||
.map(r =>
|
||||
server.get('roles').find(sr => sr.get('id') === r)
|
||||
)
|
||||
.filter(r => r != null)
|
||||
// sort by server position, backwards.
|
||||
.sort((a, b) => a.position > b.position)
|
||||
// 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 hasSafeRoles = allRoles.size > 0
|
||||
|
||||
return {
|
||||
viewMap,
|
||||
selected,
|
||||
hasSafeRoles
|
||||
}
|
||||
}
|
||||
|
||||
export const constructView = id => (dispatch, getState) => {
|
||||
const server = getState().servers.get(id)
|
||||
|
||||
const { viewMap, selected } = getViewMap(server)
|
||||
|
||||
dispatch({
|
||||
type: Symbol.for('rp: setup role picker'),
|
||||
data: {
|
||||
viewMap: viewMap,
|
||||
rolesSelected: selected,
|
||||
originalRolesSelected: selected,
|
||||
hidden: false,
|
||||
isEditingMessage: false,
|
||||
messageBuffer: ''
|
||||
}
|
||||
})
|
||||
|
||||
dispatch(UIActions.fadeIn)
|
||||
}
|
||||
|
||||
export const resetSelected = (dispatch) => {
|
||||
dispatch({
|
||||
type: Symbol.for('rp: reset selected')
|
||||
})
|
||||
}
|
||||
|
||||
export const submitSelected = serverId => async (dispatch, getState) => {
|
||||
const { rolePicker } = getState()
|
||||
const original = rolePicker.get('originalRolesSelected')
|
||||
const current = rolePicker.get('rolesSelected')
|
||||
|
||||
const diff = original.reduce((acc, v, k) => {
|
||||
if (current.get(k) !== v) {
|
||||
// if original value is false, then we know we're adding, otherwise removing.
|
||||
if (v !== true) {
|
||||
return acc.set('added', acc.get('added').add(k))
|
||||
} else {
|
||||
return acc.set('removed', acc.get('removed').add(k))
|
||||
}
|
||||
}
|
||||
|
||||
return acc
|
||||
}, Map({ added: Set(), removed: Set() }))
|
||||
|
||||
await superagent.patch(`/api/servers/${serverId}/roles`).send(diff.toJS())
|
||||
|
||||
dispatch({
|
||||
type: Symbol.for('rp: sync selected roles')
|
||||
})
|
||||
}
|
||||
|
||||
export const updateRoles = roles => ({
|
||||
type: Symbol.for('rp: update selected roles'),
|
||||
data: roles
|
||||
})
|
||||
|
||||
export const openMessageEditor = id => (dispatch, getState) => {
|
||||
const message = getState().servers.getIn([id, 'message'])
|
||||
dispatch(editServerMessage(id, message))
|
||||
dispatch({
|
||||
type: Symbol.for('rp: set message editor state'),
|
||||
data: true
|
||||
})
|
||||
}
|
||||
|
||||
export const saveServerMessage = id => async (dispatch, getState) => {
|
||||
const message = getState().rolePicker.get('messageBuffer')
|
||||
|
||||
await superagent.patch(`/api/server/${id}`).send({ message })
|
||||
|
||||
dispatch(closeMessageEditor)
|
||||
dispatch({
|
||||
type: Symbol.for('server: edit message'),
|
||||
data: {
|
||||
id,
|
||||
message
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const editServerMessage = (id, message) => ({
|
||||
type: Symbol.for('rp: edit message buffer'),
|
||||
data: message
|
||||
})
|
||||
|
||||
export const closeMessageEditor = ({
|
||||
type: Symbol.for('rp: set message editor state'),
|
||||
data: false
|
||||
})
|
|
@ -1,145 +0,0 @@
|
|||
import React, { Component, Fragment } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Prompt } from 'react-router-dom'
|
||||
import superagent from 'superagent'
|
||||
import * as Actions from './actions'
|
||||
import * as UIActions from '../../actions/ui'
|
||||
import { msgToReal } from '../../utils'
|
||||
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 {
|
||||
data: rolePicker,
|
||||
server: servers.get(ownProps.match.params.server)
|
||||
}
|
||||
}
|
||||
|
||||
@connect(mapState)
|
||||
class RolePicker extends Component {
|
||||
componentWillMount () {
|
||||
const { dispatch, match: { params: { server } } } = this.props
|
||||
dispatch(Actions.setup(server))
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (this.props.match.params.server !== nextProps.match.params.server) {
|
||||
const { dispatch } = this.props
|
||||
dispatch(UIActions.fadeOut(() => dispatch(Actions.setup(nextProps.match.params.server))))
|
||||
}
|
||||
}
|
||||
|
||||
get serverId () {
|
||||
return this.props.server.get('id')
|
||||
}
|
||||
|
||||
isSelected = id => {
|
||||
return this.props.data.getIn([ 'rolesSelected', id ])
|
||||
}
|
||||
|
||||
get rolesHaveChanged () {
|
||||
const { data } = this.props
|
||||
return !data.get('rolesSelected').equals(data.get('originalRolesSelected'))
|
||||
}
|
||||
|
||||
editServerMessage = (e) => {
|
||||
const { dispatch } = this.props
|
||||
dispatch(Actions.editServerMessage(this.serverId, e.target.value))
|
||||
}
|
||||
|
||||
saveServerMessage = (e) => {
|
||||
const { dispatch } = this.props
|
||||
dispatch(Actions.saveServerMessage(this.serverId))
|
||||
}
|
||||
|
||||
openMessageEditor = () => {
|
||||
const { dispatch } = this.props
|
||||
dispatch(Actions.openMessageEditor(this.serverId))
|
||||
}
|
||||
|
||||
closeMessageEditor = () => {
|
||||
const { dispatch } = this.props
|
||||
dispatch(Actions.closeMessageEditor)
|
||||
}
|
||||
|
||||
renderServerMessage (server) {
|
||||
const isEditing = this.props.data.get('isEditingMessage')
|
||||
const roleManager = server.getIn(['perms', 'canManageRoles'])
|
||||
const msg = server.get('message')
|
||||
const msgBuffer = this.props.data.get('messageBuffer')
|
||||
|
||||
console.log(msg, roleManager, isEditing, this.props.data.toJS())
|
||||
|
||||
if (!roleManager && msg !== '') {
|
||||
return <section>
|
||||
<h3>Server Message</h3>
|
||||
<p>{msgToReal(msg)}</p>
|
||||
</section>
|
||||
}
|
||||
|
||||
if (roleManager && !isEditing) {
|
||||
return <section>
|
||||
<div className="role-picker__header">
|
||||
<h3>Server Message</h3>
|
||||
<div uk-tooltip='' title='Edit Server Message' uk-icon="icon: pencil" onClick={this.openMessageEditor} />
|
||||
</div>
|
||||
<p dangerouslySetInnerHTML={{__html: msgToReal(msg) || '<i>no server message</i>'}}></p>
|
||||
</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={{cursor: 'pointer', color: 'var(--c-green)'}} uk-icon="icon: check; ratio: 1.4" />
|
||||
<div uk-tooltip='' title='Discard Edits' onClick={this.closeMessageEditor} style={{cursor: 'pointer', color: 'var(--c-red)', marginLeft: 10}} uk-icon="icon: trash; ratio: 0.9" />
|
||||
</div>
|
||||
<textarea className="uk-width-1-2 uk-textarea role-picker__msg-editor" rows="3" onChange={this.editServerMessage} value={msgBuffer} />
|
||||
</section>
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
render () {
|
||||
const { data, server, dispatch } = this.props
|
||||
const vm = data.get('viewMap')
|
||||
|
||||
if (server === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div className={`inner role-picker ${(data.get('hidden')) ? 'hidden' : ''}`}>
|
||||
<Prompt when={this.rolesHaveChanged} message="Are you sure you want to leave? You have unsaved changes that will be lost." />
|
||||
{ this.renderServerMessage(server) }
|
||||
<section>
|
||||
<div className="role-picker__header">
|
||||
<h3>Roles</h3>
|
||||
{ server.getIn(['perms', 'canManageRoles']) === true
|
||||
? <Link to={`/s/${server.get('id')}/edit`} uk-tooltip='' title='Edit Categories' uk-icon="icon: file-edit"></Link>
|
||||
: null
|
||||
}
|
||||
<div className="role-picker__spacer"></div>
|
||||
<div className={`role-picker__actions ${(!this.rolesHaveChanged) ? 'hidden' : ''}`}>
|
||||
<button disabled={!this.rolesHaveChanged} onClick={() => dispatch(Actions.resetSelected)} className="uk-button rp-button secondary">
|
||||
Reset
|
||||
</button>
|
||||
<button disabled={!this.rolesHaveChanged} onClick={() => dispatch(Actions.submitSelected(this.props.match.params.server))} className="uk-button rp-button primary">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="role-picker__categories">
|
||||
{
|
||||
vm.map((c, name) => <Category key={name} name={name} category={c} isSelected={this.isSelected} onChange={(roles) => dispatch(Actions.updateRoles(roles))} />).toArray()
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default RolePicker
|
|
@ -1,104 +0,0 @@
|
|||
.role
|
||||
border: 1px solid var(--role-color-rgba)
|
||||
border-radius: 32px
|
||||
box-sizing: border-box
|
||||
height: 32px
|
||||
padding: 4px
|
||||
display: inline-flex
|
||||
font-weight: 600
|
||||
align-content: center
|
||||
justify-content: center
|
||||
font-size: 16px
|
||||
vertical-align: baseline
|
||||
transition: background-color 0.15s ease-in-out, transform 0.15s ease-in-out, box-shadow 0.15s ease-in-out
|
||||
transform: translateZ(0)
|
||||
margin: 0 6px 6px 0
|
||||
position: relative
|
||||
|
||||
&.role__button
|
||||
|
||||
.role__option
|
||||
&:hover
|
||||
cursor: inherit
|
||||
|
||||
&:hover:not(.disabled)
|
||||
cursor: pointer
|
||||
.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
|
||||
|
||||
&.disabled
|
||||
.role__option
|
||||
border-color: var(--c-7)
|
||||
transition: none
|
||||
transform: translateZ(0)
|
||||
&:hover
|
||||
cursor: default
|
||||
|
||||
|
||||
&__name
|
||||
margin-right: 6px
|
||||
max-width: 200px
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
user-select: none
|
||||
|
||||
&__option
|
||||
border-radius: 50%
|
||||
height: 22px
|
||||
margin-right: 6px
|
||||
width: 22px
|
||||
clip-path: border-box circle(50% at 50% 50%) // fix for firefox and other odd things.
|
||||
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, &.is-dragging
|
||||
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
|
||||
|
||||
&::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)
|
||||
transform: translateY(0) translateZ(0px) !important
|
|
@ -1,19 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
import Role from './index'
|
||||
|
||||
export default class DemoRole extends Component {
|
||||
state = {
|
||||
isSelected: false
|
||||
}
|
||||
|
||||
handleToggle = () => {
|
||||
this.setState({ isSelected: !this.state.isSelected })
|
||||
}
|
||||
|
||||
|
||||
render () {
|
||||
return <Role selected={this.state.isSelected} role={Map({ name: this.props.name, color: this.props.color })} onToggle={this.handleToggle} type='button' />
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
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
|
||||
@DragSource(
|
||||
Symbol.for('dnd: role'),
|
||||
{
|
||||
beginDrag ({ role, categoryId }) {
|
||||
return { role, category: categoryId }
|
||||
}
|
||||
},
|
||||
(connect, monitor) => ({
|
||||
connectDragSource: connect.dragSource(),
|
||||
isDragging: monitor.isDragging()
|
||||
})
|
||||
)
|
||||
class DraggableRole extends Component {
|
||||
render () {
|
||||
return <Role {...this.props} type='drag' />
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Color from 'color'
|
||||
import './Role.sass'
|
||||
|
||||
const whiteColor = Color('#efefef')
|
||||
|
||||
class Role extends Component {
|
||||
static propTypes = {
|
||||
role: PropTypes.object.isRequired,
|
||||
onToggle: PropTypes.func,
|
||||
type: PropTypes.string,
|
||||
selected: PropTypes.bool,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
render () {
|
||||
let { role, selected, disabled, type, isDragging } = this.props
|
||||
type = type || 'button'
|
||||
|
||||
// console.log(this.props)
|
||||
|
||||
let color = Color(role.get('color'))
|
||||
|
||||
if (color.rgbNumber() === 0) {
|
||||
color = whiteColor
|
||||
}
|
||||
|
||||
const c = color
|
||||
let hc = color.lighten(0.1)
|
||||
|
||||
const out = <div
|
||||
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' : ''} ${(isDragging) ? 'is-dragging' : ''} role__${type}`}
|
||||
title={role.get('name')}
|
||||
style={{
|
||||
'--role-color-hex': c.string(),
|
||||
'--role-color-hover': hc.string(),
|
||||
'--role-color-rgba': `rgba(${c.red()}, ${c.green()}, ${c.blue()}, 0.7)`
|
||||
}}>
|
||||
<div className={`role__option ${(selected) ? 'selected' : ''}`}/>
|
||||
<div className='role__name'>
|
||||
{role.get('name')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if (type === 'drag' && this.props.connectDragSource != null) {
|
||||
return this.props.connectDragSource(out)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
export default Role
|
|
@ -1,40 +0,0 @@
|
|||
import React, { Component, Fragment } from 'react'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import PropTypes from 'prop-types'
|
||||
import ServerCard from './ServerCard'
|
||||
import UserCard from './UserCard'
|
||||
import { Scrollbars } from 'react-custom-scrollbars'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
|
||||
class ServersNavigation extends Component {
|
||||
static propTypes = {
|
||||
user: ImmutablePropTypes.map.isRequired,
|
||||
servers: ImmutablePropTypes.orderedMapOf(ImmutablePropTypes.map).isRequired,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
render () {
|
||||
// console.log(this.props.servers)
|
||||
return <Fragment>
|
||||
<UserCard user={this.props.user} />
|
||||
<div className={this.props.className}>
|
||||
<Scrollbars autoHeight autoHeightMax='calc(100vh - 180px)'>
|
||||
{
|
||||
this.props.servers.reduce((acc, s, i) => {
|
||||
acc.push(<ServerCard server={s} user={this.props.user} key={i} />)
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
<NavLink className='server-list__item add-new' activeClassName='active' to={`/s/add`}>
|
||||
<div className='server-list__item__info'>
|
||||
<i uk-icon="icon: plus; ratio: 0.9"></i>
|
||||
Add to your server
|
||||
</div>
|
||||
</NavLink>
|
||||
</Scrollbars>
|
||||
</div>
|
||||
</Fragment>
|
||||
}
|
||||
}
|
||||
|
||||
export default ServersNavigation
|
|
@ -1,45 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import './ServerCard.sass'
|
||||
import { withRouter } from 'react-router';
|
||||
|
||||
class ServerCard extends Component {
|
||||
static propTypes = {
|
||||
user: ImmutablePropTypes.map.isRequired,
|
||||
server: ImmutablePropTypes.map.isRequired,
|
||||
}
|
||||
|
||||
render () {
|
||||
const { server, user } = this.props
|
||||
|
||||
let icon = ''
|
||||
|
||||
console.log(__filename, server)
|
||||
|
||||
const s = server.get('server')
|
||||
const gm = server.get('gm')
|
||||
const perms = server.get('perms')
|
||||
|
||||
if (perms.get('canManageRoles')) {
|
||||
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')) {
|
||||
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')}`}>
|
||||
<div className='server-list__item__icon'>
|
||||
<img src={`https://cdn.discordapp.com/icons/${s.get('id')}/${s.get('icon')}.png`} alt={s.name} />
|
||||
</div>
|
||||
<div className='server-list__item__info'>
|
||||
<b>{s.get('name')}</b><br />
|
||||
<span style={{ color: gm.get('color') }}>{ gm.get('nickname') || user.get('username') }</span> { icon }
|
||||
</div>
|
||||
</NavLink>
|
||||
}
|
||||
}
|
||||
|
||||
export default ServerCard
|
|
@ -1,50 +0,0 @@
|
|||
.server-list
|
||||
&__item
|
||||
display: flex
|
||||
/* border-bottom: 1px solid var(--c-3) */
|
||||
padding: 25px 15px
|
||||
padding-right: 0
|
||||
align-items: center
|
||||
/* justify-content: center */
|
||||
|
||||
|
||||
&.add-new
|
||||
color: var(--c-7)
|
||||
|
||||
&.active
|
||||
background-color: var(--c-3)
|
||||
|
||||
&:not(.active)
|
||||
&:hover
|
||||
background-color: rgba(0,0,0,0.25) !important
|
||||
cursor: pointer
|
||||
transform: translateX(1px)
|
||||
transition: background 0.05s ease-in-out, transform 0.1s ease-in-out
|
||||
|
||||
&:nth-of-type(even)
|
||||
background-color: rgba(0,0,0,0.1)
|
||||
|
||||
&:last-of-type
|
||||
border: 0
|
||||
|
||||
&__icon img
|
||||
border-radius: 100%
|
||||
max-width: 50px
|
||||
max-height: 50px
|
||||
border: 2px solid transparent
|
||||
|
||||
&__info
|
||||
padding: 0 10px
|
||||
|
||||
&__tag
|
||||
color: var(--c-9)
|
||||
position: relative
|
||||
top: -2px
|
||||
// position: inline-block
|
||||
|
||||
a.server-list__item
|
||||
color: var(--c-white)
|
||||
text-decoration: none
|
||||
|
||||
.active
|
||||
cursor: default
|
|
@ -1,47 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Link, Redirect } from 'react-router-dom'
|
||||
import superagent from 'superagent'
|
||||
import discordLogo from '../../pages/images/discord-logo.svg'
|
||||
|
||||
export default class ServerLanding extends Component {
|
||||
state = {
|
||||
server: null,
|
||||
exit: false
|
||||
}
|
||||
|
||||
async componentWillMount () {
|
||||
console.log(this.props)
|
||||
|
||||
try {
|
||||
const rsp = await superagent.get(`/api/server/${this.props.match.params.server}/slug`)
|
||||
this.setState({ server: rsp.body })
|
||||
} catch (e) {
|
||||
this.setState({ exit: true })
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render () {
|
||||
if (this.state.exit === true) {
|
||||
return <Redirect to="/" />
|
||||
}
|
||||
|
||||
if (this.state.server === null) {
|
||||
return null //SPINNER
|
||||
}
|
||||
|
||||
return <div className="landing uk-width-1-1 uk-text-center">
|
||||
<div className="uk-container">
|
||||
<section>
|
||||
<h1>Hey there.</h1>
|
||||
<h4>{this.state.server.name} uses Roleypoly to manage self-assignable roles.</h4>
|
||||
<h5><span role="img">💖</span></h5>
|
||||
</section>
|
||||
<section>
|
||||
<Link to={`/oauth/flow?r=${window.location.pathname}`} className="uk-button rp-button discord"><img src={discordLogo} className="rp-button-logo"/> Sign in with Discord</Link>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { connect } from 'react-redux'
|
||||
import * as Actions from '../../actions'
|
||||
import './UserCard.sass'
|
||||
|
||||
@connect()
|
||||
class UserCard extends Component {
|
||||
static propTypes = {
|
||||
user: ImmutablePropTypes.map
|
||||
}
|
||||
|
||||
get avatar () {
|
||||
const { user } = this.props
|
||||
const avatar = user.get('avatar')
|
||||
|
||||
if (avatar === '' || avatar == null) {
|
||||
return `https://cdn.discordapp.com/embed/avatars/${Math.ceil(Math.random() * 9999) % 5}.png`
|
||||
}
|
||||
|
||||
return `https://cdn.discordapp.com/avatars/${user.get('id')}/${avatar}.png`
|
||||
}
|
||||
|
||||
render () {
|
||||
const { user } = this.props
|
||||
|
||||
// console.log(this.props)
|
||||
|
||||
return <div className='user-card'>
|
||||
<div className='user-card__icon'>
|
||||
<img src={this.avatar} alt={user.get('username')} />
|
||||
</div>
|
||||
<div className='user-card__info'>
|
||||
<span className='user-card__info__name'>{user.get('username')}</span><span className='user-card__info__discrim'>#{user.get('discriminator')}</span>
|
||||
</div>
|
||||
<div className='user-card__actions'>
|
||||
<ul className='uk-iconnav uk-iconnav-vertical'>
|
||||
<li><a uk-tooltip='' title='Sign out' uk-icon='icon: sign-out' onClick={() => { this.props.dispatch(Actions.userLogout) }} /></li>
|
||||
{
|
||||
(this.props.user.isRoot === true)
|
||||
? <li><NavLink uk-tooltip='' title='Root' uk-icon='icon: bolt' to='/root/' activeClassName='uk-active' /></li>
|
||||
: null
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default UserCard
|
|
@ -1,44 +0,0 @@
|
|||
.user-card
|
||||
position: relative
|
||||
display: flex
|
||||
background-color: var(--c-dark)
|
||||
/* border-bottom: 1px solid var(--c-3) */
|
||||
padding: 25px 15px
|
||||
grid-area: user
|
||||
|
||||
&__icon img
|
||||
width: 50px
|
||||
height: 50px
|
||||
border-radius: 100%
|
||||
border: 2px solid #5fc66d
|
||||
|
||||
&__info
|
||||
padding: 0 10px
|
||||
line-height: 50px
|
||||
|
||||
&__discrim
|
||||
font-weight: 100
|
||||
font-size: 0.7rem
|
||||
color: var(--c-7)
|
||||
|
||||
&__name
|
||||
/* font-size */
|
||||
/* font-weight: */
|
||||
|
||||
&__actions
|
||||
position: absolute
|
||||
right: 5px
|
||||
top: 5px
|
||||
bottom: 5px
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
svg
|
||||
transition: transform 0.05s ease-in-out
|
||||
|
||||
svg:hover
|
||||
transform: scale(1.15)
|
||||
|
||||
polygon
|
||||
fill: var(--c-7)
|
|
@ -1,55 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
import { Scrollbars } from 'react-custom-scrollbars'
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter, Redirect } from 'react-router'
|
||||
import './index.sass'
|
||||
|
||||
import Navigation from './Navigation'
|
||||
import RolePicker from '../role-picker'
|
||||
import RoleEditor from '../role-editor'
|
||||
import AddServer from '../add-server'
|
||||
import Error404 from '../../pages/Error404'
|
||||
|
||||
// import mockData from './mockData'
|
||||
|
||||
const mapState = ({ servers, user, appState }) => {
|
||||
return {
|
||||
servers,
|
||||
user,
|
||||
fade: appState.fade
|
||||
}
|
||||
}
|
||||
|
||||
@connect(mapState)
|
||||
class Servers extends Component {
|
||||
get defaultPath () {
|
||||
console.log(this.props.servers.toJS())
|
||||
|
||||
const first = this.props.servers.first()
|
||||
if (first != null) {
|
||||
return first.get('id')
|
||||
}
|
||||
|
||||
return 'add'
|
||||
}
|
||||
|
||||
render () {
|
||||
return <div className="servers">
|
||||
<Navigation className="servers__nav" servers={this.props.servers} user={this.props.user} />
|
||||
<div className='servers__content'>
|
||||
<Scrollbars className={`fade-element ${(this.props.fade) ? 'fade' : ''}`} autoHeight autoHeightMax='calc(100vh - 80px)'>
|
||||
<Switch>
|
||||
<Route path='/s/add' component={AddServer} exact />
|
||||
<Route path='/s/:server/edit' component={RoleEditor} />
|
||||
<Route path='/s/:server' component={RolePicker} />
|
||||
<Route path='/s' exact render={() => <Redirect to={`/s/${this.defaultPath}`} />} />
|
||||
<Route component={Error404} />
|
||||
</Switch>
|
||||
</Scrollbars>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default Servers
|
|
@ -1,26 +0,0 @@
|
|||
.servers
|
||||
|
||||
$fullH: calc(100vh - 180px)
|
||||
|
||||
display: grid
|
||||
grid-template-rows: 100px $fullH
|
||||
grid-template-columns: 300px 1fr
|
||||
grid-template-areas: "user content" "listing content"
|
||||
|
||||
|
||||
&__nav
|
||||
grid-area: listing
|
||||
overflow: hidden
|
||||
// height: $fullH
|
||||
|
||||
&__content
|
||||
background-color: var(--c-3)
|
||||
// padding: 15px
|
||||
grid-area: content
|
||||
position: relative
|
||||
// height: $fullH
|
||||
overflow: hidden
|
||||
box-sizing: border-box
|
||||
|
||||
.inner
|
||||
padding: 15px
|
|
@ -1,40 +0,0 @@
|
|||
import React, { Component } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import Scrollbars from 'react-custom-scrollbars'
|
||||
import Logotype from '../logotype'
|
||||
import './wrapper.sass'
|
||||
import discordLogo from '../../pages/images/discord-logo.svg'
|
||||
|
||||
class Wrapper extends Component {
|
||||
render () {
|
||||
return <div className='wrapper'>
|
||||
<Scrollbars autoHeight autoHeightMax='calc(100vh + 2px)'>
|
||||
<div className='wrapper__background' />
|
||||
<div className='wrapper__container'>
|
||||
<nav uk-navbar='' className='uk-navbar-transparent wrapper__nav'>
|
||||
<div className='uk-navbar-left'>
|
||||
<Link to="/">
|
||||
<Logotype style={{ height: '2rem' }} className='wrapper__logotype' />
|
||||
</Link>
|
||||
</div>
|
||||
<div className='uk-navbar-right'>
|
||||
<ul className='uk-navbar-nav'>
|
||||
<li><div className='wrapper__nav__button'>
|
||||
<a href="/oauth/bot/flow" target="_blank" className="uk-button rp-button discord-alt"><img src={discordLogo} className="rp-button-logo" alt=""/> Add Roleypoly</a>
|
||||
</div></li>
|
||||
<li><a href='https://discord.gg/PWQUVsd'>Support Discord</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<main className="wrapper__content">
|
||||
{
|
||||
this.props.children
|
||||
}
|
||||
</main>
|
||||
</div>
|
||||
</Scrollbars>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
export default Wrapper
|
|
@ -1,35 +0,0 @@
|
|||
// .wrapper__logo:hover mask
|
||||
// /* transform: translate 1s ease-in-out repeat; */
|
||||
// transform: rotate(30deg)
|
||||
|
||||
.wrapper
|
||||
|
||||
&__background
|
||||
background-color: var(--c-1)
|
||||
position: fixed
|
||||
top: 0
|
||||
left: 0
|
||||
right: 0
|
||||
bottom: 0
|
||||
z-index: -1000
|
||||
|
||||
&__container
|
||||
width: 960px
|
||||
margin: 0 auto
|
||||
|
||||
&__nav
|
||||
padding: 0 20px
|
||||
z-index: 1000
|
||||
|
||||
&__button
|
||||
box-sizing: border-box
|
||||
height: 82px
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
&>.rp-button
|
||||
padding-left: 15px
|
||||
padding-right: 15px
|
||||
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
.rp-button
|
||||
border: 0
|
||||
border-radius: 2px
|
||||
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out
|
||||
position: relative
|
||||
box-sizing: border-box
|
||||
transform: translateZ(0)
|
||||
|
||||
&::after
|
||||
content: ""
|
||||
position: absolute
|
||||
top: 0
|
||||
bottom: 0
|
||||
right: 0
|
||||
left: 0
|
||||
background-color: rgba(0,0,0,0.1)
|
||||
border-radius: 2px
|
||||
transform: translateZ(0)
|
||||
opacity: 0
|
||||
transition: opacity 0.15s ease-in-out
|
||||
|
||||
&:hover
|
||||
transform: translateZ(0) translateY(-1px)
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.3)
|
||||
&::after
|
||||
opacity: 0.7
|
||||
|
||||
&:active
|
||||
transform: translateZ(0) 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)
|
||||
|
||||
&.discord
|
||||
background-color: var(--c-discord)
|
||||
color: var(--c-white)
|
||||
|
||||
&.discord-alt
|
||||
background-color: transparent
|
||||
color: var(--c-white)
|
||||
border: 1px solid var(--c-discord)
|
||||
&::after
|
||||
background-color: hsla(0,0%,100%,0.05)
|
||||
transition: opacity 0.3s ease-in-out
|
||||
|
||||
&-logo
|
||||
height: 1.6rem
|
||||
|
||||
.rp-discord
|
||||
border: 1px solid var(--c-discord-dark)
|
||||
box-shadow: 0 0 1px rgba(0,0,0,0.3)
|
|
@ -1,65 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "source-han-sans-japanese", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.font-sans-serif {
|
||||
font-family: sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
:root {
|
||||
--c-white: #efefef;
|
||||
--c-9: #EBD6D4;
|
||||
--c-7: #ab9b9a;
|
||||
--c-5: #756867;
|
||||
--c-3: #5d5352;
|
||||
--c-1: #453e3d;
|
||||
--c-dark: #332d2d;
|
||||
--c-green: #46b646;
|
||||
--c-red: #e95353;
|
||||
--c-discord: #7289da;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--c-9);
|
||||
color: var(--c-1);
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--c-9);
|
||||
color: var(--c-1);
|
||||
}
|
||||
|
||||
html {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
color: var(--c-white);
|
||||
background-color: var(--c-1);
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
color: var(--c-9);
|
||||
}
|
||||
|
||||
.uk-navbar-nav>li>a {
|
||||
color: var(--c-7);
|
||||
}
|
||||
|
||||
.fade-element {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.fade {
|
||||
opacity: 0;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import './index.css'
|
||||
import App from './App'
|
||||
import {unregister} from './registerServiceWorker'
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'))
|
||||
unregister()
|
|
@ -1,7 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.6 KiB |
|
@ -1,14 +0,0 @@
|
|||
import React from 'react'
|
||||
import './landing.sass'
|
||||
|
||||
const Error404 = ({ root = false }) =>
|
||||
<div className="landing uk-width-1-1 uk-text-center">
|
||||
<div className="uk-container">
|
||||
<section>
|
||||
<h1><span role="img">💔</span> g-gomen nasai</h1>
|
||||
<h4>I'm not sure what page you were looking for <span role="img">😢</span></h4>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
export default Error404
|
|
@ -1,19 +0,0 @@
|
|||
import React, { Fragment } from 'react'
|
||||
|
||||
import goodImg from './images/whynoroles-good.png'
|
||||
import badImg from './images/whynoroles-bad.png'
|
||||
|
||||
const WhyNoRoles = (props) => {
|
||||
return <Fragment>
|
||||
<h2>Why don't I see any roles in my editor?</h2>
|
||||
<p>Roleypoly needs to be a higher role position than other roles in order to assign them to anyone.</p>
|
||||
<h3 className="pages__bad">Bad <i uk-icon="icon: ban"></i></h3>
|
||||
<img src={badImg} className="rp-discord" alt="Bad example"/>
|
||||
<p>In this example, Roleypoly is at the bottom of the list. It can't assign anyone any roles above it.</p>
|
||||
<h3 className="pages__good">Good <i uk-icon="icon: check"></i></h3>
|
||||
<img src={goodImg} className="rp-discord" alt="Good example"/>
|
||||
<p>In this example, Roleypoly is above other roles, and will be able to assign them.</p>
|
||||
</Fragment>
|
||||
}
|
||||
|
||||
export default WhyNoRoles
|
|
@ -1,21 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 245 240" style="enable-background:new 0 0 245 240;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#efefef;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M104.4,103.9c-5.7,0-10.2,5-10.2,11.1c0,6.1,4.6,11.1,10.2,11.1c5.7,0,10.2-5,10.2-11.1
|
||||
C114.7,108.9,110.1,103.9,104.4,103.9z"/>
|
||||
<path class="st0" d="M140.9,103.9c-5.7,0-10.2,5-10.2,11.1c0,6.1,4.6,11.1,10.2,11.1c5.7,0,10.2-5,10.2-11.1
|
||||
C151.1,108.9,146.6,103.9,140.9,103.9z"/>
|
||||
<path class="st0" d="M189.5,20H55.5C44.2,20,35,29.2,35,40.6v135.2c0,11.4,9.2,20.6,20.5,20.6h113.4l-5.3-18.5l12.8,11.9l12.1,11.2
|
||||
L210,220v-44.2v-10V40.6C210,29.2,200.8,20,189.5,20z M150.9,150.6c0,0-3.6-4.3-6.6-8.1c13.1-3.7,18.1-11.9,18.1-11.9
|
||||
c-4.1,2.7-8,4.6-11.5,5.9c-5,2.1-9.8,3.5-14.5,4.3c-9.6,1.8-18.4,1.3-25.9-0.1c-5.7-1.1-10.6-2.7-14.7-4.3c-2.3-0.9-4.8-2-7.3-3.4
|
||||
c-0.3-0.2-0.6-0.3-0.9-0.5c-0.2-0.1-0.3-0.2-0.4-0.3c-1.8-1-2.8-1.7-2.8-1.7s4.8,8,17.5,11.8c-3,3.8-6.7,8.3-6.7,8.3
|
||||
c-22.1-0.7-30.5-15.2-30.5-15.2c0-32.2,14.4-58.3,14.4-58.3c14.4-10.8,28.1-10.5,28.1-10.5l1,1.2c-18,5.2-26.3,13.1-26.3,13.1
|
||||
s2.2-1.2,5.9-2.9c10.7-4.7,19.2-6,22.7-6.3c0.6-0.1,1.1-0.2,1.7-0.2c6.1-0.8,13-1,20.2-0.2c9.5,1.1,19.7,3.9,30.1,9.6
|
||||
c0,0-7.9-7.5-24.9-12.7l1.4-1.6c0,0,13.7-0.3,28.1,10.5c0,0,14.4,26.1,14.4,58.3C181.5,135.4,173,149.9,150.9,150.6z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 76 KiB |
|
@ -1,25 +0,0 @@
|
|||
import React from 'react'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
import Scrollbars from 'react-custom-scrollbars'
|
||||
import './pages.sass'
|
||||
|
||||
import WhyNoRoles from './WhyNoRoles'
|
||||
import Error404 from './Error404'
|
||||
export { default as Landing } from './Landing'
|
||||
export { default as Error404 } from './Error404'
|
||||
|
||||
const Pages = (props) => {
|
||||
return <div className="pages">
|
||||
<Scrollbars autoHeight autoHeightMax='calc(100vh - 80px)'>
|
||||
<div className="pages-inner">
|
||||
<Switch>
|
||||
<Route path="/help/why-no-roles" component={WhyNoRoles} />
|
||||
{/* { isDev ? <Route path="/p/landing" component={Landing} /> : null } */}
|
||||
<Route component={Error404} />
|
||||
</Switch>
|
||||
</div>
|
||||
</Scrollbars>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Pages
|
|
@ -1,76 +0,0 @@
|
|||
.landing
|
||||
min-height: calc(100vh)
|
||||
position: relative
|
||||
top: -80px
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
.Typist .Cursor
|
||||
display: inline-block
|
||||
color: transparent
|
||||
border-left: 1px solid var(--c-white)
|
||||
|
||||
&--blinking
|
||||
opacity: 1
|
||||
animation: blink 2s ease-in-out infinite
|
||||
|
||||
@keyframes blink
|
||||
0%
|
||||
opacity: 1
|
||||
50%
|
||||
opacity: 0
|
||||
100%
|
||||
opacity: 1
|
||||
|
||||
&__roleypoly
|
||||
|
||||
|
||||
&__discord
|
||||
--not-quite-black: #23272A
|
||||
--dark-but-not-black: #2C2F33
|
||||
--greyple: #99AAB5
|
||||
--blurple: var(--c-discord)
|
||||
|
||||
background-color: var(--dark-but-not-black)
|
||||
padding: 10px
|
||||
text-align: left
|
||||
color: var(--c-white)
|
||||
|
||||
.discord
|
||||
&__chat
|
||||
padding: 10px 0
|
||||
|
||||
span
|
||||
display: inline-block
|
||||
margin-left: 5px
|
||||
|
||||
.timestamp
|
||||
font-size: 0.7em
|
||||
color: hsla(0,0%,100%,.2)
|
||||
|
||||
.username
|
||||
font-weight: bold
|
||||
|
||||
&__textarea
|
||||
background-color: hsla(218,5%,47%,.3)
|
||||
border-radius: 5px
|
||||
padding: 10px
|
||||
|
||||
|
||||
section
|
||||
margin-top: 50px
|
||||
|
||||
h1
|
||||
color: var(--c-7)
|
||||
|
||||
h4
|
||||
color: var(--c-9)
|
||||
|
||||
h1,h2,h3,h4,h5,h6
|
||||
margin: 0.3em
|
||||
|
||||
.subtext
|
||||
color: hsla(0,0%,100%,0.8)
|
||||
font-size: 0.9em
|
||||
line-height: 1.6
|
|
@ -1,14 +0,0 @@
|
|||
.pages
|
||||
background-color: var(--c-3)
|
||||
|
||||
&-inner
|
||||
padding: 45px 35px
|
||||
|
||||
&__bad
|
||||
color: var(--c-red)
|
||||
|
||||
&__good
|
||||
color: var(--c-green)
|
||||
|
||||
img
|
||||
max-width: 75%
|
|
@ -1,45 +0,0 @@
|
|||
import { combineReducers } from 'redux'
|
||||
|
||||
import servers from './servers'
|
||||
import user from './user'
|
||||
import rolePicker from './role-picker'
|
||||
import roleEditor from './role-editor'
|
||||
import { routerMiddleware } from 'react-router-redux'
|
||||
// import roles from './roles'
|
||||
|
||||
const initialState = {
|
||||
ready: false,
|
||||
fade: true
|
||||
}
|
||||
|
||||
const appState = (state = initialState, { type, data }) => {
|
||||
switch (type) {
|
||||
case Symbol.for('app ready'):
|
||||
return {
|
||||
...state,
|
||||
ready: true,
|
||||
fade: false
|
||||
}
|
||||
|
||||
case Symbol.for('app fade'):
|
||||
return {
|
||||
...state,
|
||||
fade: data
|
||||
}
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
appState,
|
||||
servers,
|
||||
user,
|
||||
router: routerMiddleware,
|
||||
// roles,
|
||||
rolePicker,
|
||||
roleEditor
|
||||
})
|
||||
|
||||
export default rootReducer
|
|
@ -1,54 +0,0 @@
|
|||
import { Map, OrderedMap, fromJS } from 'immutable'
|
||||
|
||||
const initialState = Map({
|
||||
viewMap: OrderedMap({}),
|
||||
originalSnapshot: OrderedMap({}),
|
||||
hasAvailableRoles: true
|
||||
})
|
||||
|
||||
const reducer = (state = initialState, { type, data }) => {
|
||||
switch (type) {
|
||||
case Symbol.for('re: setup'):
|
||||
const { viewMap, originalSnapshot, ...rest } = data
|
||||
return state.merge({ viewMap: OrderedMap(viewMap), originalSnapshot: OrderedMap(originalSnapshot), ...rest })
|
||||
|
||||
case Symbol.for('re: set category'):
|
||||
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'):
|
||||
return state.deleteIn(['viewMap', data])
|
||||
|
||||
case Symbol.for('re: switch category mode'):
|
||||
return state.setIn(['viewMap', data.id, 'mode'], data.mode)
|
||||
|
||||
case Symbol.for('re: add role to category'):
|
||||
const category = state.getIn(['viewMap', data.id])
|
||||
return state.setIn(['viewMap', data.id],
|
||||
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.id])
|
||||
return state.setIn(['viewMap', data.id],
|
||||
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')))
|
||||
)
|
||||
|
||||
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:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default reducer
|
|
@ -1,44 +0,0 @@
|
|||
import { Map, OrderedMap } from 'immutable'
|
||||
|
||||
const initialState = Map({
|
||||
hidden: true, // should the view be hidden?
|
||||
// emptyRoles: true, // helps derender roles so there's no visible element state change
|
||||
isEditingMessage: false,
|
||||
messageBuffer: '',
|
||||
viewMap: OrderedMap({}), // roles in categories
|
||||
originalRolesSelected: Map({}), // Map<role id, bool> -- original roles for diffing against selected
|
||||
rolesSelected: Map({}) // Map<role id, bool> -- new roles for diffing
|
||||
})
|
||||
|
||||
export default (state = initialState, { type, data }) => {
|
||||
switch (type) {
|
||||
case Symbol.for('rp: setup role picker'):
|
||||
return Map(data)
|
||||
|
||||
case Symbol.for('rp: hide role picker ui'):
|
||||
return state.set('hidden', data)
|
||||
|
||||
case Symbol.for('rp: reset role picker ui'):
|
||||
return state.set('emptyRoles', data)
|
||||
|
||||
case Symbol.for('rp: update selected roles'):
|
||||
return state.mergeIn(['rolesSelected'], data)
|
||||
|
||||
case Symbol.for('rp: sync selected roles'):
|
||||
return state.set('originalRolesSelected', state.get('rolesSelected'))
|
||||
|
||||
case Symbol.for('rp: reset selected'):
|
||||
return state.set('rolesSelected', state.get('originalRolesSelected'))
|
||||
|
||||
case Symbol.for('rp: set message editor state'):
|
||||
return state.set('isEditingMessage', data)
|
||||
|
||||
case Symbol.for('rp: edit message buffer'):
|
||||
return state.set('messageBuffer', data)
|
||||
// case Symbol.for('rp: zero role picker'):
|
||||
// return initialState
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
import { Set, OrderedMap, Map, fromJS } from 'immutable'
|
||||
|
||||
const blankServer = Map({
|
||||
id: '386659935687147521',
|
||||
gm: {
|
||||
nickname: null,
|
||||
color: '#cca1a1'
|
||||
},
|
||||
message: 'Hey hey!',
|
||||
server: {
|
||||
id: '386659935687147521',
|
||||
name: 'Roleypoly',
|
||||
ownerID: '62601275618889728',
|
||||
icon: '4fa0c1063649a739f3fe1a0589aa2c03'
|
||||
},
|
||||
roles: Set([]),
|
||||
categories: OrderedMap(),
|
||||
perms: {
|
||||
isAdmin: true,
|
||||
canManageRoles: true
|
||||
}
|
||||
})
|
||||
|
||||
const initialState = OrderedMap({})
|
||||
|
||||
export default (state = initialState, { type, data }) => {
|
||||
switch (type) {
|
||||
case Symbol.for('update servers'):
|
||||
return data.reduce((acc, s) => acc.set(s.id, fromJS(s)), OrderedMap())
|
||||
|
||||
// case Symbol.for('update server roles'):
|
||||
// return state.set(data.id,
|
||||
// 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'):
|
||||
return state.set('0', blankServer)
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
import { Map } from 'immutable'
|
||||
|
||||
const initialState = Map({
|
||||
isLoggedIn: false,
|
||||
username: 'あたし',
|
||||
discriminator: '0001',
|
||||
id: '',
|
||||
avatar: null
|
||||
})
|
||||
|
||||
export default (state = initialState, { type, data }) => {
|
||||
switch (type) {
|
||||
case Symbol.for('set user'):
|
||||
return Map({...data, isLoggedIn: true})
|
||||
|
||||
case Symbol.for('reset user'):
|
||||
return initialState
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
// In production, we register a service worker to serve assets from local cache.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on the "N+1" visit to a page, since previously
|
||||
// cached resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
|
||||
// This link also includes instructions on opting out of this behavior.
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
export default function register() {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Lets check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl);
|
||||
} else {
|
||||
// Is not local host. Just register service worker
|
||||
registerValidSW(swUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the old content will have been purged and
|
||||
// the fresh content will have been added to the cache.
|
||||
// It's the perfect time to display a "New content is
|
||||
// available; please refresh." message in your web app.
|
||||
console.log('New content is available; please refresh.');
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
if (
|
||||
response.status === 404 ||
|
||||
response.headers.get('content-type').indexOf('javascript') === -1
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
import React, { Component, Fragment } from 'react'
|
||||
import { Route, Switch, Redirect } from 'react-router-dom'
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router'
|
||||
|
||||
import Servers from '../components/servers'
|
||||
import OauthCallback from '../components/oauth-callback'
|
||||
import OauthFlow from '../components/oauth-flow'
|
||||
import OauthBotFlow from '../components/oauth-bot-flow'
|
||||
import Pages, { Landing, Error404 } from '../pages'
|
||||
import ServerLanding from '../components/servers/ServerLanding'
|
||||
|
||||
const aaa = (props) => (<div>{ JSON.stringify(props) }</div>)
|
||||
|
||||
export default
|
||||
@withRouter
|
||||
@connect(({ appState, user }) => ({ ready: appState.ready, user }))
|
||||
class AppRouter extends Component {
|
||||
render () {
|
||||
const isLoggedIn = this.props.user.get('isLoggedIn')
|
||||
|
||||
if (!this.props.ready) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <Switch>
|
||||
{ (isLoggedIn === true)
|
||||
|
||||
// YES LOGGED IN
|
||||
? <Route path='/s' component={Servers} />
|
||||
|
||||
|
||||
// NOT LOGGED IN
|
||||
: [<Route path='/s/:server' key={1} component={ServerLanding} />, <Route path='/s' key={2} render={() => <Redirect to="/" />} />]
|
||||
|
||||
}
|
||||
|
||||
{/* GENERAL ROUTES */}
|
||||
<Route path='/oauth/callback' component={OauthCallback} />
|
||||
<Route path='/oauth/flow' component={OauthFlow} />
|
||||
<Route path='/oauth/bot/flow' component={OauthBotFlow} />
|
||||
<Route path="/p/landing" exact component={Landing} />
|
||||
<Route path='/p' component={Pages} />
|
||||
<Route path='/help' component={Pages} />
|
||||
|
||||
<Route exact path='/' render={() =>
|
||||
isLoggedIn
|
||||
? <Redirect to="/s" />
|
||||
: <Landing root={true} />
|
||||
} />
|
||||
|
||||
<Route component={Error404} />
|
||||
</Switch>
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
|
||||
import { createStore, applyMiddleware, compose } from 'redux'
|
||||
import thunk from 'redux-thunk'
|
||||
import { createLogger } from 'redux-logger'
|
||||
// import api from '../middleware/api'
|
||||
import rootReducer from '../reducers'
|
||||
import DevTools from '../components/dev-tools'
|
||||
import { routerMiddleware } from 'react-router-redux'
|
||||
|
||||
const configureStore = (preloadedState, history) => {
|
||||
const store = createStore(
|
||||
rootReducer,
|
||||
preloadedState,
|
||||
compose(
|
||||
applyMiddleware(thunk, routerMiddleware(history), createLogger()),
|
||||
// DevTools.instrument()
|
||||
)
|
||||
)
|
||||
|
||||
if (module.hot) {
|
||||
// Enable Webpack hot module replacement for reducers
|
||||
module.hot.accept('../reducers', () => {
|
||||
store.replaceReducer(rootReducer)
|
||||
})
|
||||
}
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
export default configureStore
|
|
@ -1,5 +0,0 @@
|
|||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./configureStore.prod')
|
||||
} else {
|
||||
module.exports = require('./configureStore.dev')
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import { createStore, applyMiddleware } from 'redux'
|
||||
import { routerMiddleware } from 'react-router-redux'
|
||||
|
||||
import thunk from 'redux-thunk'
|
||||
// import api from '../middleware/api'
|
||||
import rootReducer from '../reducers'
|
||||
|
||||
const configureStore = (preloadedState, history) => createStore(
|
||||
rootReducer,
|
||||
preloadedState,
|
||||
applyMiddleware(thunk, routerMiddleware(history))
|
||||
)
|
||||
|
||||
export default configureStore
|
|
@ -1 +0,0 @@
|
|||
export const msgToReal = (msg) => (msg.replace(/</g, '<').replace(/\n/g, '<br />'))
|
BIN
UI/static/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
10704
UI/yarn.lock
17
api/ui.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
// note, this file only contains stuff for complicated routes.
|
||||
// next.js will handle anything beyond this.
|
||||
module.exports = (R, $) => {
|
||||
const processMappings = mapping => {
|
||||
for (let p in mapping) {
|
||||
R.get(p, ctx => {
|
||||
$.ui.render(ctx.req, ctx.res, mapping[p], {...ctx.params, ...ctx.query})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
processMappings({
|
||||
"/s/add": "/_server_add",
|
||||
"/s/:id": "/_server",
|
||||
"/help/:page": "/_help"
|
||||
})
|
||||
}
|
28
docker-compose.example.yml
Normal file
|
@ -0,0 +1,28 @@
|
|||
version: '2.1'
|
||||
services:
|
||||
|
||||
app:
|
||||
image: katie/roleypoly
|
||||
ports:
|
||||
- 6000:6769
|
||||
links:
|
||||
- pg
|
||||
environment:
|
||||
DB_URL: postgres://roleypoly:19216801@pg:5432/roleypoly
|
||||
NODE_ENV: production
|
||||
DISCORD_CLIENT_ID: '396227611649'
|
||||
DISCORD_CLIENT_SECRET: RnX8pcH5FrDcs26su-
|
||||
DISCORD_BOT_TOKEN: Mzk2MjI3MTM0Mjo7ROXbvyW09y0
|
||||
APP_URL: http://localhost:6000
|
||||
OAUTH_AUTH_CALLBACK: http://localhost:6000/oauth/callback
|
||||
APP_KEY: 9vJrVFXHazgDo0RuF
|
||||
|
||||
pg:
|
||||
image: postgres:10-alpine
|
||||
ports:
|
||||
- 5432
|
||||
environment:
|
||||
POSTGRES_PASSWORD: 19216801
|
||||
POSTGRES_DB: roleypoly
|
||||
POSTGRES_USER: roleypoly
|
||||
POSTGRES_INITDB_ARGS: -A trust
|
|
@ -28,6 +28,8 @@ Array.prototype.filterNot = Array.prototype.filterNot || function (predicate) {
|
|||
const server = http.createServer(app.callback())
|
||||
const io = _io(server, { transports: ['websocket'], path: '/api/socket.io' })
|
||||
|
||||
|
||||
|
||||
const M = new Roleypoly(router, io, app) // eslint-disable-line no-unused-vars
|
||||
|
||||
app.keys = [ process.env.APP_KEY ]
|
||||
|
@ -45,80 +47,6 @@ async function start () {
|
|||
const compress = require('koa-compress')
|
||||
app.use(compress())
|
||||
|
||||
// SPA + Static
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const pub = path.join(__dirname, 'public')
|
||||
log.info('public path', pub)
|
||||
|
||||
const staticFiles = require('koa-static')
|
||||
// app.use(staticFiles(pub, { defer: true, gzip: true, br: true }))
|
||||
|
||||
const send = require('koa-send')
|
||||
app.use(async (ctx, next) => {
|
||||
if (ctx.path.startsWith('/api')) {
|
||||
log.info('api breakout')
|
||||
return next()
|
||||
}
|
||||
|
||||
const chkPath = path.resolve(path.join(pub, ctx.path))
|
||||
log.info('chkPath', chkPath)
|
||||
if (!chkPath.startsWith(pub)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
try {
|
||||
fs.statSync(chkPath)
|
||||
log.info('sync pass')
|
||||
ctx.body = fs.readFileSync(chkPath, { encoding: 'utf-8' })
|
||||
ctx.type = path.extname(ctx.path)
|
||||
log.info('body sent')
|
||||
ctx.status = 200
|
||||
return next()
|
||||
} catch (e) {
|
||||
log.warn('failed')
|
||||
if (ctx.path.startsWith('/static/')) {
|
||||
return next()
|
||||
}
|
||||
try {
|
||||
ctx.body = fs.readFileSync(path.join(pub, 'index.html'), { encoding: 'utf-8' })
|
||||
} catch (e) {
|
||||
ctx.body = e.stack || e.trace
|
||||
ctx.status = 500
|
||||
}
|
||||
log.info('index sent')
|
||||
ctx.status = 200
|
||||
return next()
|
||||
}
|
||||
|
||||
// try {
|
||||
// await next()
|
||||
// } catch (e) {
|
||||
// send(ctx, 'index.html', { root: pub })
|
||||
// }
|
||||
|
||||
})
|
||||
// const sendOpts = {root: pub, index: 'index.html'}
|
||||
// // const sendOpts = {}
|
||||
// app.use(async (ctx, next) => {
|
||||
// if (ctx.path.startsWith('/api')) {
|
||||
// return await next()
|
||||
// }
|
||||
|
||||
// try {
|
||||
// log.info('pass 1', ctx.path, __dirname+'/public'+ctx.path)
|
||||
// await send(ctx, __dirname+'/public'+ctx.path, sendOpts)
|
||||
// } catch (error) {
|
||||
// try {
|
||||
// log.info('pass 2 /', ctx.path, __dirname+'/public/index.html')
|
||||
// await send(ctx, __dirname+'/public/index.html', sendOpts)
|
||||
// } catch (error) {
|
||||
// log.info('exit to next', ctx.path)
|
||||
// await next()
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
// Request logger
|
||||
app.use(async (ctx, next) => {
|
||||
let timeStart = new Date()
|
10
next.config.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
module.exports = {
|
||||
webpack: config => {
|
||||
// Fixes npm packages that depend on `fs` module
|
||||
config.node = {
|
||||
fs: 'empty'
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
}
|
36
package.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "roleypoly",
|
||||
"version": "2.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "pm2 start index.js",
|
||||
"dev": "pm2 start index.js --watch",
|
||||
"build": "next build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/uws": "^11.149.1",
|
||||
"chalk": "^2.4.2",
|
||||
"discord.js": "^11.4.2",
|
||||
"dotenv": "^6.2.0",
|
||||
"erlpack": "github:discordapp/erlpack",
|
||||
"glob": "^7.1.3",
|
||||
"immutable": "^3.8.2",
|
||||
"koa": "^2.7.0",
|
||||
"koa-better-router": "^2.1.1",
|
||||
"koa-bodyparser": "^4.2.1",
|
||||
"koa-compress": "^3.0.0",
|
||||
"koa-session": "^5.10.1",
|
||||
"ksuid": "^1.1.3",
|
||||
"lru-cache": "^5.1.1",
|
||||
"next": "^8.0.3",
|
||||
"pg": "^7.8.1",
|
||||
"pg-hstore": "^2.3.2",
|
||||
"pm2": "^3.3.1",
|
||||
"react": "^16.8.3",
|
||||
"react-dom": "^16.8.3",
|
||||
"sequelize": "^4.42.0",
|
||||
"socket.io": "^2.2.0",
|
||||
"superagent": "^4.1.0",
|
||||
"uuid": "^3.3.2"
|
||||
}
|
||||
}
|