From a17b76ac75f506f5da6fe8adf9c36632b60d4226 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Sat, 26 Sep 2020 14:56:02 +0200 Subject: refactor to use app-rooted js imports --- .babelrc | 9 +- .eslintrc.js | 6 +- frontend/actions.js | 18 - frontend/api/crud.actions.js | 51 -- frontend/api/crud.fetch.js | 105 ---- frontend/api/crud.reducer.js | 196 ------- frontend/api/crud.types.js | 36 -- frontend/api/crud.upload.js | 107 ---- frontend/api/index.js | 24 - frontend/app.js | 44 -- frontend/app/actions.js | 18 + frontend/app/api/crud.actions.js | 51 ++ frontend/app/api/crud.fetch.js | 105 ++++ frontend/app/api/crud.reducer.js | 196 +++++++ frontend/app/api/crud.types.js | 36 ++ frontend/app/api/crud.upload.js | 107 ++++ frontend/app/api/index.js | 24 + frontend/app/app.js | 44 ++ frontend/app/common/app.css | 428 ++++++++++++++ .../app/common/copyToClipboardButton.component.js | 24 + frontend/app/common/fonts.css | 55 ++ frontend/app/common/form.component.js | 220 +++++++ frontend/app/common/form.css | 323 +++++++++++ frontend/app/common/header.component.js | 41 ++ frontend/app/common/imageCrop.component.js | 41 ++ frontend/app/common/index.js | 32 + frontend/app/common/loader.component.js | 16 + frontend/app/common/loader.css | 125 ++++ frontend/app/common/menubutton.component.js | 128 ++++ frontend/app/common/miscellaneous.component.js | 71 +++ frontend/app/common/miscellaneous.css | 18 + frontend/app/common/modal.component.js | 9 + frontend/app/common/modal.css | 20 + frontend/app/common/slider.component.js | 120 ++++ frontend/app/common/table.component.js | 128 ++++ frontend/app/common/table.css | 96 +++ frontend/app/common/tableIndex.component.js | 129 +++++ frontend/app/common/upload.css | 26 + frontend/app/common/upload.helpers.js | 174 ++++++ frontend/app/common/uploadImage.component.js | 74 +++ frontend/app/session.js | 19 + frontend/app/site/actions.js | 19 + frontend/app/site/app.js | 33 ++ frontend/app/site/index.js | 17 + frontend/app/site/site/site.actions.js | 11 + frontend/app/site/site/site.reducer.js | 36 ++ frontend/app/site/store.js | 37 ++ frontend/app/site/types.js | 11 + frontend/app/site/viewer/viewer.container.js | 96 +++ frontend/app/store.js | 45 ++ frontend/app/types.js | 33 ++ frontend/app/utils/index.js | 331 +++++++++++ .../app/views/graph/components/graph.canvas.js | 138 +++++ .../app/views/graph/components/graph.editor.js | 212 +++++++ .../app/views/graph/components/graph.header.js | 31 + frontend/app/views/graph/components/page.edit.js | 65 +++ frontend/app/views/graph/components/page.form.js | 185 ++++++ frontend/app/views/graph/components/page.handle.js | 59 ++ frontend/app/views/graph/components/page.new.js | 47 ++ frontend/app/views/graph/graph.actions.js | 37 ++ frontend/app/views/graph/graph.container.js | 82 +++ frontend/app/views/graph/graph.css | 172 ++++++ frontend/app/views/graph/graph.reducer.js | 100 ++++ frontend/app/views/index.js | 4 + frontend/app/views/index/components/graph.form.js | 153 +++++ frontend/app/views/index/containers/graph.edit.js | 53 ++ frontend/app/views/index/containers/graph.index.js | 53 ++ frontend/app/views/index/containers/graph.new.js | 44 ++ frontend/app/views/index/index.container.js | 36 ++ frontend/app/views/index/index.css | 38 ++ frontend/app/views/page/components/page.editor.js | 215 +++++++ frontend/app/views/page/components/page.header.js | 36 ++ frontend/app/views/page/components/tile.edit.js | 84 +++ frontend/app/views/page/components/tile.form.js | 645 +++++++++++++++++++++ frontend/app/views/page/components/tile.handle.js | 139 +++++ frontend/app/views/page/components/tile.list.js | 142 +++++ frontend/app/views/page/components/tile.new.js | 55 ++ frontend/app/views/page/cursors.css | 21 + frontend/app/views/page/page.actions.js | 110 ++++ frontend/app/views/page/page.container.js | 95 +++ frontend/app/views/page/page.css | 167 ++++++ frontend/app/views/page/page.reducer.js | 202 +++++++ frontend/app/views/site/site.actions.js | 6 + frontend/app/views/site/site.reducer.js | 19 + frontend/app/views/tile/tile.actions.js | 9 + frontend/app/views/tile/tile.reducer.js | 31 + .../app/views/upload/components/upload.form.js | 16 + .../app/views/upload/components/upload.index.js | 104 ++++ .../views/upload/components/upload.indexOptions.js | 62 ++ .../app/views/upload/components/upload.menu.js | 18 + .../app/views/upload/components/upload.show.js | 70 +++ frontend/app/views/upload/upload.actions.js | 14 + frontend/app/views/upload/upload.container.js | 35 ++ frontend/app/views/upload/upload.css | 10 + frontend/app/views/upload/upload.reducer.js | 21 + frontend/common/app.css | 428 -------------- frontend/common/copyToClipboardButton.component.js | 24 - frontend/common/fonts.css | 55 -- frontend/common/form.component.js | 220 ------- frontend/common/form.css | 323 ----------- frontend/common/header.component.js | 41 -- frontend/common/imageCrop.component.js | 41 -- frontend/common/index.js | 32 - frontend/common/loader.component.js | 16 - frontend/common/loader.css | 125 ---- frontend/common/menubutton.component.js | 128 ---- frontend/common/miscellaneous.component.js | 71 --- frontend/common/miscellaneous.css | 18 - frontend/common/modal.component.js | 9 - frontend/common/modal.css | 20 - frontend/common/slider.component.js | 120 ---- frontend/common/table.component.js | 128 ---- frontend/common/table.css | 96 --- frontend/common/tableIndex.component.js | 129 ----- frontend/common/upload.css | 26 - frontend/common/upload.helpers.js | 174 ------ frontend/common/uploadImage.component.js | 74 --- frontend/index.js | 6 +- frontend/session.js | 19 - frontend/site/actions.js | 19 - frontend/site/app.js | 33 -- frontend/site/index.js | 17 - frontend/site/site/site.actions.js | 24 - frontend/site/site/site.reducer.js | 37 -- frontend/site/store.js | 38 -- frontend/site/types.js | 11 - frontend/site/viewer/viewer.container.js | 96 --- frontend/store.js | 50 -- frontend/types.js | 33 -- frontend/util/index.js | 331 ----------- frontend/views/graph/components/graph.canvas.js | 138 ----- frontend/views/graph/components/graph.editor.js | 212 ------- frontend/views/graph/components/graph.header.js | 31 - frontend/views/graph/components/page.edit.js | 65 --- frontend/views/graph/components/page.form.js | 185 ------ frontend/views/graph/components/page.handle.js | 59 -- frontend/views/graph/components/page.new.js | 47 -- frontend/views/graph/graph.actions.js | 37 -- frontend/views/graph/graph.container.js | 82 --- frontend/views/graph/graph.css | 172 ------ frontend/views/graph/graph.reducer.js | 100 ---- frontend/views/index.js | 4 - frontend/views/index/components/graph.form.js | 153 ----- frontend/views/index/containers/graph.edit.js | 53 -- frontend/views/index/containers/graph.index.js | 53 -- frontend/views/index/containers/graph.new.js | 44 -- frontend/views/index/index.container.js | 36 -- frontend/views/index/index.css | 38 -- frontend/views/page/components/page.editor.js | 215 ------- frontend/views/page/components/page.header.js | 36 -- frontend/views/page/components/tile.edit.js | 84 --- frontend/views/page/components/tile.form.js | 645 --------------------- frontend/views/page/components/tile.handle.js | 139 ----- frontend/views/page/components/tile.list.js | 142 ----- frontend/views/page/components/tile.new.js | 55 -- frontend/views/page/cursors.css | 21 - frontend/views/page/page.actions.js | 110 ---- frontend/views/page/page.container.js | 95 --- frontend/views/page/page.css | 167 ------ frontend/views/page/page.reducer.js | 202 ------- frontend/views/site/site.actions.js | 8 - frontend/views/site/site.reducer.js | 20 - frontend/views/tile/tile.actions.js | 10 - frontend/views/tile/tile.reducer.js | 32 - frontend/views/upload/components/upload.form.js | 16 - frontend/views/upload/components/upload.index.js | 104 ---- .../views/upload/components/upload.indexOptions.js | 62 -- frontend/views/upload/components/upload.menu.js | 18 - frontend/views/upload/components/upload.show.js | 70 --- frontend/views/upload/upload.actions.js | 17 - frontend/views/upload/upload.container.js | 37 -- frontend/views/upload/upload.css | 10 - frontend/views/upload/upload.reducer.js | 21 - 173 files changed, 7417 insertions(+), 7438 deletions(-) delete mode 100644 frontend/actions.js delete mode 100644 frontend/api/crud.actions.js delete mode 100644 frontend/api/crud.fetch.js delete mode 100644 frontend/api/crud.reducer.js delete mode 100644 frontend/api/crud.types.js delete mode 100644 frontend/api/crud.upload.js delete mode 100644 frontend/api/index.js delete mode 100644 frontend/app.js create mode 100644 frontend/app/actions.js create mode 100644 frontend/app/api/crud.actions.js create mode 100644 frontend/app/api/crud.fetch.js create mode 100644 frontend/app/api/crud.reducer.js create mode 100644 frontend/app/api/crud.types.js create mode 100644 frontend/app/api/crud.upload.js create mode 100644 frontend/app/api/index.js create mode 100644 frontend/app/app.js create mode 100644 frontend/app/common/app.css create mode 100644 frontend/app/common/copyToClipboardButton.component.js create mode 100644 frontend/app/common/fonts.css create mode 100644 frontend/app/common/form.component.js create mode 100644 frontend/app/common/form.css create mode 100644 frontend/app/common/header.component.js create mode 100644 frontend/app/common/imageCrop.component.js create mode 100644 frontend/app/common/index.js create mode 100644 frontend/app/common/loader.component.js create mode 100644 frontend/app/common/loader.css create mode 100644 frontend/app/common/menubutton.component.js create mode 100644 frontend/app/common/miscellaneous.component.js create mode 100644 frontend/app/common/miscellaneous.css create mode 100644 frontend/app/common/modal.component.js create mode 100644 frontend/app/common/modal.css create mode 100644 frontend/app/common/slider.component.js create mode 100644 frontend/app/common/table.component.js create mode 100644 frontend/app/common/table.css create mode 100644 frontend/app/common/tableIndex.component.js create mode 100644 frontend/app/common/upload.css create mode 100644 frontend/app/common/upload.helpers.js create mode 100644 frontend/app/common/uploadImage.component.js create mode 100644 frontend/app/session.js create mode 100644 frontend/app/site/actions.js create mode 100644 frontend/app/site/app.js create mode 100644 frontend/app/site/index.js create mode 100644 frontend/app/site/site/site.actions.js create mode 100644 frontend/app/site/site/site.reducer.js create mode 100644 frontend/app/site/store.js create mode 100644 frontend/app/site/types.js create mode 100644 frontend/app/site/viewer/viewer.container.js create mode 100644 frontend/app/store.js create mode 100644 frontend/app/types.js create mode 100644 frontend/app/utils/index.js create mode 100644 frontend/app/views/graph/components/graph.canvas.js create mode 100644 frontend/app/views/graph/components/graph.editor.js create mode 100644 frontend/app/views/graph/components/graph.header.js create mode 100644 frontend/app/views/graph/components/page.edit.js create mode 100644 frontend/app/views/graph/components/page.form.js create mode 100644 frontend/app/views/graph/components/page.handle.js create mode 100644 frontend/app/views/graph/components/page.new.js create mode 100644 frontend/app/views/graph/graph.actions.js create mode 100644 frontend/app/views/graph/graph.container.js create mode 100644 frontend/app/views/graph/graph.css create mode 100644 frontend/app/views/graph/graph.reducer.js create mode 100644 frontend/app/views/index.js create mode 100644 frontend/app/views/index/components/graph.form.js create mode 100644 frontend/app/views/index/containers/graph.edit.js create mode 100644 frontend/app/views/index/containers/graph.index.js create mode 100644 frontend/app/views/index/containers/graph.new.js create mode 100644 frontend/app/views/index/index.container.js create mode 100644 frontend/app/views/index/index.css create mode 100644 frontend/app/views/page/components/page.editor.js create mode 100644 frontend/app/views/page/components/page.header.js create mode 100644 frontend/app/views/page/components/tile.edit.js create mode 100644 frontend/app/views/page/components/tile.form.js create mode 100644 frontend/app/views/page/components/tile.handle.js create mode 100644 frontend/app/views/page/components/tile.list.js create mode 100644 frontend/app/views/page/components/tile.new.js create mode 100644 frontend/app/views/page/cursors.css create mode 100644 frontend/app/views/page/page.actions.js create mode 100644 frontend/app/views/page/page.container.js create mode 100644 frontend/app/views/page/page.css create mode 100644 frontend/app/views/page/page.reducer.js create mode 100644 frontend/app/views/site/site.actions.js create mode 100644 frontend/app/views/site/site.reducer.js create mode 100644 frontend/app/views/tile/tile.actions.js create mode 100644 frontend/app/views/tile/tile.reducer.js create mode 100644 frontend/app/views/upload/components/upload.form.js create mode 100644 frontend/app/views/upload/components/upload.index.js create mode 100644 frontend/app/views/upload/components/upload.indexOptions.js create mode 100644 frontend/app/views/upload/components/upload.menu.js create mode 100644 frontend/app/views/upload/components/upload.show.js create mode 100644 frontend/app/views/upload/upload.actions.js create mode 100644 frontend/app/views/upload/upload.container.js create mode 100644 frontend/app/views/upload/upload.css create mode 100644 frontend/app/views/upload/upload.reducer.js delete mode 100644 frontend/common/app.css delete mode 100644 frontend/common/copyToClipboardButton.component.js delete mode 100644 frontend/common/fonts.css delete mode 100644 frontend/common/form.component.js delete mode 100644 frontend/common/form.css delete mode 100644 frontend/common/header.component.js delete mode 100644 frontend/common/imageCrop.component.js delete mode 100644 frontend/common/index.js delete mode 100644 frontend/common/loader.component.js delete mode 100644 frontend/common/loader.css delete mode 100644 frontend/common/menubutton.component.js delete mode 100644 frontend/common/miscellaneous.component.js delete mode 100644 frontend/common/miscellaneous.css delete mode 100644 frontend/common/modal.component.js delete mode 100644 frontend/common/modal.css delete mode 100644 frontend/common/slider.component.js delete mode 100644 frontend/common/table.component.js delete mode 100644 frontend/common/table.css delete mode 100644 frontend/common/tableIndex.component.js delete mode 100644 frontend/common/upload.css delete mode 100644 frontend/common/upload.helpers.js delete mode 100644 frontend/common/uploadImage.component.js delete mode 100644 frontend/session.js delete mode 100644 frontend/site/actions.js delete mode 100644 frontend/site/app.js delete mode 100644 frontend/site/index.js delete mode 100644 frontend/site/site/site.actions.js delete mode 100644 frontend/site/site/site.reducer.js delete mode 100644 frontend/site/store.js delete mode 100644 frontend/site/types.js delete mode 100644 frontend/site/viewer/viewer.container.js delete mode 100644 frontend/store.js delete mode 100644 frontend/types.js delete mode 100644 frontend/util/index.js delete mode 100644 frontend/views/graph/components/graph.canvas.js delete mode 100644 frontend/views/graph/components/graph.editor.js delete mode 100644 frontend/views/graph/components/graph.header.js delete mode 100644 frontend/views/graph/components/page.edit.js delete mode 100644 frontend/views/graph/components/page.form.js delete mode 100644 frontend/views/graph/components/page.handle.js delete mode 100644 frontend/views/graph/components/page.new.js delete mode 100644 frontend/views/graph/graph.actions.js delete mode 100644 frontend/views/graph/graph.container.js delete mode 100644 frontend/views/graph/graph.css delete mode 100644 frontend/views/graph/graph.reducer.js delete mode 100644 frontend/views/index.js delete mode 100644 frontend/views/index/components/graph.form.js delete mode 100644 frontend/views/index/containers/graph.edit.js delete mode 100644 frontend/views/index/containers/graph.index.js delete mode 100644 frontend/views/index/containers/graph.new.js delete mode 100644 frontend/views/index/index.container.js delete mode 100644 frontend/views/index/index.css delete mode 100644 frontend/views/page/components/page.editor.js delete mode 100644 frontend/views/page/components/page.header.js delete mode 100644 frontend/views/page/components/tile.edit.js delete mode 100644 frontend/views/page/components/tile.form.js delete mode 100644 frontend/views/page/components/tile.handle.js delete mode 100644 frontend/views/page/components/tile.list.js delete mode 100644 frontend/views/page/components/tile.new.js delete mode 100644 frontend/views/page/cursors.css delete mode 100644 frontend/views/page/page.actions.js delete mode 100644 frontend/views/page/page.container.js delete mode 100644 frontend/views/page/page.css delete mode 100644 frontend/views/page/page.reducer.js delete mode 100644 frontend/views/site/site.actions.js delete mode 100644 frontend/views/site/site.reducer.js delete mode 100644 frontend/views/tile/tile.actions.js delete mode 100644 frontend/views/tile/tile.reducer.js delete mode 100644 frontend/views/upload/components/upload.form.js delete mode 100644 frontend/views/upload/components/upload.index.js delete mode 100644 frontend/views/upload/components/upload.indexOptions.js delete mode 100644 frontend/views/upload/components/upload.menu.js delete mode 100644 frontend/views/upload/components/upload.show.js delete mode 100644 frontend/views/upload/upload.actions.js delete mode 100644 frontend/views/upload/upload.container.js delete mode 100644 frontend/views/upload/upload.css delete mode 100644 frontend/views/upload/upload.reducer.js diff --git a/.babelrc b/.babelrc index 1bac7e8..ed122ce 100644 --- a/.babelrc +++ b/.babelrc @@ -4,13 +4,18 @@ "debug": false, "modules": false, "useBuiltIns": false - }], + }], "@babel/preset-react" ], "plugins": [ "@babel/plugin-transform-runtime", [ "@babel/plugin-proposal-class-properties", { "loose": true } ], - "transform-async-to-generator" + "transform-async-to-generator", + ["module-resolver", { + "root": ["./frontend"], + "alias": { + } + }] ], "env": { "production": { diff --git a/.eslintrc.js b/.eslintrc.js index 1e9628e..75ffa32 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,10 +19,14 @@ module.exports = { "sourceType": "module" }, "plugins": [ - "react" + "react", + "react-hooks" ], "rules": { "semi": ["error", "never"], "react/prop-types": [0], + "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks + "react-hooks/exhaustive-deps": "warn" // Checks effect dependencies } }; + diff --git a/frontend/actions.js b/frontend/actions.js deleted file mode 100644 index 69b06f0..0000000 --- a/frontend/actions.js +++ /dev/null @@ -1,18 +0,0 @@ -import { bindActionCreators } from 'redux' -import { actions as crudActions } from './api' - -import * as siteActions from './views/site/site.actions' - -import { store } from './store' - -export default - Object.keys(crudActions) - .map(a => [a, crudActions[a]]) - .concat([ - ['site', siteActions], - ]) - .map(p => [p[0], bindActionCreators(p[1], store.dispatch)]) - .concat([ - // ['socket', socketActions], - ]) - .reduce((a,b) => (a[b[0]] = b[1])&&a,{}) \ No newline at end of file diff --git a/frontend/api/crud.actions.js b/frontend/api/crud.actions.js deleted file mode 100644 index 25027a5..0000000 --- a/frontend/api/crud.actions.js +++ /dev/null @@ -1,51 +0,0 @@ -import { crud_fetch } from './crud.fetch' -import { as_type } from './crud.types' -import { upload_action } from './crud.upload' -import { store } from '../store' - -export function crud_actions(type) { - const fetch_type = crud_fetch(type) - return [ - 'index', - 'show', - 'create', - 'update', - 'destroy', - ].reduce((lookup, param) => { - lookup[param] = crud_action(type, param, (q) => fetch_type[param](q)) - return lookup - }, { - action: (method, fn) => crud_action(type, method, fn), - upload: (fd) => upload_action(type, fd), - updateOption: (key, value) => dispatch => { - dispatch({ type: as_type(type, 'update_option'), key, value }) - }, - updateOptions: opt => dispatch => { - dispatch({ type: as_type(type, 'update_options'), opt }) - }, - }) -} - -export const crud_action = (type, method, fn) => (q, load_more) => dispatch => { - return new Promise ((resolve, reject) => { - if (method === 'index') { - if (store.getState()[type].index.loading) { - return resolve({}) - } - } - dispatch({ type: as_type(type, method + '_loading'), load_more }) - fn(q).then(data => { - if (data.status === 'ok') { - dispatch({ type: as_type(type, method), data, load_more }) - resolve(data) - } else { - dispatch({ type: as_type(type, method + '_error'), error: data.error }) - reject(data) - } - }).catch(e => { - console.log(e) - dispatch({ type: as_type(type, method + '_error') }) - reject(e) - }) - }) -} diff --git a/frontend/api/crud.fetch.js b/frontend/api/crud.fetch.js deleted file mode 100644 index c88225e..0000000 --- a/frontend/api/crud.fetch.js +++ /dev/null @@ -1,105 +0,0 @@ -import fetch from 'node-fetch' - -export function crud_fetch(type, tag) { - const uri = '/api/v1/' + type + '/' + (tag || '') - return { - index: q => { - return fetch(_get_url(uri, q), _get_headers()) - .then(req => req.json()) - .catch(error) - }, - - show: id => { - let url; - if (typeof id === 'object') { - url = _get_url(uri + id[0] + '/', id[1]) - } else { - url = _get_url(uri + id + '/') - } - return fetch(url, _get_headers()) - .then(req => req.json()) - .catch(error) - }, - - create: data => { - return fetch(uri, post(data)) - .then(req => req.json()) - .catch(error) - }, - - update: data => { - return fetch(uri + data.id + '/', put(data)) - .then(req => req.json()) - .catch(error) - }, - - destroy: data => { - return fetch(uri + data.id + '/', destroy(data)) - .then(req => req.json()) - .catch(error) - }, - } -} - -function _get_url(_url, data) { - const url = new URL(window.location.origin + _url) - if (data) { - Object.keys(data).forEach(key => url.searchParams.append(key, data[key])) - } - return url -} -export function _get_headers() { - return { - method: 'GET', - credentials: 'same-origin', - headers: { - 'Accept': 'application/json', - }, - } -} -export function post(data) { - return { - method: 'POST', - body: JSON.stringify(data), - credentials: 'same-origin', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - } -} -export function postBody(data) { - return { - method: 'POST', - body: data, - credentials: 'same-origin', - headers: { - 'Accept': 'application/json', - }, - } -} -export function put(data) { - return { - method: 'PUT', - body: JSON.stringify(data), - credentials: 'same-origin', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - } -} -export function destroy(data) { - return { - method: 'DELETE', - body: JSON.stringify(data), - credentials: 'same-origin', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - } -} -function error(err) { - console.warn(err) -} diff --git a/frontend/api/crud.reducer.js b/frontend/api/crud.reducer.js deleted file mode 100644 index 2a7e4c4..0000000 --- a/frontend/api/crud.reducer.js +++ /dev/null @@ -1,196 +0,0 @@ -import * as types from 'app/types' -import { getOrderedIds, getOrderedIdsFromLookup } from 'app/utils' -import { session } from 'app/session' - -export const crudState = (type, options) => ({ - index: {}, - show: {}, - create: {}, - update: {}, - destroy: {}, - lookup: [], - ...options, -}) - -export const crudReducer = (type) => { - const crud_type = types[type] - return (state, action) => { - switch (action.type) { - // index - case crud_type.index_loading: - return { - ...state, - index: action.load_more - ? { ...state.index, loading: true } - : { loading: true }, - } - case crud_type.index: - if (action.data.res.length) { - return { - ...state, - index: { - lookup: action.data.res.reduce((a, b) => { a[b.id] = b; return a }, action.load_more ? state.index.lookup : {}), - order: getOrderedIds(action.data.res, state.options.sort, action.load_more ? state.index.order : []), - }, - } - } else { - Object.keys(action.data.res).forEach(key => { - const el = action.data.res[key] - el.key = key - el.id = el.id || key - }) - return { - ...state, - index: { - lookup: action.data.res, - order: getOrderedIdsFromLookup(action.data.res, state.options.sort), - }, - } - } - case crud_type.index_error: - return { - ...state, - index: { loading: false, error: true }, - } - case crud_type.index_sort: - return { - ...state, - index: { - ...state.index, - order: action.data.res.map(b => b.id), - }, - } - - // show - case crud_type.show_loading: - return { - ...state, - show: { loading: true }, - } - case crud_type.show: - if (!action.data) { - return { - ...state, - show: { not_found: true }, - } - } - return { - ...state, - show: action.data, - } - case crud_type.show_error: - return { - ...state, - show: { loading: false, error: true }, - } - - // - // create - case crud_type.create_loading: - return { - ...state, - create: { loading: true }, - } - case crud_type.create: - return { - ...state, - create: action.data, - index: addToIndex(state.index, action.data.res, state.options.sort), - } - case crud_type.create_error: - return { - ...state, - create: action.data, - } - - // - // update - case crud_type.update_loading: - return { - ...state, - update: { loading: true }, - } - case crud_type.update: - return { - ...state, - update: action.data, - index: addToIndex(state.index, action.data.res, state.options.sort), - show: (state.show.res && state.show.res.id === action.data.res.id) ? { - res: { - ...state.show.res, - ...action.data.res, - } - } : state.show - } - case crud_type.update_error: - return { - ...state, - update: { loading: false, error: true }, - } - - // - // destroy - case crud_type.destroy_loading: - return { - ...state, - destroy: { loading: true }, - } - case crud_type.destroy: - return { - ...state, - index: { - ...(() => { - if (!state.index.lookup) { - return {} - } - delete state.index.lookup[action.data.id] - const _id = parseInt(action.data.id) - state.index.order = state.index.order.filter(id => id !== _id) - return { ...state.index } - })() - }, - destroy: { loading: false }, - } - case crud_type.destroy_error: - return { - ...state, - destroy: { error: true }, - } - - // - // options - case crud_type.update_option: - session.set(type + "." + action.key, action.value) - return { - ...state, - options: { - ...state.options, - [action.key]: action.value, - } - } - - case crud_type.update_options: - session.setAll( - Object.keys(action.opt).reduce((a,b) => { a[type + '.' + b] = action.opt[b]; return a }, {}) - ) - return { - ...state, - options: { - ...action.opt, - } - } - - default: - return state - } - } -} - -const addToIndex = (index, data, sort) => { - const lookup = (index && index.lookup) ? { - ...index.lookup, - } : {} - lookup[data.id] = data - const order = getOrderedIdsFromLookup(lookup, sort) - return { lookup, order } -} diff --git a/frontend/api/crud.types.js b/frontend/api/crud.types.js deleted file mode 100644 index 7b24811..0000000 --- a/frontend/api/crud.types.js +++ /dev/null @@ -1,36 +0,0 @@ - -export const as_type = (a, b) => [a, b].join('_').toUpperCase() - -export const with_type = (type, actions) => - actions.reduce((a, b) => (a[b] = as_type(type, b)) && a, {}) - -export const crud_type = (type, actions=[]) => - with_type(type, actions.concat([ - 'index_loading', - 'index', - 'index_error', - 'index_sort', - 'show_loading', - 'show', - 'show_error', - 'create_loading', - 'create', - 'create_error', - 'update_loading', - 'update', - 'update_error', - 'destroy_loading', - 'destroy', - 'destroy_error', - 'upload_loading', - 'upload_progress', - 'upload_waiting', - 'upload_complete', - 'upload_error', - 'sort', - 'update_option', - 'update_options', - 'loading', - 'loaded', - 'error', - ])) diff --git a/frontend/api/crud.upload.js b/frontend/api/crud.upload.js deleted file mode 100644 index 8a711c7..0000000 --- a/frontend/api/crud.upload.js +++ /dev/null @@ -1,107 +0,0 @@ -import { as_type } from './crud.types' - -export function crud_upload(type, data, dispatch) { - return new Promise( (resolve, reject) => { - // console.log(type, data) - const { id } = data - - const fd = new FormData() - - Object.keys(data).forEach(key => { - if (key !== 'id') { - fd.append(key, data[key]) - } - }) - - let url = id ? '/api/v1/' + type + '/' + id + '/' - : '/api/v1/' + type + '/' - // console.log(url) - - const xhr = new XMLHttpRequest() - xhr.upload.addEventListener("progress", uploadProgress, false) - xhr.addEventListener("load", uploadComplete, false) - xhr.addEventListener("error", uploadFailed, false) - xhr.addEventListener("abort", uploadCancelled, false) - xhr.open("POST", url) - xhr.send(fd) - - dispatch && dispatch({ type: as_type(type, 'upload_loading')}) - - let complete = false - - function uploadProgress (e) { - if (e.lengthComputable) { - const percent = Math.round(e.loaded * 100 / e.total) || 0 - if (percent > 99) { - dispatch && dispatch({ - type: as_type(type, 'upload_waiting'), - percent, - [type]: id, - }) - } else { - dispatch && dispatch({ - type: as_type(type, 'upload_progress'), - percent, - [type]: id, - }) - } - } - else { - dispatch && dispatch({ - type: as_type(type, 'upload_error'), - error: 'unable to compute upload progress', - [type]: id, - }) - } - } - - function uploadComplete (e) { - let parsed; - try { - parsed = JSON.parse(e.target.responseText) - } catch (e) { - dispatch && dispatch({ - type: as_type(type, 'upload_error'), - error: 'upload failed', - [type]: id, - }) - reject(e) - return - } - dispatch && dispatch({ - type: as_type(type, 'upload_complete'), - data: parsed, - [type]: id, - }) - if (parsed.res) { - (parsed.res.length ? parsed.res : [parsed.res]).forEach(file => { - dispatch && dispatch({ - type: as_type('upload', 'create'), - data: { res: file }, - }) - }) - } - resolve(parsed) - } - - function uploadFailed (evt) { - dispatch && dispatch({ - type: as_type(type, 'upload_error'), - error: 'upload failed', - [type]: id, - }) - reject(evt) - } - - function uploadCancelled (evt) { - dispatch && dispatch({ - type: as_type(type, 'upload_error'), - error: 'upload cancelled', - [type]: id, - }) - reject(evt) - } - }) -} - -export const upload_action = (type, data) => dispatch => crud_upload(type, data, dispatch) diff --git a/frontend/api/index.js b/frontend/api/index.js deleted file mode 100644 index 26a8baa..0000000 --- a/frontend/api/index.js +++ /dev/null @@ -1,24 +0,0 @@ -import { crud_actions } from './crud.actions' -import * as util from '../util' - -/* -for our crud events, create corresponding actions -the actions fire a 'loading' event, call the underlying api method, and then resolve. -so you can do ... - import { folderActions } from '../../api' - folderActions.index({ module: 'samplernn' }) - folderActions.show(12) - folderActions.create({ module: 'samplernn', name: 'foo' }) - folderActions.update(12, { module: 'pix2pix' }) - folderActions.destroy(12, { confirm: true }) - folderActions.upload(12, form_data) -*/ - -export { util } - -export const actions = [ - 'graph', - 'page', - 'tile', - 'upload', -].reduce((a,b) => (a[b] = crud_actions(b)) && a, {}) diff --git a/frontend/app.js b/frontend/app.js deleted file mode 100644 index ac1f3e1..0000000 --- a/frontend/app.js +++ /dev/null @@ -1,44 +0,0 @@ -import React, { Component } from 'react' -import { ConnectedRouter } from 'connected-react-router' -import { Route } from 'react-router' - -// import actions from './actions' - -import * as views from './views' - -const viewList = Object.keys(views).map(name => { - const view = views[name] - let path, exact = false - if (name === 'graph') { - path = '/:graph_name' - exact = true - } else if (name === 'page') { - path = '/:graph_name/:page_name' - exact = true - } else { - path = '/' + name - } - return ( - - ) -}) - -export default class App extends Component { - componentDidMount() { - // actions.modelzoo.index() - } - render() { - return ( - -
- {viewList} - { - // redirect to index!! - setTimeout(() => this.props.history.push('/index'), 10) - return null - }} /> -
-
- ) - } -} diff --git a/frontend/app/actions.js b/frontend/app/actions.js new file mode 100644 index 0000000..0fba6d1 --- /dev/null +++ b/frontend/app/actions.js @@ -0,0 +1,18 @@ +import { bindActionCreators } from 'redux' +import { actions as crudActions } from './api' + +import * as siteActions from 'app/views/site/site.actions' + +import { store } from 'app/store' + +export default + Object.keys(crudActions) + .map(a => [a, crudActions[a]]) + .concat([ + ['site', siteActions], + ]) + .map(p => [p[0], bindActionCreators(p[1], store.dispatch)]) + .concat([ + // ['socket', socketActions], + ]) + .reduce((a,b) => (a[b[0]] = b[1])&&a,{}) \ No newline at end of file diff --git a/frontend/app/api/crud.actions.js b/frontend/app/api/crud.actions.js new file mode 100644 index 0000000..86c2948 --- /dev/null +++ b/frontend/app/api/crud.actions.js @@ -0,0 +1,51 @@ +import { crud_fetch } from 'app/api/crud.fetch' +import { as_type } from 'app/api/crud.types' +import { upload_action } from 'app/api/crud.upload' +import { store } from 'app/store' + +export function crud_actions(type) { + const fetch_type = crud_fetch(type) + return [ + 'index', + 'show', + 'create', + 'update', + 'destroy', + ].reduce((lookup, param) => { + lookup[param] = crud_action(type, param, (q) => fetch_type[param](q)) + return lookup + }, { + action: (method, fn) => crud_action(type, method, fn), + upload: (fd) => upload_action(type, fd), + updateOption: (key, value) => dispatch => { + dispatch({ type: as_type(type, 'update_option'), key, value }) + }, + updateOptions: opt => dispatch => { + dispatch({ type: as_type(type, 'update_options'), opt }) + }, + }) +} + +export const crud_action = (type, method, fn) => (q, load_more) => dispatch => { + return new Promise ((resolve, reject) => { + if (method === 'index') { + if (store.getState()[type].index.loading) { + return resolve({}) + } + } + dispatch({ type: as_type(type, method + '_loading'), load_more }) + fn(q).then(data => { + if (data.status === 'ok') { + dispatch({ type: as_type(type, method), data, load_more }) + resolve(data) + } else { + dispatch({ type: as_type(type, method + '_error'), error: data.error }) + reject(data) + } + }).catch(e => { + console.log(e) + dispatch({ type: as_type(type, method + '_error') }) + reject(e) + }) + }) +} diff --git a/frontend/app/api/crud.fetch.js b/frontend/app/api/crud.fetch.js new file mode 100644 index 0000000..c88225e --- /dev/null +++ b/frontend/app/api/crud.fetch.js @@ -0,0 +1,105 @@ +import fetch from 'node-fetch' + +export function crud_fetch(type, tag) { + const uri = '/api/v1/' + type + '/' + (tag || '') + return { + index: q => { + return fetch(_get_url(uri, q), _get_headers()) + .then(req => req.json()) + .catch(error) + }, + + show: id => { + let url; + if (typeof id === 'object') { + url = _get_url(uri + id[0] + '/', id[1]) + } else { + url = _get_url(uri + id + '/') + } + return fetch(url, _get_headers()) + .then(req => req.json()) + .catch(error) + }, + + create: data => { + return fetch(uri, post(data)) + .then(req => req.json()) + .catch(error) + }, + + update: data => { + return fetch(uri + data.id + '/', put(data)) + .then(req => req.json()) + .catch(error) + }, + + destroy: data => { + return fetch(uri + data.id + '/', destroy(data)) + .then(req => req.json()) + .catch(error) + }, + } +} + +function _get_url(_url, data) { + const url = new URL(window.location.origin + _url) + if (data) { + Object.keys(data).forEach(key => url.searchParams.append(key, data[key])) + } + return url +} +export function _get_headers() { + return { + method: 'GET', + credentials: 'same-origin', + headers: { + 'Accept': 'application/json', + }, + } +} +export function post(data) { + return { + method: 'POST', + body: JSON.stringify(data), + credentials: 'same-origin', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + } +} +export function postBody(data) { + return { + method: 'POST', + body: data, + credentials: 'same-origin', + headers: { + 'Accept': 'application/json', + }, + } +} +export function put(data) { + return { + method: 'PUT', + body: JSON.stringify(data), + credentials: 'same-origin', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + } +} +export function destroy(data) { + return { + method: 'DELETE', + body: JSON.stringify(data), + credentials: 'same-origin', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + } +} +function error(err) { + console.warn(err) +} diff --git a/frontend/app/api/crud.reducer.js b/frontend/app/api/crud.reducer.js new file mode 100644 index 0000000..2a7e4c4 --- /dev/null +++ b/frontend/app/api/crud.reducer.js @@ -0,0 +1,196 @@ +import * as types from 'app/types' +import { getOrderedIds, getOrderedIdsFromLookup } from 'app/utils' +import { session } from 'app/session' + +export const crudState = (type, options) => ({ + index: {}, + show: {}, + create: {}, + update: {}, + destroy: {}, + lookup: [], + ...options, +}) + +export const crudReducer = (type) => { + const crud_type = types[type] + return (state, action) => { + switch (action.type) { + // index + case crud_type.index_loading: + return { + ...state, + index: action.load_more + ? { ...state.index, loading: true } + : { loading: true }, + } + case crud_type.index: + if (action.data.res.length) { + return { + ...state, + index: { + lookup: action.data.res.reduce((a, b) => { a[b.id] = b; return a }, action.load_more ? state.index.lookup : {}), + order: getOrderedIds(action.data.res, state.options.sort, action.load_more ? state.index.order : []), + }, + } + } else { + Object.keys(action.data.res).forEach(key => { + const el = action.data.res[key] + el.key = key + el.id = el.id || key + }) + return { + ...state, + index: { + lookup: action.data.res, + order: getOrderedIdsFromLookup(action.data.res, state.options.sort), + }, + } + } + case crud_type.index_error: + return { + ...state, + index: { loading: false, error: true }, + } + case crud_type.index_sort: + return { + ...state, + index: { + ...state.index, + order: action.data.res.map(b => b.id), + }, + } + + // show + case crud_type.show_loading: + return { + ...state, + show: { loading: true }, + } + case crud_type.show: + if (!action.data) { + return { + ...state, + show: { not_found: true }, + } + } + return { + ...state, + show: action.data, + } + case crud_type.show_error: + return { + ...state, + show: { loading: false, error: true }, + } + + // + // create + case crud_type.create_loading: + return { + ...state, + create: { loading: true }, + } + case crud_type.create: + return { + ...state, + create: action.data, + index: addToIndex(state.index, action.data.res, state.options.sort), + } + case crud_type.create_error: + return { + ...state, + create: action.data, + } + + // + // update + case crud_type.update_loading: + return { + ...state, + update: { loading: true }, + } + case crud_type.update: + return { + ...state, + update: action.data, + index: addToIndex(state.index, action.data.res, state.options.sort), + show: (state.show.res && state.show.res.id === action.data.res.id) ? { + res: { + ...state.show.res, + ...action.data.res, + } + } : state.show + } + case crud_type.update_error: + return { + ...state, + update: { loading: false, error: true }, + } + + // + // destroy + case crud_type.destroy_loading: + return { + ...state, + destroy: { loading: true }, + } + case crud_type.destroy: + return { + ...state, + index: { + ...(() => { + if (!state.index.lookup) { + return {} + } + delete state.index.lookup[action.data.id] + const _id = parseInt(action.data.id) + state.index.order = state.index.order.filter(id => id !== _id) + return { ...state.index } + })() + }, + destroy: { loading: false }, + } + case crud_type.destroy_error: + return { + ...state, + destroy: { error: true }, + } + + // + // options + case crud_type.update_option: + session.set(type + "." + action.key, action.value) + return { + ...state, + options: { + ...state.options, + [action.key]: action.value, + } + } + + case crud_type.update_options: + session.setAll( + Object.keys(action.opt).reduce((a,b) => { a[type + '.' + b] = action.opt[b]; return a }, {}) + ) + return { + ...state, + options: { + ...action.opt, + } + } + + default: + return state + } + } +} + +const addToIndex = (index, data, sort) => { + const lookup = (index && index.lookup) ? { + ...index.lookup, + } : {} + lookup[data.id] = data + const order = getOrderedIdsFromLookup(lookup, sort) + return { lookup, order } +} diff --git a/frontend/app/api/crud.types.js b/frontend/app/api/crud.types.js new file mode 100644 index 0000000..7b24811 --- /dev/null +++ b/frontend/app/api/crud.types.js @@ -0,0 +1,36 @@ + +export const as_type = (a, b) => [a, b].join('_').toUpperCase() + +export const with_type = (type, actions) => + actions.reduce((a, b) => (a[b] = as_type(type, b)) && a, {}) + +export const crud_type = (type, actions=[]) => + with_type(type, actions.concat([ + 'index_loading', + 'index', + 'index_error', + 'index_sort', + 'show_loading', + 'show', + 'show_error', + 'create_loading', + 'create', + 'create_error', + 'update_loading', + 'update', + 'update_error', + 'destroy_loading', + 'destroy', + 'destroy_error', + 'upload_loading', + 'upload_progress', + 'upload_waiting', + 'upload_complete', + 'upload_error', + 'sort', + 'update_option', + 'update_options', + 'loading', + 'loaded', + 'error', + ])) diff --git a/frontend/app/api/crud.upload.js b/frontend/app/api/crud.upload.js new file mode 100644 index 0000000..8c1b265 --- /dev/null +++ b/frontend/app/api/crud.upload.js @@ -0,0 +1,107 @@ +import { as_type } from 'app/api/crud.types' + +export function crud_upload(type, data, dispatch) { + return new Promise( (resolve, reject) => { + // console.log(type, data) + const { id } = data + + const fd = new FormData() + + Object.keys(data).forEach(key => { + if (key !== 'id') { + fd.append(key, data[key]) + } + }) + + let url = id ? '/api/v1/' + type + '/' + id + '/' + : '/api/v1/' + type + '/' + // console.log(url) + + const xhr = new XMLHttpRequest() + xhr.upload.addEventListener("progress", uploadProgress, false) + xhr.addEventListener("load", uploadComplete, false) + xhr.addEventListener("error", uploadFailed, false) + xhr.addEventListener("abort", uploadCancelled, false) + xhr.open("POST", url) + xhr.send(fd) + + dispatch && dispatch({ type: as_type(type, 'upload_loading')}) + + let complete = false + + function uploadProgress (e) { + if (e.lengthComputable) { + const percent = Math.round(e.loaded * 100 / e.total) || 0 + if (percent > 99) { + dispatch && dispatch({ + type: as_type(type, 'upload_waiting'), + percent, + [type]: id, + }) + } else { + dispatch && dispatch({ + type: as_type(type, 'upload_progress'), + percent, + [type]: id, + }) + } + } + else { + dispatch && dispatch({ + type: as_type(type, 'upload_error'), + error: 'unable to compute upload progress', + [type]: id, + }) + } + } + + function uploadComplete (e) { + let parsed; + try { + parsed = JSON.parse(e.target.responseText) + } catch (e) { + dispatch && dispatch({ + type: as_type(type, 'upload_error'), + error: 'upload failed', + [type]: id, + }) + reject(e) + return + } + dispatch && dispatch({ + type: as_type(type, 'upload_complete'), + data: parsed, + [type]: id, + }) + if (parsed.res) { + (parsed.res.length ? parsed.res : [parsed.res]).forEach(file => { + dispatch && dispatch({ + type: as_type('upload', 'create'), + data: { res: file }, + }) + }) + } + resolve(parsed) + } + + function uploadFailed (evt) { + dispatch && dispatch({ + type: as_type(type, 'upload_error'), + error: 'upload failed', + [type]: id, + }) + reject(evt) + } + + function uploadCancelled (evt) { + dispatch && dispatch({ + type: as_type(type, 'upload_error'), + error: 'upload cancelled', + [type]: id, + }) + reject(evt) + } + }) +} + +export const upload_action = (type, data) => dispatch => crud_upload(type, data, dispatch) diff --git a/frontend/app/api/index.js b/frontend/app/api/index.js new file mode 100644 index 0000000..c3d0aa4 --- /dev/null +++ b/frontend/app/api/index.js @@ -0,0 +1,24 @@ +import { crud_actions } from 'app/api/crud.actions' +import * as util from 'app/api/utils' + +/* +for our crud events, create corresponding actions +the actions fire a 'loading' event, call the underlying api method, and then resolve. +so you can do ... + import { folderActions } from 'app/api' + folderActions.index({ module: 'samplernn' }) + folderActions.show(12) + folderActions.create({ module: 'samplernn', name: 'foo' }) + folderActions.update(12, { module: 'pix2pix' }) + folderActions.destroy(12, { confirm: true }) + folderActions.upload(12, form_data) +*/ + +export { util } + +export const actions = [ + 'graph', + 'page', + 'tile', + 'upload', +].reduce((a,b) => (a[b] = crud_actions(b)) && a, {}) diff --git a/frontend/app/app.js b/frontend/app/app.js new file mode 100644 index 0000000..8dbbd0f --- /dev/null +++ b/frontend/app/app.js @@ -0,0 +1,44 @@ +import React, { Component } from 'react' +import { ConnectedRouter } from 'connected-react-router' +import { Route } from 'react-router' + +// import actions from 'app/actions' + +import * as views from 'app/views' + +const viewList = Object.keys(views).map(name => { + const view = views[name] + let path, exact = false + if (name === 'graph') { + path = '/:graph_name' + exact = true + } else if (name === 'page') { + path = '/:graph_name/:page_name' + exact = true + } else { + path = '/' + name + } + return ( + + ) +}) + +export default class App extends Component { + componentDidMount() { + // actions.modelzoo.index() + } + render() { + return ( + +
+ {viewList} + { + // redirect to index!! + setTimeout(() => this.props.history.push('/index'), 10) + return null + }} /> +
+
+ ) + } +} diff --git a/frontend/app/common/app.css b/frontend/app/common/app.css new file mode 100644 index 0000000..d9f9946 --- /dev/null +++ b/frontend/app/common/app.css @@ -0,0 +1,428 @@ +* { box-sizing: border-box; } +html, body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; +} +body { + background: #000; + color: #ddd; + overflow: hidden; + font-family: 'Roboto', sans-serif; + font-size: 0.875rem; + height: 100%; + width: 100%; +} +.gray { + color: #888; +} + +/* layout */ + +.container { + height: 100%; + width: 100%; +} +.app { + /*display: flex;*/ + height: 100%; + width: 100%; +} +.app > div { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; +} +.app .body { + display: flex; + flex-direction: column; + flex-grow: 1; + position: relative; + height: 100%; + width: 100%; +} + +.row { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; +} +.row > div { + margin-right: 1.5rem; +} +.row > div:last-child { + margin-right: 0; +} + + +.row.menubar { + justify-content: flex-end; +} +.menubar > :first-child { + flex: 1; +} + +/* lists */ + +ul { + margin: 0.75rem 0; +} +li { + line-height: 1.5; +} + +/* header */ + +header { + height: 3.125rem; + font-size: 0.875rem; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + background: rgba(16,32,64,0.5); + color: white; + z-index: 50; +} +header b { + font-weight: 900; +} +header a { + color: rgba(255,255,255,0.95); + text-decoration: none; + font-size: 0.875rem; + font-weight: 500; +} +header > div:first-child { + display: flex; + justify-content: flex-start; + align-items: center; + padding-left: 1.5rem; +} +header > div:last-child { + padding-right: 1.5rem; +} +header > div > button { + padding: 0.25rem; + margin: 0 0 0 0.5rem; + background: #000; + border-color: #888; + color: #888; +} +header > div > button:hover { + border-color: #fff; + color: #fff; +} +header .vcat-btn { + font-size: 0.875rem; + padding-left: 0.5rem; + letter-spacing: 0.0625rem; +} +header > div:last-child a { + padding: 0.5rem; +} +header .btn-link:focus, +header .btn-link:hover, +header .btn-link:active, +header a:focus, +header a:hover, +header a:active { + text-decoration: none; + color: white; +} +header a:focus, +header a:hover, +header a:active { + color: white; +} +.menuToggle { + width: 1.625rem; + height: 1.625rem; + cursor: pointer; + line-height: 1; +} +header a.navbar-brand { + font-size: .8rem; +} + +header .username { + cursor: pointer; +} + +/* headings */ + +h1 { + color: #eee; + margin-bottom: 1.25rem; + font-size: 1.5rem; + font-weight: normal; +} +div:first-child > h1:first-child, +.menuButtons + div > h1:first-child { + margin-top: 0; +} +h2 { + color: #eee; + font-size: 1.25rem; + font-weight: normal; +} +h3 { + color: #eee; + margin-top: 0; + margin-bottom: 1.25rem; + font-size: 1.0rem; + font-weight: normal; +} +p { + margin: 1.25rem 0; + line-height: 1.5; +} +.byline { + color: #888; + font-size: 0.75rem; + margin-top: 0.25rem; + margin-bottom: 1.25rem; +} + +/* links */ + +b { + color: #fff; +} +a { + text-decoration: underline; + color: #8df; +} + +/* menu button */ + +.menuButtons { + width: 2.5rem; + min-height: 18rem; +} +.menuButton { + position: relative; + text-align: center; + text-transform: uppercase; + font-size: 0.625rem; + color: #333; + text-decoration: none; + cursor: pointer; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 2.5rem; + margin-bottom: 0.75rem; +} +.menuButton .icon { + background-color: #fff; + width: 2.5rem; + height: 2.5rem; + border: 1px solid; + border-color: #888; + margin-bottom: 0.3rem; + display: flex; + justify-content: center; + align-items: center; + transition: border-color 0.1s; + border-radius: 0.125rem; +} +.menuButton svg { + width: 80%; + fill: #888; +} +.menuButton:hover .icon { + background-color: #eef; + border-color: #000; +} +.menuButton:hover svg { + fill: #000; +} + +.menuButton.small { + width: 2.0rem; + margin-bottom: 0; + margin-right: 0.1875rem; +} +.menuButton.small .icon { + border: 0; + border-color: #888; + margin-bottom: 0; + width: 2.0rem; + height: 2.0rem; + border-radius: 0.0625rem; +} +.menuButton.small svg { + fill: #888; + width: 1.75rem; + height: 1.75rem; +} +.menuButton.small .icon:hover { + border-color: #11f; + background-color: #11f; +} +.menuButton.small:hover svg { + fill: #fff; +} +.results.th .menuButton.small { + width: 1.5rem; +} +.results.th .menuButton.small .icon { + width: 1.5rem; + height: 1.5rem; +} +.results.th .menuButton.small svg { + width: 1.25rem; + height: 1.25rem; +} + +.menuButton.small.active .icon { + border-color: #11f; + background-color: #11f; +} +.menuButton.small.active svg { + fill: #fff; +} +.menuButton.small.active:hover .icon { + border-color: #fff; + background-color: #11f; +} +.menuButton.small.active:hover svg { + fill: #fff; +} + +/* rows - like a table */ + +.rows { + width: 100%; +} +.rows .row { + width: 100%; +} +.rows .row:nth-of-type(2n+1) { + background: #f8f8f8; +} +.rows .row:nth-of-type(2n+2) { + background: #eeeeee; +} +.rows .row:hover { + background: #d8d8d8; +} +.rows .row > div, +.rows .row a > div { + padding: 0.75rem; + overflow: hidden; + text-overflow: ellipsis; + margin: 0; +} +.rows .row div.title { + width: 10rem; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.rows .row div.string, +.rows .row div.str { + min-width: 6rem; +} +.rows .row div.bool { + width: 4rem; + overflow: visible; + text-align: center; +} +.rows .row div.color { + width: 4rem; + overflow: visible; + text-align: center; +} +.rows .row div.date { + min-width: 10rem; +} +.rows .row div.int, +.rows .row div.float { + text-align: right; + min-width: 6rem; +} +.rows .row.heading div.int, +.rows .row.heading div.float { + text-align: center; +} +.rows .row div.text { + flex: 1; + max-width: 20rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.rows .row.heading div { + text-transform: capitalize; + font-weight: bold; + background: #f8f8f8; +} +.rows .row.heading:hover { + background: #f8f8f8; +} + +/* misc ui */ + +pre, code, .license { + font-family: Menlo, monospace; + font-size: 0.75rem; + line-height: 2; +} +.swatch { + display: inline-block; + width: 0.75rem; + height: 0.75rem; + border: 1px solid #333; +} +.dot { + display: inline-block; + width: 0.5rem; + height: 0.5rem; + border-radius: 50%; +} +.light { + color: #888; +} +.pill { + display: inline-block; + padding: 0.125rem; + width: 2.5rem; + text-align: center; + border-radius: 0.5rem; + font-size: 0.75rem; + text-transform: uppercase; + font-weight: bold; +} +.pill.yes { + background: #11f; + color: #fff; +} +.pill.no { + color: #ccc; + border: 1px solid; +} + +/* columns (of tags) */ + +.form .columnCells { + padding-top: 0.25rem +} +.columnCells .column { + margin-top: 0.25rem; +} +.columnCells .column > div { + max-width: 100%; + padding: 0 0.375rem 0.375rem 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.columnCells .selected { + color: #11f; +} \ No newline at end of file diff --git a/frontend/app/common/copyToClipboardButton.component.js b/frontend/app/common/copyToClipboardButton.component.js new file mode 100644 index 0000000..0defba5 --- /dev/null +++ b/frontend/app/common/copyToClipboardButton.component.js @@ -0,0 +1,24 @@ +import React, { Component } from 'react' +import { writeToClipboard } from 'app/utils' + +export default class CopyToClipboardButton extends Component { + state = { + copied: false, + } + + handleClick() { + writeToClipboard(this.props.data) + this.setState({ copied: true }) + } + + render() { + return ( + + ) + } +} diff --git a/frontend/app/common/fonts.css b/frontend/app/common/fonts.css new file mode 100644 index 0000000..c782885 --- /dev/null +++ b/frontend/app/common/fonts.css @@ -0,0 +1,55 @@ +@font-face { + font-family: 'Roboto'; + src: url('/static/fonts/Roboto-Bold.ttf') format('truetype'); + font-weight: bold; +} +@font-face { + font-family: 'Roboto'; + src: url('/static/fonts/Roboto-BoldItalic.ttf') format('truetype'); + font-weight: bold; + font-style: italic; +} +/* +@font-face { + font-family: 'Roboto'; + src: url('/static/fonts/Roboto-Light.ttf') format('truetype'); + font-weight: 100; +} +@font-face { + font-family: 'Roboto'; + src: url('/static/fonts/Roboto-LightItalic.ttf') format('truetype'); + font-weight: 100; +} +*/ +@font-face { + font-family: 'Roboto'; + src: url('/static/fonts/Roboto-Medium.ttf') format('truetype'); + font-weight: 300; +} +@font-face { + font-family: 'Roboto'; + src: url('/static/fonts/Roboto-MediumItalic.ttf') format('truetype'); + font-style: italic; + font-weight: 300; +} +@font-face { + font-family: 'Roboto'; + src: url('/static/fonts/Roboto-Regular.ttf') format('truetype'); +} +@font-face { + font-family: 'Roboto'; + src: url('/static/fonts/Roboto-Italic.ttf') format('truetype'); + font-style: italic; +} +/* +@font-face { + font-family: 'Roboto'; + src: url('/static/fonts/Roboto-Thin.ttf') format('truetype'); + font-weight: 100; +} +@font-face { + font-family: 'Roboto'; + src: url('/static/fonts/Roboto-ThinItalic.ttf') format('truetype'); + font-weight: 100; +} +*/ \ No newline at end of file diff --git a/frontend/app/common/form.component.js b/frontend/app/common/form.component.js new file mode 100644 index 0000000..cf3e466 --- /dev/null +++ b/frontend/app/common/form.component.js @@ -0,0 +1,220 @@ +import React, { Component } from 'react'; +import { courtesyS } from 'app/utils' + +export const TextInput = props => ( + +) + +export const LabelDescription = props => ( + +) + +export const NumberInput = props => ( + +) + +export const ColorInput = props => ( + +) + +export const TextArea = props => ( +