flow: stuff
This commit is contained in:
parent
b32f879ccb
commit
89952ec168
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/frontend/old
|
31
actions.js
31
actions.js
|
@ -1,5 +1,3 @@
|
|||
const {invokeHookSerial} = require('@truss/truss');
|
||||
|
||||
const {Responder} = require('./response.dev');
|
||||
|
||||
let responder = new Responder();
|
||||
|
@ -7,24 +5,21 @@ responder.start();
|
|||
|
||||
module.exports = () => ({
|
||||
|
||||
'truss/http-get': (action) => {
|
||||
const {payload: {headers}} = action;
|
||||
|
||||
delete headers.host;
|
||||
delete headers.connection;
|
||||
|
||||
return responder.respond(action).then(({html, headers: resHeaders}) => {
|
||||
return invokeHookSerial('truss/web-response', {
|
||||
status: 200,
|
||||
headers: {...defaultResponseHeaders(), ...resHeaders},
|
||||
html,
|
||||
}, headers);
|
||||
});
|
||||
'@truss/http-get': async (action) => {
|
||||
const {html, headers} = await responder.respond(action);
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
...defaultResponseHeaders(),
|
||||
...headers
|
||||
},
|
||||
response: html,
|
||||
};
|
||||
},
|
||||
|
||||
'truss/schema': () => {
|
||||
return {executors: ['truss/http-get']};
|
||||
},
|
||||
'@truss/schema': () => ({
|
||||
executors: ['@truss/http-get'],
|
||||
}),
|
||||
});
|
||||
|
||||
function defaultResponseHeaders() {
|
||||
|
|
69
frontend/app/api.js
Normal file
69
frontend/app/api.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
import {RSAA} from 'redux-api-middleware';
|
||||
|
||||
const PERSEA_FRONTHOST = '10.0.0.93';
|
||||
|
||||
export function createActionTypes(type, prefix) {
|
||||
return ['REQUEST', 'SUCCESS', 'FAILURE'].reduce((actions, action) => {
|
||||
return {
|
||||
...actions,
|
||||
[`${type}_${action}`]: `${prefix}/${type}_${action}`,
|
||||
};
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function createCall(uri, types, type) {
|
||||
const prefix = `http://${PERSEA_FRONTHOST}/api`;
|
||||
return {
|
||||
[RSAA]: {
|
||||
endpoint: `${prefix}${uri}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
types: ['REQUEST', 'SUCCESS', 'FAILURE'].map((action) => {
|
||||
return types[`${type}_${action}`]
|
||||
}),
|
||||
fetch: managedFetch,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export class ApiRequestHandler {
|
||||
|
||||
doesFetchEndpoint(input) {
|
||||
return false;
|
||||
}
|
||||
|
||||
async fetch(input, init) {
|
||||
return new Response(null);
|
||||
}
|
||||
|
||||
static normalizeRequestUrl(input) {
|
||||
return ('string' === typeof input) ? input : input.url;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const handlers = [];
|
||||
|
||||
export function registerApiRequestHandler(handler) {
|
||||
handlers.push(handler);
|
||||
}
|
||||
|
||||
function handlerForInput(input) {
|
||||
for (const handler of handlers) {
|
||||
if (handler.doesFetchEndpoint(input)) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function managedFetch(input, init) {
|
||||
const handler = handlerForInput(input);
|
||||
if (handler) {
|
||||
return handler.fetch(input, init);
|
||||
}
|
||||
else {
|
||||
return fetch(input, init);
|
||||
}
|
||||
}
|
45
frontend/app/component.js
Normal file
45
frontend/app/component.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import React from 'react';
|
||||
import {hot} from 'react-hot-loader';
|
||||
import {BrowserRouter} from 'react-router-dom';
|
||||
import {compose} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import contempo from 'contempo';
|
||||
|
||||
import Editor from './editor/component';
|
||||
import {ducks} from './index';
|
||||
import {createMapDispatchToProps} from './ducks';
|
||||
|
||||
import DevTools from '../dev-tools';
|
||||
|
||||
const decorate = compose(
|
||||
hot(module),
|
||||
contempo(require('./component.scss')),
|
||||
connect(
|
||||
(state) => ({
|
||||
currentWorkspace: state.workspace.current,
|
||||
}),
|
||||
createMapDispatchToProps(ducks)
|
||||
),
|
||||
)
|
||||
|
||||
function App({
|
||||
currentWorkspace,
|
||||
workspace,
|
||||
}) {
|
||||
return <React.Fragment>
|
||||
<BrowserRouter>
|
||||
<div className="app">
|
||||
<Editor workspace={currentWorkspace} />
|
||||
<button onClick={() => {
|
||||
workspace.load('/workspace/test');
|
||||
}}>Load</button>
|
||||
</div>
|
||||
</BrowserRouter>
|
||||
<div className="dev-tools">
|
||||
<DevTools />
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
};
|
||||
|
||||
export default decorate(App);
|
58
frontend/app/component.scss
Normal file
58
frontend/app/component.scss
Normal file
|
@ -0,0 +1,58 @@
|
|||
@import './forms.scss';
|
||||
|
||||
.app {
|
||||
background-color: #222;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aside {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
button {
|
||||
&:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
button, input, select, option {
|
||||
|
||||
&[disabled] {
|
||||
filter: grayscale(1);
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: inset 0 0 1pt 0.5pt #CCC, 0 0 1pt 0.5pt #CCC
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid #333;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.loading {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
svg {
|
||||
height: 25%;
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.editor.avocado-environment {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
45
frontend/app/ducks.js
Normal file
45
frontend/app/ducks.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import {combineReducers} from 'redux';
|
||||
import {all, call} from 'redux-saga/effects';
|
||||
|
||||
export function createMapDispatchToProps(ducks) {
|
||||
return (dispatch) => {
|
||||
const map = {};
|
||||
for (const i in ducks) {
|
||||
map[i] = {};
|
||||
for (const j in ducks[i].actions.creators) {
|
||||
map[i][j] = (...args) => {
|
||||
return dispatch(ducks[i].actions.creators[j](...args));
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
};
|
||||
}
|
||||
|
||||
export function createReducer(ducks) {
|
||||
const map = {};
|
||||
for (const i in ducks) {
|
||||
map[i] = ducks[i].reducer;
|
||||
}
|
||||
return combineReducers(map);
|
||||
}
|
||||
|
||||
export function createSaga(ducks) {
|
||||
const sagas = [];
|
||||
for (const i in ducks) {
|
||||
if (!ducks[i].saga) {
|
||||
continue;
|
||||
}
|
||||
sagas.push(call(ducks[i].saga));
|
||||
}
|
||||
return function* () {
|
||||
yield all(sagas);
|
||||
}
|
||||
}
|
||||
|
||||
export function compose(ducks) {
|
||||
return {
|
||||
reducer: createReducer(ducks),
|
||||
saga: createSaga(ducks),
|
||||
}
|
||||
}
|
24
frontend/app/editor/component.js
Normal file
24
frontend/app/editor/component.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import React from 'react';
|
||||
import {compose} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import Tabs from '../tabs/component';
|
||||
import Tab from '../tabs/tab';
|
||||
|
||||
const decorate = compose(
|
||||
connect(
|
||||
(state) => ({
|
||||
tabs: state.editor.tabs,
|
||||
}),
|
||||
)
|
||||
);
|
||||
|
||||
function Editor({tabs}) {
|
||||
return <div className="editor">
|
||||
<Tabs>
|
||||
<Tab icon="😂" basename="index.js" path="../editor" />
|
||||
</Tabs>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default decorate(Editor);
|
31
frontend/app/editor/index.js
Normal file
31
frontend/app/editor/index.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import {combineReducers} from 'redux'
|
||||
|
||||
const types = {
|
||||
EDITOR_CREATE_TAB: '@@persea/EDITOR_CREATE_TAB',
|
||||
EDITOR_CLOSE_TAB: '@@persea/EDITOR_CLOSE_TAB',
|
||||
};
|
||||
|
||||
const creators = {
|
||||
createTab: (title, uri) => {
|
||||
return {
|
||||
type: types.EDITOR_CREATE_TAB,
|
||||
payload: {
|
||||
title,
|
||||
uri,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const actions = {types, creators};
|
||||
|
||||
export const reducer = combineReducers({
|
||||
tabs: (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case types.EDITOR_CREATE_TAB:
|
||||
return [...state, action.payload];
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
},
|
||||
});
|
45
frontend/app/fixtures.js
Normal file
45
frontend/app/fixtures.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import {ApiRequestHandler, registerApiRequestHandler} from './api';
|
||||
|
||||
export const data = {
|
||||
'/api/config.json': {
|
||||
workspace: '/workspace/test',
|
||||
},
|
||||
'/api/workspace/test': {
|
||||
name: "Some workspace",
|
||||
tree: [],
|
||||
open: [
|
||||
{
|
||||
uri: ""
|
||||
}
|
||||
],
|
||||
settings: {},
|
||||
},
|
||||
};
|
||||
|
||||
function lookupData(url) {
|
||||
const {pathname} = new URL(url);
|
||||
return data[pathname];
|
||||
}
|
||||
|
||||
class FixtureApiRequestHandler extends ApiRequestHandler {
|
||||
|
||||
doesFetchEndpoint(input) {
|
||||
const url = ApiRequestHandler.normalizeRequestUrl(input);
|
||||
return !!lookupData(url);
|
||||
}
|
||||
|
||||
fetch(input, init) {
|
||||
const url = ApiRequestHandler.normalizeRequestUrl(input);
|
||||
const data = lookupData(url);
|
||||
return new Response(
|
||||
JSON.stringify(data), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerApiRequestHandler(new FixtureApiRequestHandler());
|
|
@ -7,7 +7,7 @@ button, input, select {
|
|||
|
||||
button {
|
||||
border: 1px solid #333;
|
||||
padding: 0;
|
||||
padding: 2em;
|
||||
|
||||
&:hover {
|
||||
background-color: #555;
|
|
@ -1,25 +1,54 @@
|
|||
import React from 'react';
|
||||
import {put, take} from 'redux-saga/effects';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import {Route, Switch} from 'react-router';
|
||||
import {compose} from './ducks';
|
||||
import * as editor from './editor';
|
||||
import * as workspace from './workspace';
|
||||
|
||||
import Welcome from './welcome';
|
||||
function App() {
|
||||
import {createActionTypes, createCall} from './api';
|
||||
|
||||
return <div className={classnames({
|
||||
app: true,
|
||||
dev: 'production' !== process.env.NODE_ENV,
|
||||
})}>
|
||||
<Switch>
|
||||
<Route path="/" component={Welcome} />
|
||||
</Switch>
|
||||
</div>;
|
||||
const types = {
|
||||
APPLICATION_START: '@@persea/APPLICATION_START',
|
||||
...createActionTypes('APPLICATION_CONFIG_LOAD', '@@persea'),
|
||||
};
|
||||
|
||||
const creators = {
|
||||
loadConfig: () => {
|
||||
return createCall('/config.json', types, 'APPLICATION_CONFIG_LOAD');
|
||||
},
|
||||
start: () => {
|
||||
return {
|
||||
type: types.APPLICATION_START,
|
||||
payload: null,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const actions = {types, creators};
|
||||
|
||||
export const ducks = {
|
||||
editor,
|
||||
workspace,
|
||||
};
|
||||
|
||||
const {reducer, saga: ducksSaga} = compose(ducks);
|
||||
|
||||
export {reducer};
|
||||
|
||||
export function* saga() {
|
||||
yield ducksSaga;
|
||||
// Wait for application start.
|
||||
yield take(types.APPLICATION_START);
|
||||
// Load fixtures.
|
||||
require('./fixtures');
|
||||
// Read config.
|
||||
yield put(creators.loadConfig());
|
||||
const {payload: config} = yield take(types.APPLICATION_CONFIG_LOAD_SUCCESS);
|
||||
// Workspace to load?
|
||||
if (config.workspace) {
|
||||
yield put(workspace.actions.creators.load(config.workspace));
|
||||
}
|
||||
// Put the dummy tab.
|
||||
else {
|
||||
yield put(editor.actions.creators.createTab('Welcome', '/foo/bar'));
|
||||
}
|
||||
}
|
||||
|
||||
import contempo from 'contempo';
|
||||
App = contempo(App, require('./index.scss'));
|
||||
|
||||
import {hot} from 'react-hot-loader';
|
||||
App = hot(module)(App);
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -1,6 +1,58 @@
|
|||
.app {
|
||||
background-color: #444444;
|
||||
color: #fff;
|
||||
@import './forms.scss';
|
||||
|
||||
.app-container {
|
||||
background-color: #222;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aside {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
button {
|
||||
&:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
button, input, select, option {
|
||||
|
||||
&[disabled] {
|
||||
filter: grayscale(1);
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: inset 0 0 1pt 0.5pt #CCC, 0 0 1pt 0.5pt #CCC
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid #333;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.loading {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
svg {
|
||||
height: 25%;
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.editor.avocado-environment {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
20
frontend/app/tabs/component.js
Normal file
20
frontend/app/tabs/component.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import React from 'react';
|
||||
import {compose} from 'redux';
|
||||
|
||||
import contempo from 'contempo';
|
||||
|
||||
import Tab from './tab';
|
||||
|
||||
const decorate = compose(
|
||||
contempo(require('./component.scss'))
|
||||
);
|
||||
|
||||
function Tabs({
|
||||
children,
|
||||
}) {
|
||||
return <div className="tabs">
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default decorate(Tabs);
|
4
frontend/app/tabs/component.scss
Normal file
4
frontend/app/tabs/component.scss
Normal file
|
@ -0,0 +1,4 @@
|
|||
.tabs {
|
||||
width: 100%;
|
||||
height: 3em;
|
||||
}
|
3
frontend/app/tabs/index.js
Normal file
3
frontend/app/tabs/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
// persea:///workspace
|
||||
// http://api-server.com/workspace
|
||||
// file:///persea-data-dir/workspace
|
23
frontend/app/tabs/tab.js
Normal file
23
frontend/app/tabs/tab.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import {compose} from 'redux';
|
||||
|
||||
import contempo from 'contempo';
|
||||
|
||||
const decorate = compose(
|
||||
contempo(require('./tab.scss'))
|
||||
);
|
||||
|
||||
function Tab({
|
||||
basename,
|
||||
icon,
|
||||
path
|
||||
}) {
|
||||
return <div className="tab">
|
||||
<span className="icon">{icon}</span>
|
||||
<span className="basename">{basename}</span>
|
||||
<span className="path">{path}</span>
|
||||
<span className="close">•</span>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default decorate(Tab);
|
21
frontend/app/tabs/tab.scss
Normal file
21
frontend/app/tabs/tab.scss
Normal file
|
@ -0,0 +1,21 @@
|
|||
.tab {
|
||||
display: flex;
|
||||
height: 3em;
|
||||
float: left;
|
||||
padding: 0.5em;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
|
||||
.close {
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
.icon {
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
.path {
|
||||
padding-left: 0.5em;
|
||||
color: #777777;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
class Welcome extends React.Component {
|
||||
|
||||
render() {
|
||||
return <div className="welcome">
|
||||
<div className="content">
|
||||
<h2>
|
||||
<span>💕 </span>
|
||||
Welcome back to <strong>Persea</strong>
|
||||
<span> 💕</span>
|
||||
</h2>
|
||||
{this.thingsToDo()}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
thingsToDo() {
|
||||
const things = <p>You have nothing to do!</p>;
|
||||
return <div className="things-to-do">{things}</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
import contempo from 'contempo';
|
||||
Welcome = contempo(Welcome, require('./welcome.scss'));
|
||||
|
||||
export default Welcome;
|
|
@ -1,27 +0,0 @@
|
|||
|
||||
.welcome {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
h2 {
|
||||
display: block;
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
|
||||
span {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
background-color: #333;
|
||||
padding: 6em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.things-to-do div {
|
||||
margin-bottom: 1em;
|
||||
}
|
45
frontend/app/workspace.js
Normal file
45
frontend/app/workspace.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import {combineReducers} from 'redux'
|
||||
|
||||
import {createActionTypes, createCall} from './api';
|
||||
|
||||
const types = {
|
||||
...createActionTypes('WORKSPACE_LOAD', '@@persea'),
|
||||
};
|
||||
|
||||
const creators = {
|
||||
load: (uri) => {
|
||||
return createCall(uri, types, 'WORKSPACE_LOAD');
|
||||
},
|
||||
};
|
||||
|
||||
export const actions = {types, creators};
|
||||
|
||||
export const reducer = combineReducers({
|
||||
current: (state = null, action) => {
|
||||
switch (action.type) {
|
||||
case types.WORKSPACE_LOAD_SUCCESS:
|
||||
return action.payload;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
class Duck {
|
||||
|
||||
constructor(namespace) {
|
||||
|
||||
}
|
||||
|
||||
types() {
|
||||
return {};
|
||||
}
|
||||
|
||||
creators() {
|
||||
return {};
|
||||
}
|
||||
|
||||
*saga() {
|
||||
}
|
||||
|
||||
}
|
|
@ -7,18 +7,15 @@ import DockMonitor from 'redux-devtools-dock-monitor';
|
|||
import FilterMonitor from 'redux-devtools-filter-actions';
|
||||
|
||||
const DevTools = createDevTools(
|
||||
|
||||
<DockMonitor
|
||||
defaultIsVisible={false}
|
||||
changePositionKey='ctrl-w'
|
||||
toggleVisibilityKey='ctrl-h'
|
||||
changePositionKey='ctrl-q'
|
||||
>
|
||||
|
||||
<FilterMonitor>
|
||||
|
||||
<LogMonitor
|
||||
theme='tomorrow'
|
||||
/>
|
||||
|
||||
</FilterMonitor>
|
||||
</DockMonitor>
|
||||
);
|
|
@ -1,10 +1,19 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {Provider} from 'react-redux';
|
||||
|
||||
import Root from './root';
|
||||
import createRootStore from './root/store';
|
||||
import App from './app/component';
|
||||
import {create as createStore} from './store';
|
||||
|
||||
const store = createStore();
|
||||
|
||||
// Application start.
|
||||
import {actions} from './app';
|
||||
store.dispatch(actions.creators.start());
|
||||
|
||||
ReactDOM.render(
|
||||
<Root store={createRootStore()} />,
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.querySelector('.root')
|
||||
);
|
||||
|
|
7
frontend/reducer.js
Normal file
7
frontend/reducer.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import {combineReducers} from 'redux'
|
||||
|
||||
import {reducer} from './app';
|
||||
|
||||
export default function createRootReducer() {
|
||||
return reducer;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import React from 'react';
|
||||
import {Provider} from 'react-redux';
|
||||
import {BrowserRouter} from 'react-router-dom';
|
||||
|
||||
import DevTools from './dev-tools';
|
||||
|
||||
import App from '../app';
|
||||
|
||||
function Root ({store}) {
|
||||
|
||||
return <Provider store={store}>
|
||||
<React.Fragment>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
<div className="dev-tools">
|
||||
<DevTools />
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</Provider>;
|
||||
};
|
||||
|
||||
import contempo from 'contempo';
|
||||
Root = contempo(Root, require('./index.scss'));
|
||||
|
||||
export default Root;
|
|
@ -1,6 +0,0 @@
|
|||
if ('production' === process.env.NODE_ENV) {
|
||||
module.exports = require('./index.prod');
|
||||
}
|
||||
else {
|
||||
module.exports = require('./index.dev');
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import React from 'react';
|
||||
import {Provider} from 'react-redux';
|
||||
import {BrowserRouter} from 'react-router-dom';
|
||||
|
||||
import App from '../app';
|
||||
|
||||
function Root ({store}) {
|
||||
|
||||
return <Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</Provider>;
|
||||
};
|
||||
|
||||
import contempo from 'contempo';
|
||||
Root = contempo(Root, require('./index.scss'));
|
||||
|
||||
export default Root;
|
|
@ -1,9 +0,0 @@
|
|||
@media (max-width: 1023px) {
|
||||
.testing {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dev-tools {
|
||||
opacity: 0.9;
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import {combineReducers} from 'redux';
|
||||
import {reducer as formReducer} from 'redux-form/immutable';
|
||||
|
||||
export default function createRootReducer() {
|
||||
|
||||
return combineReducers({
|
||||
form: formReducer,
|
||||
});
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import {all, call} from 'redux-saga/effects'
|
||||
|
||||
export default function createRootSaga() {
|
||||
return function*() {
|
||||
return yield all([
|
||||
].map(call));
|
||||
};
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import createSagaMiddleware from 'redux-saga';
|
||||
import {compose, createStore, applyMiddleware} from 'redux';
|
||||
|
||||
import DevTools from './dev-tools';
|
||||
|
||||
import createRootReducer from './reducer';
|
||||
import createRootSaga from './saga';
|
||||
|
||||
export default function createRootStore() {
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
|
||||
const store = createStore(
|
||||
createRootReducer(),
|
||||
compose(
|
||||
applyMiddleware(sagaMiddleware),
|
||||
DevTools.instrument()
|
||||
)
|
||||
);
|
||||
|
||||
module.hot.accept('./reducer', () => {
|
||||
store.replaceReducer(require('./reducer').default());
|
||||
});
|
||||
|
||||
sagaMiddleware.run(createRootSaga());
|
||||
|
||||
return store;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
import createSagaMiddleware from 'redux-saga';
|
||||
import {compose, createStore, applyMiddleware} from 'redux';
|
||||
|
||||
import createRootReducer from './reducer';
|
||||
import createRootSaga from './saga';
|
||||
|
||||
export default function createRootStore() {
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware()
|
||||
|
||||
const store = createStore(
|
||||
createRootReducer(),
|
||||
compose(
|
||||
applyMiddleware(sagaMiddleware)
|
||||
)
|
||||
);
|
||||
|
||||
sagaMiddleware.run(createRootSaga());
|
||||
|
||||
return store
|
||||
}
|
7
frontend/saga.js
Normal file
7
frontend/saga.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import {all} from 'redux-saga/effects';
|
||||
|
||||
import {saga} from './app';
|
||||
|
||||
export default function createRootSaga() {
|
||||
return saga;
|
||||
}
|
33
frontend/store.dev.js
Normal file
33
frontend/store.dev.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import {compose, createStore, applyMiddleware} from 'redux';
|
||||
import {apiMiddleware} from 'redux-api-middleware';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
|
||||
import DevTools from './dev-tools';
|
||||
|
||||
import {reducer, saga} from './app';
|
||||
|
||||
export function create() {
|
||||
const sagaMiddleware = createSagaMiddleware({
|
||||
onError: (error, options) => {
|
||||
console.error(error);
|
||||
if (options && options.sagaStack) {
|
||||
console.error(`(above error originates from: ${options.sagaStack})`);
|
||||
}
|
||||
}
|
||||
});
|
||||
const middleware = applyMiddleware(apiMiddleware, sagaMiddleware);
|
||||
const enhancer = compose(middleware, DevTools.instrument());
|
||||
const store = createStore(reducer, enhancer);
|
||||
let sagaTask = sagaMiddleware.run(saga);
|
||||
if (module.hot) {
|
||||
module.hot.accept('./app', () => {
|
||||
const {reducer: newReducer, saga: newSaga} = require('./app');
|
||||
store.replaceReducer(newReducer);
|
||||
sagaTask.cancel();
|
||||
sagaTask.done.then(() => {
|
||||
sagaTask = sagaMiddleware.run(newSaga);
|
||||
});
|
||||
});
|
||||
}
|
||||
return store;
|
||||
}
|
23
hooks.js
23
hooks.js
|
@ -1 +1,22 @@
|
|||
module.exports = () => ({});
|
||||
module.exports = () => ({
|
||||
ormCollections: () => {
|
||||
return [
|
||||
{
|
||||
identity: 'project',
|
||||
datastore: 'default',
|
||||
primaryKey: 'id',
|
||||
attributes: {
|
||||
id: {
|
||||
type: 'number',
|
||||
autoMigrations: {
|
||||
autoIncrement: true,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
}
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
|
|
2
index.js
2
index.js
|
@ -1,4 +1,4 @@
|
|||
import {createDispatcher} from '@truss/truss';
|
||||
const {createDispatcher} = require('@truss/comm');
|
||||
|
||||
const dispatcher = createDispatcher();
|
||||
dispatcher.lookupActions(require('./actions'));
|
||||
|
|
14
package.json
14
package.json
|
@ -4,14 +4,17 @@
|
|||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "node -e '' -r '@truss/truss/task/build'",
|
||||
"build": "node -e '' -r '@truss/webpack/task/build'",
|
||||
"default": "yarn run dev",
|
||||
"dev": "node -e '' -r '@truss/truss/task/scaffold'"
|
||||
"dev": "node -e '' -r '@truss/webpack/task/scaffold'"
|
||||
},
|
||||
"author": "cha0s",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@truss/truss": "1.x",
|
||||
"@persea/behavior": "1.x",
|
||||
"@truss/comm": "1.x",
|
||||
"@truss/webpack": "1.x",
|
||||
"babel-plugin-redux-saga": "1.0.2",
|
||||
"classnames": "^2.2.6",
|
||||
"contempo": "1.x",
|
||||
"debug": "3.1.0",
|
||||
|
@ -23,7 +26,9 @@
|
|||
"react-redux": "^5.0.7",
|
||||
"react-router": "^4.3.1",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react-split-pane": "^0.1.84",
|
||||
"redux": "^4.0.0",
|
||||
"redux-api-middleware": "3.0.1",
|
||||
"redux-devtools": "^3.4.1",
|
||||
"redux-devtools-dock-monitor": "^1.1.3",
|
||||
"redux-devtools-filter-actions": "^1.2.2",
|
||||
|
@ -31,6 +36,7 @@
|
|||
"redux-form": "^7.4.2",
|
||||
"redux-saga": "^0.16.0",
|
||||
"webpack-dev-middleware": "3.1.3",
|
||||
"webpack-hot-middleware": "2.22.3"
|
||||
"webpack-hot-middleware": "2.22.3",
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,11 +71,6 @@ exports.Responder = class Responder {
|
|||
}
|
||||
|
||||
respond({payload: {headers, url}}) {
|
||||
headers = {...headers};
|
||||
|
||||
delete headers.host;
|
||||
delete headers.connection;
|
||||
|
||||
// forward to internal HTTP
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
|
|
|
@ -1,55 +1,4 @@
|
|||
html, body, .app, .root {
|
||||
html, body, .root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.aside {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
button {
|
||||
&:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
button, input, select, option {
|
||||
|
||||
&[disabled] {
|
||||
filter: grayscale(1);
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: inset 0 0 1pt 0.5pt #CCC, 0 0 1pt 0.5pt #CCC
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid #333;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.loading {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
svg {
|
||||
height: 25%;
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
.editor.avocado-environment {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -37,16 +37,25 @@ module.exports.webpackConfig = function() {
|
|||
style: [
|
||||
path.join(cssPrefix, styleDirectory, 'reset.css'),
|
||||
path.join(scssPrefix, styleDirectory, 'global.scss'),
|
||||
path.join(scssPrefix, styleDirectory, 'forms.scss'),
|
||||
],
|
||||
},
|
||||
optimization: {},
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
commons: {
|
||||
test: /(node_modules\/(?!contempo|@avocado|@truss))/,
|
||||
name: 'vendor',
|
||||
chunks: 'all',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: [
|
||||
/(node_modules\/(?!contempo))/,
|
||||
/(node_modules\/(?!contempo|@avocado|@truss))/,
|
||||
],
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
|
@ -55,7 +64,13 @@ module.exports.webpackConfig = function() {
|
|||
OUTPUT_PATH,
|
||||
],
|
||||
plugins: [
|
||||
'babel-plugin-redux-saga',
|
||||
['@babel/plugin-proposal-decorators', {
|
||||
legacy: true,
|
||||
}],
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'react-hot-loader/babel',
|
||||
],
|
||||
presets: [
|
||||
|
|
Loading…
Reference in New Issue
Block a user