This commit is contained in:
Katalina / stardust 2018-01-06 17:40:52 -06:00
parent 7dd7c170b4
commit 86a222fb98
20 changed files with 291 additions and 46 deletions

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}

View file

@ -49,10 +49,31 @@ module.exports = (R, $) => {
}) })
R.get('/api/auth/redirect', ctx => { R.get('/api/auth/redirect', ctx => {
ctx.body = { url: $.discord.getAuthUrl() } const url = $.discord.getAuthUrl()
if (ctx.query.url === '✔️') {
ctx.body = { url }
return
}
ctx.redirect(url)
}) })
R.post('/api/auth/logout', ctx => { R.post('/api/auth/logout', ctx => {
ctx.session = null ctx.session = null
}) })
R.get('/api/oauth/bot', ctx => {
const url = $.discord.getBotJoinUrl()
if (ctx.query.url === '✔️') {
ctx.body = { url }
return
}
ctx.redirect(url)
})
R.get('/api/oauth/bot/callback', ctx => {
console.log(ctx.request)
})
} }

View file

@ -67,9 +67,13 @@ module.exports = (R, $) => {
gm = await gm.addRoles(added.filter(pred)) gm = await gm.addRoles(added.filter(pred))
} }
if (removed.length > 0) { setTimeout(() => {
gm = await gm.removeRoles(removed.filter(pred)) if (removed.length > 0) {
} gm.removeRoles(removed.filter(pred))
}
}, 1000)
// console.log('role patch', { added, removed, allowedRoles, addedFiltered: added.filterNot(pred), removedFiltered: removed.filterNot(pred) })
ctx.body = { ok: true } ctx.body = { ok: true }
}) })

View file

@ -18,6 +18,10 @@ Array.prototype.areduce = async function (predicate, acc = []) { // eslint-disab
return acc return acc
} }
Array.prototype.filterNot = Array.prototype.filterNot || function (predicate) {
return this.filter(v => !predicate(v))
}
// Create the server and socket.io server // Create the server and socket.io server
const server = http.createServer(app.callback()) const server = http.createServer(app.callback())
const io = _io(server, { transports: ['websocket'], path: '/api/socket.io', wsEngine: 'uws' }) const io = _io(server, { transports: ['websocket'], path: '/api/socket.io', wsEngine: 'uws' })

View file

@ -137,7 +137,7 @@ class DiscordService extends Service {
// returns the bot join url with MANAGE_ROLES permission // returns the bot join url with MANAGE_ROLES permission
// MANAGE_ROLES is the only permission we really need. // MANAGE_ROLES is the only permission we really need.
getBotJoinUrl () { getBotJoinUrl () {
return `https://discordapp.com/oauth2/authorize?client_id=${this.clientId}&scope=bot&permissions=268435456&redirect_uri=${this.botCallback}` return `https://discordapp.com/oauth2/authorize?client_id=${this.clientId}&scope=bot&permissions=268435456`
} }
mentionResponse (message) { mentionResponse (message) {
@ -215,6 +215,7 @@ class DiscordService extends Service {
} }
} }
} }
} }
module.exports = DiscordService module.exports = DiscordService

View file

@ -25,12 +25,12 @@ class PresentationService extends Service {
async presentableServers (collection, userId) { async presentableServers (collection, userId) {
return collection.array().areduce(async (acc, server) => { return collection.array().areduce(async (acc, server) => {
const gm = server.members.get(userId) const gm = server.members.get(userId)
acc.push(await this.presentableServer(server, gm)) acc.push(await this.presentableServer(server, gm, { incRoles: false }))
return acc return acc
}) })
} }
async presentableServer (server, gm) { async presentableServer (server, gm, { incRoles = true } = {}) {
const sd = await this.ctx.server.get(server.id) const sd = await this.ctx.server.get(server.id)
return { return {
@ -45,7 +45,7 @@ class PresentationService extends Service {
ownerID: server.ownerID, ownerID: server.ownerID,
icon: server.icon icon: server.icon
}, },
roles: (await this.rolesByServer(server, sd)).map(r => ({ ...r, selected: gm.roles.has(r.id) })), roles: (incRoles) ? (await this.rolesByServer(server, sd)).map(r => ({ ...r, selected: gm.roles.has(r.id) })) : [],
message: sd.message, message: sd.message,
categories: sd.categories, categories: sd.categories,
perms: this.discord.getPermissions(gm) perms: this.discord.getPermissions(gm)

View file

@ -1,4 +1,5 @@
import superagent from 'superagent' import superagent from 'superagent'
import { push } from 'react-router-redux'
export const fetchServers = async dispatch => { export const fetchServers = async dispatch => {
const rsp = await superagent.get('/api/servers') const rsp = await superagent.get('/api/servers')
@ -46,3 +47,38 @@ export const userLogout = async dispatch => {
window.location.href = '/' 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
}

View file

@ -1,9 +1,39 @@
import React, { Component } from 'react' 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 { export default class AddServer extends Component {
render () { polling = null
return <div className="inner">
componentDidMount () {
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> </div>
} }
} }

View file

@ -0,0 +1,29 @@
.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

View file

@ -0,0 +1,12 @@
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

View file

@ -0,0 +1,29 @@
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

View file

@ -0,0 +1,48 @@
.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

View file

@ -0,0 +1,27 @@
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

View file

@ -12,7 +12,7 @@ class OauthCallback extends Component {
} }
async componentDidMount () { async componentDidMount () {
const { body: { url } } = await superagent.get('/api/auth/redirect') const { body: { url } } = await superagent.get('/api/auth/redirect?url=✔️')
try { try {
const rsp = await superagent.get('/api/auth/user') const rsp = await superagent.get('/api/auth/user')
this.props.dispatch({ this.props.dispatch({

View file

@ -113,7 +113,7 @@ class RoleEditor extends Component {
render () { render () {
const { server } = this.props const { server } = this.props
if (server.getIn(['server', 'perms', 'canManageRoles']) !== true) { if (server.getIn(['perms', 'canManageRoles']) !== true) {
return <Redirect to={`/s/${server.get('id')}`} /> return <Redirect to={`/s/${server.get('id')}`} />
} }

View file

@ -3,11 +3,12 @@ import { Link } from 'react-router-dom'
import Scrollbars from 'react-custom-scrollbars' import Scrollbars from 'react-custom-scrollbars'
import Logotype from '../logotype' import Logotype from '../logotype'
import './wrapper.sass' import './wrapper.sass'
import discordLogo from '../../pages/images/discord-logo.svg'
class Wrapper extends Component { class Wrapper extends Component {
render () { render () {
return <div className='wrapper'> return <div className='wrapper'>
<Scrollbars autoHeight autoHeightMax='100vh'> <Scrollbars autoHeight autoHeightMax='calc(100vh + 2px)'>
<div className='wrapper__background' /> <div className='wrapper__background' />
<div className='wrapper__container'> <div className='wrapper__container'>
<nav uk-navbar='' className='uk-navbar-transparent wrapper__nav'> <nav uk-navbar='' className='uk-navbar-transparent wrapper__nav'>
@ -18,7 +19,9 @@ class Wrapper extends Component {
</div> </div>
<div className='uk-navbar-right'> <div className='uk-navbar-right'>
<ul className='uk-navbar-nav'> <ul className='uk-navbar-nav'>
<li><Link to='/start'>Get Started</Link></li> <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> <li><a href='https://discord.gg/PWQUVsd'>Support Discord</a></li>
</ul> </ul>
</div> </div>

View file

@ -21,4 +21,15 @@
padding: 0 20px padding: 0 20px
z-index: 1000 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

View file

@ -3,6 +3,8 @@
border-radius: 2px border-radius: 2px
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out transition: transform 0.2s ease-out, box-shadow 0.2s ease-out
position: relative position: relative
box-sizing: border-box
transform: translateZ(0)
&::after &::after
content: "" content: ""
@ -13,17 +15,18 @@
left: 0 left: 0
background-color: rgba(0,0,0,0.1) background-color: rgba(0,0,0,0.1)
border-radius: 2px border-radius: 2px
transform: translateZ(0)
opacity: 0 opacity: 0
transition: opacity 0.15s ease-in-out transition: opacity 0.15s ease-in-out
&:hover &:hover
transform: translateY(-1px) transform: translateZ(0) translateY(-1px)
box-shadow: 0 1px 2px rgba(0,0,0,0.3) box-shadow: 0 1px 2px rgba(0,0,0,0.3)
&::after &::after
opacity: 0.7 opacity: 0.7
&:active &:active
transform: translateY(0px) transform: translateZ(0) translateY(0px)
box-shadow: none box-shadow: none
&::after &::after
opacity: 1 opacity: 1
@ -40,6 +43,14 @@
background-color: var(--c-discord) background-color: var(--c-discord)
color: var(--c-white) 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 &-logo
height: 1.6rem height: 1.6rem

View file

@ -5,7 +5,8 @@ import Typist from 'react-typist'
import moment from 'moment' import moment from 'moment'
import './landing.sass' import './landing.sass'
import discordLogo from './images/discord-logo.svg' import discordLogo from './images/discord-logo.svg'
import RoleDemo from '../components/role/demo' import RoleypolyDemo from '../components/demos/roleypoly'
import TypingDemo from '../components/demos/typing'
const Landing = ({ root = false }) => const Landing = ({ root = false }) =>
<div className="landing uk-width-1-1 uk-text-center"> <div className="landing uk-width-1-1 uk-text-center">
@ -20,39 +21,12 @@ const Landing = ({ root = false }) =>
<section uk-grid=""> <section uk-grid="">
{/* Typist */} {/* Typist */}
<div className="uk-width-1-2"> <div className="uk-width-1-2">
<div className="landing__discord rp-discord"> <TypingDemo />
<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>
<p className="subtext">Why are we stuck in the stupid ages?</p> <p className="subtext">Why are we stuck in the stupid ages?</p>
</div> </div>
{/* role side */} {/* role side */}
<div className="uk-width-1-2"> <div className="uk-width-1-2">
<div className="landing__roleypoly"> <RoleypolyDemo />
<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>
<p className="subtext">It's 2018. We can do better.</p> <p className="subtext">It's 2018. We can do better.</p>
</div> </div>
</section> </section>

View file

@ -6,6 +6,7 @@ import { withRouter } from 'react-router'
import Servers from '../components/servers' import Servers from '../components/servers'
import OauthCallback from '../components/oauth-callback' import OauthCallback from '../components/oauth-callback'
import OauthFlow from '../components/oauth-flow' import OauthFlow from '../components/oauth-flow'
import OauthBotFlow from '../components/oauth-bot-flow'
import Pages, { Landing } from '../pages' import Pages, { Landing } from '../pages'
const aaa = (props) => (<div>{ JSON.stringify(props) }</div>) const aaa = (props) => (<div>{ JSON.stringify(props) }</div>)
@ -25,6 +26,7 @@ export default class AppRouter extends Component {
<Route path='/oauth/callback' component={OauthCallback} /> <Route path='/oauth/callback' component={OauthCallback} />
<Route path='/oauth/flow' component={OauthFlow} /> <Route path='/oauth/flow' component={OauthFlow} />
<Route path='/oauth/bot/flow' component={OauthBotFlow} />
<Route path="/p/landing" exact component={Landing} /> <Route path="/p/landing" exact component={Landing} />
<Route path='/p' component={Pages} /> <Route path='/p' component={Pages} />
<Route path='/help' component={Pages} /> <Route path='/help' component={Pages} />