Gisle Aune
6 years ago
36 changed files with 0 additions and 13241 deletions
-
21rpdata-ui/.gitignore
-
14rpdata-ui/README.md
-
12110rpdata-ui/package-lock.json
-
34rpdata-ui/package.json
-
BINrpdata-ui/public/assets/images/bg.png
-
BINrpdata-ui/public/favicon.ico
-
40rpdata-ui/public/index.html
-
15rpdata-ui/public/manifest.json
-
23rpdata-ui/src/App.js
-
13rpdata-ui/src/client.js
-
90rpdata-ui/src/colors.css
-
58rpdata-ui/src/common/Background.js
-
70rpdata-ui/src/common/List.js
-
13rpdata-ui/src/common/LoadingScreen.js
-
15rpdata-ui/src/common/Main.js
-
60rpdata-ui/src/common/Menu.js
-
22rpdata-ui/src/common/css/Background.css
-
50rpdata-ui/src/common/css/List.css
-
7rpdata-ui/src/common/css/LoadingScreen.css
-
3rpdata-ui/src/common/css/Main.css
-
75rpdata-ui/src/common/css/Menu.css
-
33rpdata-ui/src/index.css
-
21rpdata-ui/src/index.js
-
117rpdata-ui/src/registerServiceWorker.js
-
16rpdata-ui/src/routes/logs/LogsMenu.js
-
14rpdata-ui/src/routes/logs/LogsRoot.js
-
51rpdata-ui/src/routes/story-content/StoryContentMenu.js
-
44rpdata-ui/src/routes/story-content/StoryContentRoot.js
-
28rpdata-ui/src/routes/story-content/gql/StoryContentRoot.js
-
36rpdata-ui/src/routes/story/StoryList.js
-
33rpdata-ui/src/routes/story/StoryMenu.js
-
43rpdata-ui/src/routes/story/StoryRoot.js
-
34rpdata-ui/src/routes/story/StoryTags.js
-
16rpdata-ui/src/routes/story/gql/StoryList.js
-
12rpdata-ui/src/routes/story/gql/StoryRoot.js
-
10rpdata-ui/src/routes/story/gql/StoryTags.js
@ -1,21 +0,0 @@ |
|||||
# See https://help.github.com/ignore-files/ for more about ignoring files. |
|
||||
|
|
||||
# dependencies |
|
||||
/node_modules |
|
||||
|
|
||||
# testing |
|
||||
/coverage |
|
||||
|
|
||||
# production |
|
||||
/build |
|
||||
|
|
||||
# misc |
|
||||
.DS_Store |
|
||||
.env.local |
|
||||
.env.development.local |
|
||||
.env.test.local |
|
||||
.env.production.local |
|
||||
|
|
||||
npm-debug.log* |
|
||||
yarn-debug.log* |
|
||||
yarn-error.log* |
|
@ -1,14 +0,0 @@ |
|||||
# User Interface |
|
||||
|
|
||||
## Setup |
|
||||
|
|
||||
This is a UI created with `create-react-app`, so the process of getting started on a new host is navigating to the directory and running these commands. |
|
||||
|
|
||||
```sh |
|
||||
npm install |
|
||||
npm start |
|
||||
``` |
|
||||
|
|
||||
## Release |
|
||||
|
|
||||
This is built and packaged with the parent project. |
|
12110
rpdata-ui/package-lock.json
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,34 +0,0 @@ |
|||||
{ |
|
||||
"name": "rpdata-ui", |
|
||||
"version": "0.1.0", |
|
||||
"private": true, |
|
||||
"dependencies": { |
|
||||
"ajv": "^6.5.2", |
|
||||
"apollo-boost": "^0.1.10", |
|
||||
"apollo-cache-inmemory": "^1.3.0-beta.6", |
|
||||
"graphql": "^0.13.2", |
|
||||
"graphql-tag": "^2.9.2", |
|
||||
"moment": "^2.22.2", |
|
||||
"pluralize": "^7.0.0", |
|
||||
"react": "^16.4.1", |
|
||||
"react-apollo": "^2.1.9", |
|
||||
"react-dom": "^16.4.1", |
|
||||
"react-router": "^4.3.1", |
|
||||
"react-router-dom": "^4.3.1", |
|
||||
"react-scripts": "1.1.4" |
|
||||
}, |
|
||||
"scripts": { |
|
||||
"start": "react-scripts start", |
|
||||
"build": "react-scripts build", |
|
||||
"test": "react-scripts test --env=jsdom", |
|
||||
"eject": "react-scripts eject" |
|
||||
}, |
|
||||
"devDependencies": { |
|
||||
"@playlyfe/gql": "^2.6.0" |
|
||||
}, |
|
||||
"proxy": { |
|
||||
"/graphql": { |
|
||||
"target": "http://10.32.7.1:17000" |
|
||||
} |
|
||||
} |
|
||||
} |
|
Before Width: 301 | Height: 256 | Size: 62 KiB |
@ -1,40 +0,0 @@ |
|||||
<!DOCTYPE html> |
|
||||
<html lang="en"> |
|
||||
<head> |
|
||||
<meta charset="utf-8"> |
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
|
||||
<meta name="theme-color" content="#000000"> |
|
||||
<!-- |
|
||||
manifest.json provides metadata used when your web app is added to the |
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ |
|
||||
--> |
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"> |
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> |
|
||||
<!-- |
|
||||
Notice the use of %PUBLIC_URL% in the tags above. |
|
||||
It will be replaced with the URL of the `public` folder during the build. |
|
||||
Only files inside the `public` folder can be referenced from the HTML. |
|
||||
|
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will |
|
||||
work correctly both with client-side routing and a non-root public URL. |
|
||||
Learn how to configure a non-root public URL by running `npm run build`. |
|
||||
--> |
|
||||
<title>Aite RP</title> |
|
||||
</head> |
|
||||
<body> |
|
||||
<noscript> |
|
||||
You need to enable JavaScript to run this app. |
|
||||
</noscript> |
|
||||
<div id="root"></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`. |
|
||||
--> |
|
||||
</body> |
|
||||
</html> |
|
@ -1,15 +0,0 @@ |
|||||
{ |
|
||||
"short_name": "Aite RP", |
|
||||
"name": "Aite RP Website (aiterp.net)", |
|
||||
"icons": [ |
|
||||
{ |
|
||||
"src": "favicon.ico", |
|
||||
"sizes": "64x64 32x32 24x24 16x16", |
|
||||
"type": "image/x-icon" |
|
||||
} |
|
||||
], |
|
||||
"start_url": "./index.html", |
|
||||
"display": "standalone", |
|
||||
"theme_color": "#000000", |
|
||||
"background_color": "#000000" |
|
||||
} |
|
@ -1,23 +0,0 @@ |
|||||
import React, { Component } from 'react' |
|
||||
import { Route, Redirect, Switch } from "react-router-dom"; |
|
||||
|
|
||||
import StoryRoot from './routes/story/StoryRoot' |
|
||||
import StoryContentRoot from './routes/story-content/StoryContentRoot' |
|
||||
import LogsRoot from './routes/logs/LogsRoot' |
|
||||
|
|
||||
export default class App extends Component { |
|
||||
render() { |
|
||||
return ( |
|
||||
<div className="App"> |
|
||||
<Switch> |
|
||||
<Redirect exact path="/" to="/story/" /> |
|
||||
<Route path="/story/:id(S[0-9a-z]{15})" component={StoryContentRoot}/> |
|
||||
<Route path="/story/" component={StoryRoot}/> |
|
||||
<Route path="/logs/" component={LogsRoot}/> |
|
||||
</Switch> |
|
||||
</div> |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/* See ./common/Menu.js to change the top navbar */ |
|
@ -1,13 +0,0 @@ |
|||||
import { ApolloClient } from "apollo-client" |
|
||||
import { HttpLink } from "apollo-link-http" |
|
||||
import { InMemoryCache } from "apollo-cache-inmemory" |
|
||||
|
|
||||
const client = new ApolloClient({ |
|
||||
link: new HttpLink({ |
|
||||
uri: "/graphql" |
|
||||
}), |
|
||||
|
|
||||
cache: new InMemoryCache(), |
|
||||
}) |
|
||||
|
|
||||
export default client |
|
@ -1,90 +0,0 @@ |
|||||
/* /story/ */ |
|
||||
.theme-story .color-menu { |
|
||||
color: #789; |
|
||||
} |
|
||||
.theme-story .MenuLink.selected { |
|
||||
background-color: rgba(17, 187, 255, 0.125); |
|
||||
} |
|
||||
.theme-story .MenuLink:hover, .theme-story .Menu > .head-menu > a:hover { |
|
||||
background-color: rgba(17, 187, 255, 0.25); |
|
||||
} |
|
||||
.theme-story .color-primary { |
|
||||
color: #4BF; |
|
||||
} |
|
||||
.theme-story .color-text { |
|
||||
color: #ABC; |
|
||||
} |
|
||||
.theme-story .color-highlight-primary { |
|
||||
background-color: rgba(34, 187, 255, 0.25); |
|
||||
color: #4BF; |
|
||||
} |
|
||||
.theme-story .color-highlight-dark { |
|
||||
background-color: rgba(0, 9, 18, 0.50); |
|
||||
} |
|
||||
|
|
||||
/* /logs/ */ |
|
||||
.theme-logs .color-menu { |
|
||||
color: #998; |
|
||||
} |
|
||||
.theme-logs .MenuLink.selected { |
|
||||
background-color: rgba(255, 187, 17, 0.125); |
|
||||
} |
|
||||
.theme-logs .MenuLink:hover, .theme-logs .Menu > .head-menu > a:hover { |
|
||||
background-color: rgba(255, 187, 17, 0.25); |
|
||||
} |
|
||||
.theme-logs .color-primary { |
|
||||
color: #FB1; |
|
||||
} |
|
||||
.theme-logs .color-text { |
|
||||
color: #CCB; |
|
||||
} |
|
||||
.theme-logs .color-highlight-primary { |
|
||||
background-color: rgba(255, 187, 15, 0.25); |
|
||||
color: #FB1; |
|
||||
} |
|
||||
.theme-logs .color-highlight-dark { |
|
||||
background-color: rgba(18, 9, 0, 0.50); |
|
||||
} |
|
||||
|
|
||||
/* /data/ */ |
|
||||
.theme-data .color-menu { |
|
||||
color: #898; |
|
||||
} |
|
||||
.theme-data .MenuLink.selected { |
|
||||
background-color: rgba(35, 255, 128, 0.125); |
|
||||
} |
|
||||
.theme-data .MenuLink:hover, .theme-data .Menu > .head-menu > a:hover { |
|
||||
background-color: rgba(35, 255, 128, 0.25); |
|
||||
} |
|
||||
.theme-data .color-primary { |
|
||||
color: #1F8; |
|
||||
} |
|
||||
.theme-data .color-text { |
|
||||
color: #ACB; |
|
||||
} |
|
||||
|
|
||||
/* Tag colors */ |
|
||||
.color-tag-event { |
|
||||
color: #B77; |
|
||||
} |
|
||||
.color-tag-location { |
|
||||
color: #7B7; |
|
||||
} |
|
||||
.color-tag-organization { |
|
||||
color: #B7B; |
|
||||
} |
|
||||
.color-tag-series { |
|
||||
color: #BB7; |
|
||||
} |
|
||||
.color-tag-character { |
|
||||
color: #37B; |
|
||||
} |
|
||||
|
|
||||
/* Danger */ |
|
||||
.color-danger { |
|
||||
color: #FA3; |
|
||||
} |
|
||||
|
|
||||
.MenuLink:hover, .MenuLink:hover .text, .Menu > .head-menu > a:hover { |
|
||||
color: #EEE !important; |
|
||||
} |
|
@ -1,58 +0,0 @@ |
|||||
import React, { Component } from "react" |
|
||||
import "./css/Background.css" |
|
||||
|
|
||||
/** |
|
||||
* Background renders a full-size image that detects whether the source is |
|
||||
* landscape or portrait. It's a react-ification of AiteStory's background. |
|
||||
*/ |
|
||||
export default class Background extends Component { |
|
||||
constructor(props, ctx) { |
|
||||
super(props, ctx) |
|
||||
|
|
||||
this.state = { |
|
||||
landscape: false, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* @param {HTMLImageElement} img |
|
||||
*/ |
|
||||
onRef(img) { |
|
||||
if (img == null) { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
if (img.complete && typeof(img.naturalHeight) !== 'undefined' && img.naturalHeight !== 0) { |
|
||||
const width = img.naturalWidth |
|
||||
const height = img.naturalHeight |
|
||||
|
|
||||
this.setLandscape(width > (height * 1.50)) |
|
||||
} else { |
|
||||
img.onload = () => { |
|
||||
const width = img.naturalWidth |
|
||||
const height = img.naturalHeight |
|
||||
|
|
||||
this.setLandscape(width > (height * 1.50)) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Change the landscape setting of the image. |
|
||||
* |
|
||||
* @param {boolean} value Whether the image is landscape |
|
||||
*/ |
|
||||
setLandscape(value) { |
|
||||
if (value !== this.state.landscape) { |
|
||||
this.setState({landscape: value}) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
render() { |
|
||||
const src = this.props.src || "/assets/images/bg.png" |
|
||||
const opacity = this.props.opacity || 0.20 |
|
||||
const className = "Background" + (this.state.landscape ? " landscape" : "") |
|
||||
|
|
||||
return <img alt="Background" style={{filter: `brightness(${(Number(opacity)) * 100}%)`}} ref={this.onRef.bind(this)} className={className} src={src} /> |
|
||||
} |
|
||||
} |
|
@ -1,70 +0,0 @@ |
|||||
import React, { Component } from "react" |
|
||||
import { Link } from "react-router-dom" |
|
||||
import moment from "moment" |
|
||||
|
|
||||
import "./css/List.css" |
|
||||
|
|
||||
export default class List extends Component { |
|
||||
render() { |
|
||||
return ( |
|
||||
<table className="List" cellSpacing="0px"> |
|
||||
<tbody> |
|
||||
{this.props.children} |
|
||||
</tbody> |
|
||||
</table> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export function Item({link, name, icon, author, description, tags, fictionalDate, createdDate}) { |
|
||||
return [ |
|
||||
<tr className="ListItem color-primary"> |
|
||||
<td className="icon color-highlight-primary">{icon}</td> |
|
||||
<td className="content color-primary color-highlight-dark"> |
|
||||
<div className="title"> |
|
||||
<Link className="color-primary" to={link}>{name}</Link> |
|
||||
</div> |
|
||||
<div className="description color-text">{description}</div> |
|
||||
<div className="meta"> |
|
||||
<ItemMeta key="D2" kind="date" value={createdDate} /> |
|
||||
<ItemMeta key="D1" kind="date" value={fictionalDate} /> |
|
||||
<ItemMeta key="T" kind="tag" value={tags[0] || null} /> |
|
||||
<ItemMeta key="A" kind="author" value={author} /> |
|
||||
</div> |
|
||||
</td> |
|
||||
</tr>, |
|
||||
<tr className="ListItem-spacer"> |
|
||||
<td></td> |
|
||||
</tr> |
|
||||
] |
|
||||
} |
|
||||
|
|
||||
function ItemMeta({kind, value, className}) { |
|
||||
// Hide empty fields
|
|
||||
if (value == null || value === "") { |
|
||||
return null |
|
||||
} |
|
||||
|
|
||||
switch (kind) { |
|
||||
case "date": { |
|
||||
const m = moment(value) |
|
||||
if (m.year() < 2) { |
|
||||
return null |
|
||||
} |
|
||||
|
|
||||
return <div className="date color-menu">{m.format("MMM D, YYYY")}</div> |
|
||||
} |
|
||||
|
|
||||
case "tag": { |
|
||||
return <div className={`tag color-tag-${value.kind.toLowerCase()}`}>{value.name}</div> |
|
||||
} |
|
||||
|
|
||||
case "author": { |
|
||||
return <div className="author color-menu">{value}</div> |
|
||||
} |
|
||||
|
|
||||
default: { |
|
||||
return null; |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,13 +0,0 @@ |
|||||
import React from "react" |
|
||||
import Background from "./Background" |
|
||||
|
|
||||
import './css/LoadingScreen.css' |
|
||||
|
|
||||
export function LoadingScreen({className}) { |
|
||||
return ( |
|
||||
<div className={`Loading ${className}`}> |
|
||||
<Background /> |
|
||||
<div className="text color-primary">Loading...</div> |
|
||||
</div> |
|
||||
) |
|
||||
} |
|
@ -1,15 +0,0 @@ |
|||||
import React, { Component } from "react" |
|
||||
|
|
||||
import './css/Main.css' |
|
||||
|
|
||||
export default class Main extends Component { |
|
||||
render() { |
|
||||
const { children, className } = this.props |
|
||||
|
|
||||
return ( |
|
||||
<main className={`Main ${className}`}> |
|
||||
{ children } |
|
||||
</main> |
|
||||
) |
|
||||
} |
|
||||
} |
|
@ -1,60 +0,0 @@ |
|||||
import React, { Component } from "react" |
|
||||
import { Link } from "react-router-dom" |
|
||||
|
|
||||
import './css/Menu.css' |
|
||||
|
|
||||
export default class Menu extends Component { |
|
||||
render() { |
|
||||
const { children } = this.props |
|
||||
|
|
||||
return ( |
|
||||
<nav className="Menu"> |
|
||||
<h1 className="color-primary">Aite RP</h1> |
|
||||
<div className="head-menu"> |
|
||||
<HeadMenuLink to="/story/">Story</HeadMenuLink> |
|
||||
<HeadMenuLink to="/logs/">Logs</HeadMenuLink> |
|
||||
</div> |
|
||||
<div className="content"> |
|
||||
{ children } |
|
||||
</div> |
|
||||
</nav> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export function MenuLink({ icon, children, tagKind, to, onClick }) { |
|
||||
const isSelected = window.location.pathname === to |
|
||||
const className = isSelected ? "selected" : "not-selected" |
|
||||
const textClass = isSelected ? "color-primary" : `color-tag-${(tagKind||"none").toLowerCase()}` |
|
||||
|
|
||||
const body = ( |
|
||||
<div className={`MenuLink color-menu ${className}`} onClick={onClick}> |
|
||||
<div className="icon color-primary">{icon}</div> |
|
||||
<div className={`text ${textClass}`}>{children}</div> |
|
||||
</div> |
|
||||
) |
|
||||
|
|
||||
if (to == null) { |
|
||||
return body |
|
||||
} else { |
|
||||
return <Link to={to}>{body}</Link> |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export function MenuGap() { |
|
||||
return <div className="MenuGap"></div> |
|
||||
} |
|
||||
|
|
||||
export function MenuHeader({children}) { |
|
||||
return <div className="MenuHeader color-menu">{children}</div> |
|
||||
} |
|
||||
|
|
||||
export function HeadMenuLink({to, children}) { |
|
||||
const className = (window.location.pathname.startsWith(to) ? "color-primary" : "color-menu") |
|
||||
|
|
||||
return <Link className={className} to={to}>{children}</Link> |
|
||||
} |
|
||||
|
|
||||
export function MenuBlurb({children}) { |
|
||||
return <div className="MenuBlurb">{children}</div> |
|
||||
} |
|
@ -1,22 +0,0 @@ |
|||||
img.Background { |
|
||||
/* Set rules to fill background */ |
|
||||
width: 100%; |
|
||||
/* Set up proportionate scaling */ |
|
||||
min-height: 100%; |
|
||||
height: auto; |
|
||||
/* Set up positioning */ |
|
||||
position: fixed; |
|
||||
top: 0; |
|
||||
left: 0; |
|
||||
z-index: -1; |
|
||||
user-select: none; |
|
||||
-webkit-user-drag: none; |
|
||||
} |
|
||||
|
|
||||
img.Background.landscape { |
|
||||
/* Set rules to fill background */ |
|
||||
height: 100%; |
|
||||
/* Set up proportionate scaling */ |
|
||||
min-width: 100%; |
|
||||
width: auto; |
|
||||
} |
|
@ -1,50 +0,0 @@ |
|||||
.List { |
|
||||
padding: 1em 1ch; |
|
||||
width: 100%; |
|
||||
max-width: 140ch; |
|
||||
margin: auto; |
|
||||
margin-top: 0.75em; |
|
||||
} |
|
||||
|
|
||||
.ListItem { |
|
||||
user-select: none; |
|
||||
outline: 0.05px solid; |
|
||||
} |
|
||||
.ListItem td.icon { |
|
||||
font-size: 2.75em; |
|
||||
text-align: center; |
|
||||
width: 2.5ch; |
|
||||
outline: 0.05px solid; |
|
||||
margin: 0; |
|
||||
} |
|
||||
.ListItem td.content { |
|
||||
padding: 0.25em; |
|
||||
padding-bottom: 0.33em; |
|
||||
margin: 0; |
|
||||
} |
|
||||
.ListItem td.content .title { |
|
||||
padding: 0.25em 1ch; |
|
||||
} |
|
||||
.ListItem td.content a { |
|
||||
font-size: 1.333em; |
|
||||
} |
|
||||
.ListItem td.content a:hover { |
|
||||
color: white; |
|
||||
} |
|
||||
.ListItem td.content .description { |
|
||||
margin: 0 1ch; |
|
||||
} |
|
||||
.ListItem td.content p { |
|
||||
margin: 0; |
|
||||
} |
|
||||
.ListItem td.content .meta { |
|
||||
|
|
||||
} |
|
||||
.ListItem td.content .meta > div { |
|
||||
display: inline-block; |
|
||||
padding: 0.25em 1ch; |
|
||||
} |
|
||||
|
|
||||
.ListItem-spacer td { |
|
||||
height: 0.75em; |
|
||||
} |
|
@ -1,7 +0,0 @@ |
|||||
.Loading .text { |
|
||||
width: 100%; |
|
||||
margin-top: 6em; |
|
||||
font-size: 3em; |
|
||||
opacity: 0.5; |
|
||||
text-align: center; |
|
||||
} |
|
@ -1,3 +0,0 @@ |
|||||
.Main { |
|
||||
margin-left: 30ch; |
|
||||
} |
|
@ -1,75 +0,0 @@ |
|||||
.Menu { |
|
||||
position: fixed; |
|
||||
width: 28ch; |
|
||||
top: 0; |
|
||||
bottom: 0; |
|
||||
overflow-x: hidden; |
|
||||
overflow-y: auto; |
|
||||
text-align: center; |
|
||||
user-select: none; |
|
||||
} |
|
||||
|
|
||||
.Menu > .head-menu { |
|
||||
padding: 0 2ch; |
|
||||
padding-bottom: 0.5em; |
|
||||
} |
|
||||
|
|
||||
.Menu > .head-menu > a { |
|
||||
display: inline-block; |
|
||||
padding: 0.25em 1ch; |
|
||||
|
|
||||
font-size: 0.75em; |
|
||||
} |
|
||||
|
|
||||
.Menu > h1 { |
|
||||
padding: 1em 0 0em 0; |
|
||||
margin: 0; |
|
||||
font-size: 2.5em; |
|
||||
|
|
||||
user-select: none; |
|
||||
} |
|
||||
|
|
||||
.Menu > .content { |
|
||||
margin-top: 1em; |
|
||||
text-align: left; |
|
||||
} |
|
||||
|
|
||||
.MenuLink { |
|
||||
width: 100%; |
|
||||
padding-left: 0.5ch; |
|
||||
font-size: 1.1em; |
|
||||
|
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
.MenuLink .icon { |
|
||||
display: inline-block; |
|
||||
width: 3ch; |
|
||||
padding: 0.15em 0.125em; |
|
||||
text-align: center; |
|
||||
vertical-align: middle; |
|
||||
} |
|
||||
|
|
||||
.MenuLink .text { |
|
||||
display: inline-block; |
|
||||
max-width: 22ch; |
|
||||
white-space: nowrap; |
|
||||
overflow-x: hidden; |
|
||||
text-overflow: ellipsis; |
|
||||
vertical-align: middle; |
|
||||
} |
|
||||
|
|
||||
.MenuGap { |
|
||||
padding: 0.25em; |
|
||||
} |
|
||||
|
|
||||
.MenuHeader { |
|
||||
opacity: 0.5; |
|
||||
user-select: none; |
|
||||
padding: 0.125em 1.5ch; |
|
||||
} |
|
||||
|
|
||||
.MenuBlurb { |
|
||||
opacity: 0.5; |
|
||||
padding: 0.25em 1.5ch; |
|
||||
} |
|
@ -1,33 +0,0 @@ |
|||||
body { |
|
||||
margin: 0; |
|
||||
padding: 0; |
|
||||
font-family: sans-serif; |
|
||||
background-color: black; |
|
||||
color: white; |
|
||||
} |
|
||||
|
|
||||
a { |
|
||||
text-decoration: none; |
|
||||
} |
|
||||
|
|
||||
/* |
|
||||
* Less glaring chrome/ium scrollbars. |
|
||||
*/ |
|
||||
::-webkit-scrollbar { |
|
||||
width: 2px; |
|
||||
/* for vertical scrollbars */ |
|
||||
height: 2px; |
|
||||
/* for horizontal scrollbars */ |
|
||||
} |
|
||||
::-webkit-scrollbar-button { |
|
||||
background-color: #555; |
|
||||
color: 000; |
|
||||
display: none; |
|
||||
} |
|
||||
::-webkit-scrollbar-track { |
|
||||
background-color: #222; |
|
||||
} |
|
||||
::-webkit-scrollbar-thumb { |
|
||||
background-color: #555; |
|
||||
color: #000; |
|
||||
} |
|
@ -1,21 +0,0 @@ |
|||||
import React from "react"; |
|
||||
import ReactDOM from "react-dom"; |
|
||||
import { BrowserRouter } from "react-router-dom" |
|
||||
import { ApolloProvider } from "react-apollo"; |
|
||||
|
|
||||
import client from "./client"; |
|
||||
import App from "./App" |
|
||||
import registerServiceWorker from "./registerServiceWorker" |
|
||||
|
|
||||
import "./index.css" |
|
||||
import "./colors.css"; |
|
||||
|
|
||||
ReactDOM.render(( |
|
||||
<ApolloProvider client={client}> |
|
||||
<BrowserRouter> |
|
||||
<App /> |
|
||||
</BrowserRouter> |
|
||||
</ApolloProvider> |
|
||||
), document.getElementById('root')) |
|
||||
|
|
||||
registerServiceWorker(); |
|
@ -1,117 +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); |
|
||||
|
|
||||
// Add some additional logging to localhost, pointing developers to the
|
|
||||
// service worker/PWA documentation.
|
|
||||
navigator.serviceWorker.ready.then(() => { |
|
||||
console.log( |
|
||||
'This web app is being served cache-first by a service ' + |
|
||||
'worker. To learn more, visit https://goo.gl/SC7cgQ' |
|
||||
); |
|
||||
}); |
|
||||
} 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,16 +0,0 @@ |
|||||
import React, { Component } from "react" |
|
||||
import Menu, { MenuHeader, MenuLink, MenuGap } from '../../common/Menu' |
|
||||
|
|
||||
export default class LogsMenu extends Component { |
|
||||
render() { |
|
||||
return ( |
|
||||
<Menu> |
|
||||
<MenuHeader>Logs</MenuHeader> |
|
||||
<MenuLink to="/logs/" icon="R">Recent</MenuLink> |
|
||||
<MenuGap /> |
|
||||
<MenuHeader>Options</MenuHeader> |
|
||||
<MenuLink icon="+">Create Log</MenuLink> |
|
||||
</Menu> |
|
||||
) |
|
||||
} |
|
||||
} |
|
@ -1,14 +0,0 @@ |
|||||
import React, { Component } from "react" |
|
||||
import Background from "../../common/Background" |
|
||||
import LogsMenu from "./LogsMenu" |
|
||||
|
|
||||
export default class LogsRoot extends Component { |
|
||||
render() { |
|
||||
return ( |
|
||||
<div className="LogsRoot theme-logs"> |
|
||||
<Background /> |
|
||||
<LogsMenu /> |
|
||||
</div> |
|
||||
) |
|
||||
} |
|
||||
} |
|
@ -1,51 +0,0 @@ |
|||||
import React, { Component } from "react" |
|
||||
import moment from "moment" |
|
||||
|
|
||||
import Menu, { MenuHeader, MenuLink, MenuGap } from '../../common/Menu' |
|
||||
|
|
||||
export default class StoryContentMenu extends Component { |
|
||||
render() { |
|
||||
const { story } = this.props |
|
||||
|
|
||||
console.log(story.tags) |
|
||||
|
|
||||
return ( |
|
||||
<Menu> |
|
||||
<MenuHeader>Chapters</MenuHeader> |
|
||||
<ChapterMenu storyId={story.id} chapters={story.chapters} /> |
|
||||
<MenuGap /> |
|
||||
<MenuHeader>Tags</MenuHeader> |
|
||||
<TagMenu tags={story.tags} /> |
|
||||
<MenuGap /> |
|
||||
<MenuHeader>Story</MenuHeader> |
|
||||
<MonthMenuItem date={story.fictionalDate} /> |
|
||||
<MenuLink to={`/story/author/${story.author}`} icon="A">{story.author}</MenuLink> |
|
||||
<MenuGap /> |
|
||||
</Menu> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function ChapterMenu({chapters, storyId}) { |
|
||||
return chapters.map((chapter, index) => ( |
|
||||
<MenuLink to={`/story/${storyId}/${chapter.id}`} icon={index + 1}>{chapter.title}</MenuLink> |
|
||||
)) |
|
||||
} |
|
||||
|
|
||||
function TagMenu({tags}) { |
|
||||
return tags.map(tag => ( |
|
||||
<MenuLink tagKind={tag.kind} to={`/story/tag/${tag.kind}/${tag.name}`} icon={tag.kind.charAt(0)}>{tag.name}</MenuLink> |
|
||||
)) |
|
||||
} |
|
||||
|
|
||||
function MonthMenuItem({date}) { |
|
||||
const fictionalDate = moment.utc(date) |
|
||||
if (fictionalDate.year() < 100) { |
|
||||
return null |
|
||||
} |
|
||||
|
|
||||
const monthKey = fictionalDate.format("YYYY-MM") |
|
||||
const monthText = fictionalDate.format("MMM D, YYYY") |
|
||||
|
|
||||
return <MenuLink to={`/story/month/${monthKey}`} icon="D">{monthText}</MenuLink> |
|
||||
} |
|
@ -1,44 +0,0 @@ |
|||||
import React, { Component } from "react" |
|
||||
import { Route, Switch, Redirect } from "react-router-dom"; |
|
||||
import { Query } from "react-apollo" |
|
||||
|
|
||||
import Background from "../../common/Background" |
|
||||
import { LoadingScreen } from "../../common/LoadingScreen" |
|
||||
import StoryContentMenu from "./StoryContentMenu" |
|
||||
|
|
||||
import storyContentRoot from "./gql/StoryContentRoot"; |
|
||||
|
|
||||
class StoryContentRoot extends Component { |
|
||||
render() { |
|
||||
const story = this.props.story |
|
||||
|
|
||||
const chapterRedirect = story.chapters.length > 0 ? ( |
|
||||
<Redirect exact from={`/story/${story.id}/`} to={`/story/${story.id}/${story.chapters[0].id}`} /> |
|
||||
) : null |
|
||||
|
|
||||
return ( |
|
||||
<div className="StoryContentRoot theme-story"> |
|
||||
<Background /> |
|
||||
<StoryContentMenu story={this.props.story} /> |
|
||||
<Switch> |
|
||||
{chapterRedirect} |
|
||||
</Switch> |
|
||||
</div> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default ({id, match}) => ( |
|
||||
<Query query={storyContentRoot} variables={{id: id || match.params.id}}> |
|
||||
{({ loading, error, data }) => { |
|
||||
if (loading) { |
|
||||
return <LoadingScreen className="theme-story" /> |
|
||||
} |
|
||||
if (error) { |
|
||||
return `Error! ${error.message}` |
|
||||
} |
|
||||
|
|
||||
return <StoryContentRoot {...data} /> |
|
||||
}} |
|
||||
</Query> |
|
||||
) |
|
@ -1,28 +0,0 @@ |
|||||
import gql from "graphql-tag" |
|
||||
|
|
||||
export default gql`
|
|
||||
query StoryContentRoot($id: String!) { |
|
||||
story(id: $id) { |
|
||||
id |
|
||||
name |
|
||||
author |
|
||||
category |
|
||||
createdDate |
|
||||
fictionalDate |
|
||||
updatedDate |
|
||||
tags { |
|
||||
kind |
|
||||
name |
|
||||
} |
|
||||
chapters { |
|
||||
id |
|
||||
title |
|
||||
author |
|
||||
source |
|
||||
createdDate |
|
||||
fictionalDate |
|
||||
editedDate |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
`
|
|
@ -1,36 +0,0 @@ |
|||||
import React, { Component } from "react" |
|
||||
import { Query } from "react-apollo" |
|
||||
|
|
||||
import { LoadingScreen } from "../../common/LoadingScreen" |
|
||||
import Main from "../../common/Main" |
|
||||
import List, {Item} from "../../common/List" |
|
||||
|
|
||||
import storyListQuery from "./gql/StoryList"; |
|
||||
|
|
||||
export class StoryList extends Component { |
|
||||
render() { |
|
||||
const { stories } = this.props |
|
||||
const items = stories.map(story => <Item key={story.id} link={`/story/${story.id}`} icon={story.category.charAt(0)} {...story} />) |
|
||||
|
|
||||
return ( |
|
||||
<Main className="StoryList"> |
|
||||
<List>{items}</List> |
|
||||
</Main> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default ({filter}) => ( |
|
||||
<Query query={storyListQuery} variables={{filter}}> |
|
||||
{({ loading, error, data }) => { |
|
||||
if (loading) { |
|
||||
return <LoadingScreen className="theme-story" /> |
|
||||
} |
|
||||
if (error) { |
|
||||
return `Error! ${error.message}` |
|
||||
} |
|
||||
|
|
||||
return <StoryList {...data} /> |
|
||||
}} |
|
||||
</Query> |
|
||||
) |
|
@ -1,33 +0,0 @@ |
|||||
import React, { Component } from "react" |
|
||||
import Menu, { MenuHeader, MenuLink, MenuGap } from "../../common/Menu" |
|
||||
import pluralize from "pluralize" |
|
||||
|
|
||||
export default class StoryMenu extends Component { |
|
||||
render() { |
|
||||
return ( |
|
||||
<Menu> |
|
||||
<MenuHeader>Story</MenuHeader> |
|
||||
<MenuLink to="/story/" icon="A">All</MenuLink> |
|
||||
<MenuLink to="/story/open" icon="O">Open</MenuLink> |
|
||||
<MenuGap /> |
|
||||
<MenuHeader>By Category</MenuHeader> |
|
||||
<CategoryLinks categories={this.props.categories} /> |
|
||||
<MenuGap /> |
|
||||
<MenuHeader>By Tag</MenuHeader> |
|
||||
<MenuLink to="/story/tags/" icon="T">All Tags</MenuLink> |
|
||||
<MenuGap /> |
|
||||
<MenuHeader>Options</MenuHeader> |
|
||||
<MenuLink icon="+">Create Story</MenuLink> |
|
||||
</Menu> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
function CategoryLinks({categories}) { |
|
||||
return categories.map(category => { |
|
||||
const icon = category.charAt(0).toUpperCase() |
|
||||
const label = pluralize(category) |
|
||||
|
|
||||
return <MenuLink to={`/story/category/${category}`} icon={icon}>{label}</MenuLink> |
|
||||
}) |
|
||||
} |
|
@ -1,43 +0,0 @@ |
|||||
import React, { Component } from "react" |
|
||||
import { Route, Switch } from "react-router-dom"; |
|
||||
import { Query } from "react-apollo" |
|
||||
|
|
||||
import StoryMenu from "./StoryMenu" |
|
||||
import Background from "../../common/Background" |
|
||||
import { LoadingScreen } from "../../common/LoadingScreen" |
|
||||
|
|
||||
import storyRootQuery from "./gql/StoryRoot" |
|
||||
|
|
||||
import StoryList from "./StoryList"; |
|
||||
import StoryTags from "./StoryTags"; |
|
||||
|
|
||||
export class StoryIndex extends Component { |
|
||||
render() { |
|
||||
const categories = this.props.categoryType.enumValues.map(ev => ev.name).sort() |
|
||||
|
|
||||
return ( |
|
||||
<div className="StoryIndex theme-story"> |
|
||||
<Background /> |
|
||||
<StoryMenu categories={categories} /> |
|
||||
<Switch> |
|
||||
<Route exact path="/story/" component={() => <StoryList filter={{}} />} /> |
|
||||
<Route exact path="/story/open/" component={() => <StoryList filter={{open: true}} />} /> |
|
||||
<Route exact path="/story/tags/" component={StoryTags} /> |
|
||||
<Route exact path="/story/tag/:kind/:name/" component={({match}) => <StoryList filter={{tags: [match.params]}} />} /> |
|
||||
<Route exact path="/story/category/:category/" component={({match: {params: {category}}}) => <StoryList filter={{category}} />} /> |
|
||||
</Switch> |
|
||||
</div> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default () => ( |
|
||||
<Query query={storyRootQuery}> |
|
||||
{({ loading, error, data }) => { |
|
||||
if (loading) return <LoadingScreen className="theme-story" /> |
|
||||
if (error) return `Error! ${error.message}` |
|
||||
|
|
||||
return <StoryIndex {...data} /> |
|
||||
}} |
|
||||
</Query> |
|
||||
) |
|
@ -1,34 +0,0 @@ |
|||||
import React, { Component } from "react" |
|
||||
import { Query } from "react-apollo" |
|
||||
|
|
||||
import { LoadingScreen } from "../../common/LoadingScreen" |
|
||||
import Main from "../../common/Main" |
|
||||
|
|
||||
import storyTagsQuery from "./gql/StoryTags"; |
|
||||
|
|
||||
export class StoryTags extends Component { |
|
||||
render() { |
|
||||
const { tags } = this.props |
|
||||
|
|
||||
return ( |
|
||||
<Main className="StoryTags"> |
|
||||
{ tags.map(s => <h1>{s.name}</h1>) } |
|
||||
</Main> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default () => ( |
|
||||
<Query query={storyTagsQuery}> |
|
||||
{({ loading, error, data }) => { |
|
||||
if (loading) { |
|
||||
return <LoadingScreen className="theme-story" /> |
|
||||
} |
|
||||
if (error) { |
|
||||
return `Error! ${error.message}` |
|
||||
} |
|
||||
|
|
||||
return <StoryTags {...data} /> |
|
||||
}} |
|
||||
</Query> |
|
||||
) |
|
@ -1,16 +0,0 @@ |
|||||
import gql from "graphql-tag" |
|
||||
|
|
||||
export default gql`
|
|
||||
query StoryList($filter: StoriesFilter) { |
|
||||
stories(filter: $filter) { |
|
||||
id |
|
||||
name |
|
||||
author |
|
||||
category |
|
||||
tags { kind name } |
|
||||
createdDate |
|
||||
fictionalDate |
|
||||
updatedDate |
|
||||
} |
|
||||
} |
|
||||
`
|
|
@ -1,12 +0,0 @@ |
|||||
import gql from "graphql-tag" |
|
||||
|
|
||||
export default gql`
|
|
||||
query StoryRoot { |
|
||||
categoryType: __type(name: "StoryCategory") { |
|
||||
enumValues { |
|
||||
name |
|
||||
description |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
`
|
|
@ -1,10 +0,0 @@ |
|||||
import gql from "graphql-tag" |
|
||||
|
|
||||
export default gql`
|
|
||||
query StoryTags { |
|
||||
tags { |
|
||||
kind |
|
||||
name |
|
||||
} |
|
||||
} |
|
||||
`
|
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue