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