Commit cd097c45 authored by Jaden's avatar Jaden

refactor state into redux store + trimmings

parent 2e3ecb9e
......@@ -24,7 +24,8 @@
"env": {
"development": {
"plugins": [
"react-hot-loader/babel"
"react-hot-loader/babel",
"flow-react-proptypes"
]
},
"test": {
......
......@@ -12,3 +12,5 @@
[lints]
[options]
module.name_mapper='^@helpers' ->'<PROJECT_ROOT>/src/helpers'
module.name_mapper='^@store' ->'<PROJECT_ROOT>/src/store'
......@@ -897,6 +897,18 @@
"babel-runtime": "6.26.0"
}
},
"babel-plugin-flow-react-proptypes": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-flow-react-proptypes/-/babel-plugin-flow-react-proptypes-6.1.0.tgz",
"integrity": "sha512-h0IvRZGKjcJfCsiamupn5YKV6SRK0SLzKuaR639ehJWHBVvDOpF/2YD6kgk41qtJSwxz7wUmSd8eWqN44cxdrw==",
"dev": true,
"requires": {
"babel-core": "6.26.0",
"babel-template": "6.26.0",
"babel-traverse": "6.26.0",
"babel-types": "6.26.0"
}
},
"babel-plugin-istanbul": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz",
......@@ -3741,33 +3753,15 @@
}
},
"eslint-plugin-compat": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-1.0.4.tgz",
"integrity": "sha512-16yjDdjrivRQT7/Kov+3O6DMvfg8WYC1JKPAsvf/UNtdLBeMXVYATohAM4nOak1ynGP69mKUlOjw7nroUqY9Sg==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-2.0.1.tgz",
"integrity": "sha512-6LtsFQsrFyPGLBVJMnQEmUEY1Sc0Trpz56i/y3qTCA5JKc1KePKS5q5Xcc8LoGgPyeDw3IfnyYqS4Q7vR1Ffeg==",
"dev": true,
"requires": {
"babel-runtime": "6.26.0",
"browserslist": "2.1.4",
"caniuse-db": "1.0.30000671",
"browserslist": "2.4.0",
"caniuse-db": "1.0.30000741",
"requireindex": "1.1.0"
},
"dependencies": {
"browserslist": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.1.4.tgz",
"integrity": "sha1-zFJq9KExK30uBWU+VtDIq3DA4FM=",
"dev": true,
"requires": {
"caniuse-lite": "1.0.30000741",
"electron-to-chromium": "1.3.24"
}
},
"caniuse-db": {
"version": "1.0.30000671",
"resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000671.tgz",
"integrity": "sha1-nwcbvHuWmUY4zLr0eCnVihV3qO0=",
"dev": true
}
}
},
"eslint-plugin-flowtype": {
......@@ -8596,9 +8590,9 @@
}
},
"postcss-loader": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.0.6.tgz",
"integrity": "sha512-HIq7yy1hh9KI472Y38iSRV4WupZUNy6zObkxQM/ZuInoaE2+PyX4NcO6jjP5HG5mXL7j5kcNEl0fAG4Kva7O9w==",
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.0.7.tgz",
"integrity": "sha512-eTPw6KbYiYpLJsTtevT4kX1aDL7HuSNmMWKLz/HascJClww/KJ3AWtMOY/z+sQd14F3SnIA/WE208/S7JDOzEQ==",
"dev": true,
"requires": {
"loader-utils": "1.1.0",
......@@ -9870,9 +9864,9 @@
}
},
"react-hot-loader": {
"version": "3.0.0-beta.7",
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-3.0.0-beta.7.tgz",
"integrity": "sha1-1YR7gWXXMcTVsw2G1dRxYieg+oM=",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-3.0.0.tgz",
"integrity": "sha512-5CWQPoaZ7lQ2GXzBv2A+6RH7iJxc4drxx/EPUIpRR0jP9tDsZb/6eBOAJL2OjONYsODRWhECLgmbvupvQHqY7g==",
"dev": true,
"requires": {
"babel-template": "6.26.0",
......@@ -9880,17 +9874,14 @@
"react-deep-force-update": "2.1.1",
"react-proxy": "3.0.0-alpha.1",
"redbox-react": "1.5.0",
"source-map": "0.4.4"
"source-map": "0.6.1"
},
"dependencies": {
"source-map": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
"dev": true,
"requires": {
"amdefine": "1.0.1"
}
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
}
},
......@@ -10186,6 +10177,11 @@
"symbol-observable": "1.0.4"
}
},
"redux-thunk": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.2.0.tgz",
"integrity": "sha1-5hWhbha0ehmlFXZhM9Hj6Zt4UuU="
},
"regenerate": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz",
......
......@@ -18,6 +18,10 @@ import {
TabContent, TabPane, Nav, NavItem, NavLink, CardTitle, CardText,
} from 'reactstrap';
import { connect } from 'react-redux';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
import * as Selectors from '@store/selectors.js';
import {
Route,
......@@ -26,33 +30,46 @@ import {
import cn from 'classnames';
import EntityDetail, { mapParamsToData } from './EntityDetail.jsx';
import { objToArr } from './EntityList.jsx';
type Props = {
match: any,
data: any,
allData: any,
libraries: any,
dataformats: any,
};
type State = {
activeTab: string,
cache: any,
dfs: any[],
resultDfs: string[],
};
const orderedParams = (params: any): string[] => [ params.user, params.name, params.version ];
const findAlg = (algs, nameStr) => algs.find(a => a.name === nameStr) || {};
const mapStateToProps: MapStateToProps<*,*,*> = (state, ownProps) => {
const paramName = orderedParams(ownProps.match.params).join('/');
//console.log(`mapStateToProps ${ paramName }`);
const obj = {
data: findAlg(Selectors.algorithmGet(state), paramName),
libraries: Selectors.libraryGet(state),
dataformats: Selectors.dataformatGet(state),
};
//console.log(`mapStateToProps found data: "${ JSON.stringify(obj.data) }"`);
return obj;
};
const getDescField = (obj) => obj && obj['#description'] ? '#description' : 'description';
const getValidObj = ({ match, data }: Props) => {
const getValidObj = (data = {}) => {
//console.log(`Calling getValidObj with data: "${ JSON.stringify(data) }"`);
const getObj = {
name: '',
contents: {},
...JSON.parse(JSON.stringify(mapParamsToData(orderedParams(match.params), data)))
...JSON.parse(JSON.stringify(data))
};
return {
name: '',
...getObj,
contents: {
splittable: false,
......@@ -60,8 +77,8 @@ const getValidObj = ({ match, data }: Props) => {
uses: {},
groups: [
],
[getDescField(getObj.contents)]: '',
...getObj.contents,
[getDescField(getObj.contents)]: getObj.contents[getDescField(getObj.contents)] || ''
},
};
};
......@@ -142,15 +159,21 @@ const resultDfs = [
class AlgorithmsDetail extends React.Component<Props, State> {
constructor(props: Props) {
//console.log(`Creating AlgDetail`);
super(props);
}
state = {
activeTab: '0',
cache: getValidObj(this.props),
dfs: objToArr(this.props.allData.dataformats) || [],
libs: objToArr(this.props.allData.libraries) || [],
resultDfs: resultDfs.concat((objToArr(this.props.allData.dataformats) || []).filter(d => d.name.startsWith('plot')).map(d => d.name)),
cache: getValidObj(this.props.data),
resultDfs: resultDfs.concat(this.props.dataformats.filter(d => d.name.startsWith('plot')).map(d => d.name)),
}
componentWillReceiveProps (nextProps) {
this.setState({
cache: getValidObj(nextProps.data),
resultDfs: resultDfs.concat(nextProps.dataformats.filter(d => d.name.startsWith('plot')).map(d => d.name)),
});
}
tabTo = (tab: string) => { if(this.activeTab !== tab) this.setState({ activeTab: tab }); }
......@@ -325,7 +348,7 @@ class AlgorithmsDetail extends React.Component<Props, State> {
<TabContent className='mt-3' activeTab={this.state.activeTab}>
<TabPane tabId='0'> { this.state.cache.contents.groups.map((group, i) => {
const ioUpdate = (subObj: string) => (oldName: string, newName: string, newObj: any) => {
console.log(`changing ${subObj} "${oldName}" -> "${newName}":"${ newObj.type }"`);
console.log(`changing ${ subObj } "${ oldName }" -> "${ newName }":"${ newObj.type }"`);
const changedObj = changeObjFieldName(group[subObj], oldName, newName);
changedObj[newName] = newObj;
this.updateGroup(group,
......@@ -373,7 +396,7 @@ class AlgorithmsDetail extends React.Component<Props, State> {
key={i}
name={name}
ioObj={ioObj}
dfs={this.state.dfs}
dfs={this.props.dataformats}
updateFunc={inputsUpdate}
deleteFunc={this.deleteIO(group, 'inputs')}
/>
......@@ -407,7 +430,7 @@ class AlgorithmsDetail extends React.Component<Props, State> {
key={i}
name={name}
ioObj={ioObj}
dfs={this.state.dfs}
dfs={this.props.dataformats}
updateFunc={outputsUpdate}
deleteFunc={this.deleteIO(group, 'outputs')}
/>
......@@ -549,7 +572,7 @@ class AlgorithmsDetail extends React.Component<Props, State> {
key={i}
name={name}
ioObj={lib}
dfs={this.state.libs}
dfs={this.props.libraries}
updateFunc={(oldName, newName, obj) => {
const libs = this.state.cache.contents.uses;
delete libs[oldName];
......@@ -676,4 +699,4 @@ class AlgorithmsDetail extends React.Component<Props, State> {
}
}
export default AlgorithmsDetail;
export default connect(mapStateToProps)(AlgorithmsDetail);
......@@ -13,61 +13,24 @@ import {
import MainNav from './MainNav.jsx';
import MainContent from './MainContent.jsx';
import * as Api from './api.js';
import { objToArr } from './EntityList.jsx';
type Props = {
};
type State = {
databases: any,
libraries: any,
dataformats: any,
algorithms: any,
toolchains: any,
experiments: any,
};
const dataMainComponent = (data: State) => {
const Wrapper = (props: any) => (<MainContent {...props} {...data} />);
return Wrapper;
};
const dataNavComponent = (allLists: State) => {
const Wrapper = (props: any) => (<MainNav {...props} allLists={allLists} />);
return Wrapper;
};
class App extends React.Component<Props, State> {
state = {
databases: {},
libraries: {},
dataformats: {},
algorithms: {},
toolchains: {},
experiments: {},
class App extends React.PureComponent<Props, State> {
constructor(props: Props){
super(props);
}
componentDidMount () {
Object.values(Api.BEAT_ENTITIES)
.forEach((e: any, i) =>
Api.genModuleApiFuncs(e).get()
.then(json => this.setState({ [e]: json }))
);
}
getAllLists = (): any => Object.entries(this.state).map(([e, obj]) => [e, objToArr(obj)]).reduce((o, [e, arr]) => ({...o, [e]: arr}), {});
render () {
const Main = dataMainComponent(this.state);
const Nav = dataNavComponent(this.getAllLists());
return (
<Router>
<Container>
<Route component={Nav} />
<Route path='/:entity' component={Main} />
<Route component={MainNav} />
<Route path='/:entity' component={MainContent} />
</Container>
</Router>
);
......
......@@ -18,6 +18,8 @@ import {
Route,
} from 'react-router-dom';
import type { BeatEntity } from '@helpers/beat.js';
import EntityList from './EntityList.jsx';
import NewEntityModal from './NewEntityModal.jsx';
......@@ -29,9 +31,7 @@ import ToolchainsDetail from './ToolchainsDetail.jsx';
import ExperimentsDetail from './ExperimentsDetail.jsx';
type Props = {
entity: string,
data: any,
allData: any,
entity: BeatEntity,
};
const detailPaths = new Map([
......@@ -48,7 +48,7 @@ type State = {
newOpen: boolean,
};
class EntityHome extends React.PureComponent<Props, State> {
class EntityHome extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
}
......@@ -67,9 +67,8 @@ class EntityHome extends React.PureComponent<Props, State> {
return (
<Switch>
{ Array.from(detailPaths).map(([path, C], i) => {
const Detail = ({ match }: any) => <C data={this.props.data} match={match} allData={this.props.allData} />;
return (
<Route key={i} exact path={path} component={Detail} />
<Route key={i} exact path={path} component={C} />
);
})}
<Route>
......@@ -81,7 +80,7 @@ class EntityHome extends React.PureComponent<Props, State> {
<div className='ml-auto'>
<Button outline color='primary' onClick={this.toggleNew}>New</Button>
<NewEntityModal
titleStr={`New ${this.props.entity}`}
titleStr={`New ${ this.props.entity }`}
toggle={this.toggleNew}
isOpen={this.state.newOpen}
save={this.saveNew}
......@@ -92,7 +91,7 @@ class EntityHome extends React.PureComponent<Props, State> {
</Row>
<Row>
<Col>
<EntityList data={this.props.data} entity={this.props.entity} />
<EntityList entity={this.props.entity} />
</Col>
</Row>
</Container>
......
......@@ -14,22 +14,19 @@ import {
Badge,
} from 'reactstrap';
import {
Route,
Link
} from 'react-router-dom';
import Validate, { VALIDATORS } from './schema';
import { connect } from 'react-redux';
import type { MapStateToProps, MapDispatchToProps } from 'react-redux';
const objToArr = (data: any): any[] => {
if(Array.isArray(data))
return data;
return Object.entries(data)
.map(([k, obj]) => objToArr(obj))
.reduce((a, objs) => [...a, ...objs], [])
;
};
import * as Selectors from '@store/selectors.js';
import { BEAT_ENTITIES, entityToSingular } from '@helpers/beat.js';
import Validate, { VALIDATORS } from '@helpers/schema';
const El = ({ data, entity }: any) => {
const isValid = Validate(VALIDATORS[entity], data);
......@@ -59,13 +56,14 @@ type Props = {
data: any
};
const mapStateToProps: MapStateToProps<*,*,*> = (state, ownProps) => ({
data: Selectors[`${ entityToSingular(ownProps.entity) }Get`](state),
});
const EntityList = ({ data, entity }: Props) => (
<ListGroup>
{ objToArr(data).map((d, i) => <El key={i} data={d} entity={entity} />) }
{ data.map((d, i) => <El key={i} data={d} entity={entity} />) }
</ListGroup>
);
export {
objToArr
};
export default EntityList;
export default connect(mapStateToProps)(EntityList);
......@@ -16,52 +16,12 @@ import EntityHome from './EntityHome.jsx';
type Props = {
match: any,
databases: any,
libraries: any,
dataformats: any,
algorithms: any,
toolchains: any,
experiments: any,
};
class MainContent extends React.PureComponent<Props> {
constructor(props: Props) {
super(props);
}
dataWrapper = (data: any) => (C: React.ComponentType<any>): any => { return () => <C data={data} />; }
db = (C: React.ComponentType<any>) => {
return this.dataWrapper(this.props.databases)(C);
}
lib = (C: React.ComponentType<any>) => {
return this.dataWrapper(this.props.libraries)(C);
}
df = (C: React.ComponentType<any>) => {
return this.dataWrapper(this.props.dataformats)(C);
}
alg = (C: React.ComponentType<any>) => {
return this.dataWrapper(this.props.algorithms)(C);
}
tc = (C: React.ComponentType<any>) => {
return this.dataWrapper(this.props.toolchains)(C);
}
exp = (C: React.ComponentType<any>) => {
return this.dataWrapper(this.props.experiments)(C);
}
render () {
return (
<div>
<EntityHome entity={this.props.match.params.entity} data={this.props[this.props.match.params.entity]} allData={this.props} />
</div>
);
}
}
const MainContent = ({ match }: Props) => (
<div>
<EntityHome entity={match.params.entity} />
</div>
);
export default MainContent;
......@@ -15,21 +15,22 @@ import {
NavLink as RLink,
} from 'react-router-dom';
import Settings from './Settings.jsx';
import { connect } from 'react-redux';
import type { MapStateToProps } from 'react-redux';
import * as Selectors from '@store/selectors';
import * as Api from './api.js';
import Settings from './Settings.jsx';
const saveAll = (allLists: any) => {
return Promise.all(
Object.values(Api.BEAT_ENTITIES).map(e => {
const post = Api.genModuleApiFuncs(e).post;
return post(allLists[e]);
})
);
};
import { BEAT_ENTITIES, entityToSingular } from '@helpers/beat.js';
import { genModuleApiFuncs } from '@helpers/api.js';
type Props = {
allLists: any
databases: any[],
libraries: any[],
dataformats: any[],
algorithms: any[],
toolchains: any[],
experiments: any[],
};
type State = {
......@@ -37,7 +38,28 @@ type State = {
isSaving: boolean,
};
export default class MainNav extends React.PureComponent<Props, State> {
const saveAll = (allLists: Props) => {
return Promise.all(
BEAT_ENTITIES
.filter(e => Array.isArray(allLists[e]))
.map(e => {
const post = genModuleApiFuncs(e).post;
return post(allLists[e]);
})
);
};
const mapStateToProps: MapStateToProps<*,*,*> = state => ({
databases: Selectors.databaseGet(state),
libraries: Selectors.libraryGet(state),
dataformats: Selectors.dataformatGet(state),
algorithms: Selectors.algorithmGet(state),
toolchains: Selectors.toolchainGet(state),
experiments: Selectors.experimentGet(state),
});
class MainNav extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
}
......@@ -58,7 +80,7 @@ export default class MainNav extends React.PureComponent<Props, State> {
this.setState({
isSaving: true
});
const res = await saveAll(this.props.allLists);
const res = await saveAll(this.props);
this.setState({
isSaving: false
});
......@@ -73,7 +95,7 @@ export default class MainNav extends React.PureComponent<Props, State> {
<NavbarBrand href="/">beat.editor</NavbarBrand>
<Nav className="mr-auto" navbar>
{
Object.values(Api.BEAT_ENTITIES)
BEAT_ENTITIES
.map((e, i) => {
return (
<NavItem key={i}>
......@@ -102,3 +124,4 @@ export default class MainNav extends React.PureComponent<Props, State> {
}
}
export default connect(mapStateToProps)(MainNav);
const BEAT_ENTITIES = {
DATAFORMAT : 'dataformats',
DATABASE : 'databases',
LIBRARY : 'libraries',
ALGORITHM : 'algorithms',
TOOLCHAIN : 'toolchains',
EXPERIMENT : 'experiments',
};
Object.freeze(BEAT_ENTITIES);
// @flow
import type { BeatEntity } from './beat.js';
const fetch_config = {
mode: 'cors'
......@@ -17,10 +9,10 @@ let port = '5000';
const toUrl = (be) => `http://127.0.0.1:${ port }/${ be }`;
const genApiCall = (method) => (be) => async (obj) => {
const genApiCall = (method) => (be: BeatEntity) => async (obj: any) => {
const validMethods = ['GET', 'POST', 'PUT', 'DELETE'];
if(!validMethods.includes(method))
throw new Exception(`invalid method: ${ method }`);
throw new Error(`invalid method: ${ method }`);
const headers = new Headers();
if(method !== 'GET')
headers.append('Content-Type', 'application/json');
......@@ -33,21 +25,17 @@ const genApiCall = (method) => (be) => async (obj) => {
}
};
try {
let res = await fetch(toUrl(be), config);
let json = await res.json();
console.log(json);
return json;
} catch (e) {
console.error(e.message);
}
let res = await fetch(toUrl(be), config);
let json = await res.json();
console.log(json);
return json;
};
const setPort = (newPort) => {
const setPort = (newPort: number) => {
port = newPort;
};
const genModuleApiFuncs = (be) => {
const genModuleApiFuncs = (be: BeatEntity) => {
return {
get: genApiCall('GET')(be),
post: genApiCall('POST')(be),
......@@ -59,5 +47,4 @@ const genModuleApiFuncs = (be) => {
export {
genModuleApiFuncs,
setPort,