mirror of
https://github.com/yldio/copilot.git
synced 2024-11-13 06:40:06 +02:00
Merge pull request #25 from yldio/ramos/stacks-spike
update stack spike code
This commit is contained in:
commit
a433daaeb0
15
spikes/stacks/redux-loop/.babelrc
Normal file
15
spikes/stacks/redux-loop/.babelrc
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"presets": [
|
||||
"react",
|
||||
"es2015"
|
||||
],
|
||||
"plugins": [
|
||||
["transform-object-rest-spread", {
|
||||
"useBuiltIns": true
|
||||
}],
|
||||
"add-module-exports",
|
||||
"transform-es2015-modules-commonjs",
|
||||
"react-hot-loader/babel"
|
||||
],
|
||||
"sourceMaps": "both"
|
||||
}
|
3
spikes/stacks/redux-loop/.eslintignore
Normal file
3
spikes/stacks/redux-loop/.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
/node_modules
|
||||
coverage
|
||||
.nyc_output
|
29
spikes/stacks/redux-loop/.eslintrc
Normal file
29
spikes/stacks/redux-loop/.eslintrc
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"extends": "semistandard",
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 7,
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"babel",
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"generator-star-spacing": 0,
|
||||
"babel/generator-star-spacing": 1,
|
||||
"space-before-function-paren": [2, "never"],
|
||||
"react/jsx-uses-react": 2,
|
||||
"react/jsx-uses-vars": 2,
|
||||
"react/react-in-jsx-scope": 2,
|
||||
"object-curly-newline": ["error", {
|
||||
"minProperties": 1
|
||||
}],
|
||||
"sort-vars": ["error", {
|
||||
"ignoreCase": true
|
||||
}]
|
||||
}
|
||||
}
|
4
spikes/stacks/redux-loop/.gitignore
vendored
Normal file
4
spikes/stacks/redux-loop/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/node_modules
|
||||
coverage
|
||||
.nyc_output
|
||||
npm-debug.log
|
5
spikes/stacks/redux-loop/.storybook/config.js
Normal file
5
spikes/stacks/redux-loop/.storybook/config.js
Normal file
@ -0,0 +1,5 @@
|
||||
const Storybook = require('@kadira/storybook');
|
||||
|
||||
Storybook.configure(() => {
|
||||
require('../stories');
|
||||
}, module);
|
75
spikes/stacks/redux-loop/package.json
Normal file
75
spikes/stacks/redux-loop/package.json
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "redux-loop-spike",
|
||||
"private": true,
|
||||
"license": "private",
|
||||
"main": "src/server/index.js",
|
||||
"scripts": {
|
||||
"start": "node .",
|
||||
"lint": "eslint .",
|
||||
"test": "NODE_ENV=test nyc ava test/*.js --fail-fast --verbose --tap",
|
||||
"open": "nyc report --reporter=html & open coverage/index.html",
|
||||
"coverage": "nyc check-coverage --statements 100 --functions 100 --lines 100 --branches 100",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"build-storybook": "build-storybook"
|
||||
},
|
||||
"dependencies": {
|
||||
"aphrodite": "^0.6.0",
|
||||
"babel-eslint": "^7.0.0",
|
||||
"babel-loader": "^6.2.5",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"babel-plugin-transform-es2015-modules-commonjs": "^6.16.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.16.0",
|
||||
"babel-plugin-transform-runtime": "^6.15.0",
|
||||
"babel-preset-es2015": "^6.16.0",
|
||||
"babel-preset-react": "^6.16.0",
|
||||
"babel-preset-react-hmre": "^1.1.1",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"component-emitter": "^1.2.1",
|
||||
"crosstab": "^0.2.12",
|
||||
"express-graphql": "^0.5.4",
|
||||
"graphql": "^0.7.2",
|
||||
"graphql-fetch": "^1.0.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"lodash.find": "^4.6.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.values": "^4.3.0",
|
||||
"node-uuid": "^1.4.7",
|
||||
"react": "^15.3.2",
|
||||
"react-dom": "^15.3.2",
|
||||
"react-hot-loader": "^3.0.0-beta.6",
|
||||
"react-intl": "^2.1.5",
|
||||
"react-redux": "^4.4.5",
|
||||
"react-router": "^4.0.0-alpha.4",
|
||||
"reduce-reducers": "^0.1.2",
|
||||
"redux": "^3.6.0",
|
||||
"redux-actions": "^0.12.0",
|
||||
"redux-batched-actions": "^0.1.3",
|
||||
"redux-logger": "^2.7.0",
|
||||
"redux-loop": "^2.2.2",
|
||||
"redux-promise-middleware": "^4.1.0",
|
||||
"reselect": "^2.5.4",
|
||||
"webpack": "^1.13.2",
|
||||
"webpack-dev-server": "^1.16.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kadira/storybook": "^2.24.0",
|
||||
"ava": "^0.16.0",
|
||||
"babel-register": "^6.16.3",
|
||||
"enzyme": "^2.4.1",
|
||||
"eslint": "^3.8.0",
|
||||
"eslint-config-semistandard": "^7.0.0",
|
||||
"eslint-config-standard": "^6.2.0",
|
||||
"eslint-plugin-babel": "^3.3.0",
|
||||
"eslint-plugin-promise": "^3.0.0",
|
||||
"eslint-plugin-react": "^6.4.1",
|
||||
"eslint-plugin-standard": "^2.0.1",
|
||||
"nyc": "^8.3.1",
|
||||
"react-addons-test-utils": "^15.3.2"
|
||||
},
|
||||
"ava": {
|
||||
"require": [
|
||||
"babel-register"
|
||||
],
|
||||
"babel": "inherit"
|
||||
}
|
||||
}
|
49
spikes/stacks/redux-loop/src/client/api.js
Normal file
49
spikes/stacks/redux-loop/src/client/api.js
Normal file
@ -0,0 +1,49 @@
|
||||
var fetch = require('graphql-fetch')(`${document.location.origin}/graphql`);
|
||||
|
||||
exports.fetchChanges = () => {
|
||||
return fetch(`
|
||||
query {
|
||||
changes {
|
||||
id,
|
||||
product {
|
||||
id,
|
||||
artist,
|
||||
title,
|
||||
label,
|
||||
format,
|
||||
price,
|
||||
currency
|
||||
},
|
||||
price,
|
||||
currency
|
||||
}
|
||||
}
|
||||
`).then(({
|
||||
data
|
||||
}) => {
|
||||
return data.changes;
|
||||
});
|
||||
};
|
||||
|
||||
exports.removeChange = (id) => {
|
||||
console.log(`
|
||||
mutation {
|
||||
removeChange(id: "${id}")
|
||||
}
|
||||
`);
|
||||
return fetch(`
|
||||
mutation {
|
||||
removeChange(id: "${id}") {
|
||||
id
|
||||
}
|
||||
}
|
||||
`).then(({
|
||||
errors
|
||||
}) => {
|
||||
if (!errors) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(errors[0].message);
|
||||
});
|
||||
};
|
14
spikes/stacks/redux-loop/src/client/components/change.js
Normal file
14
spikes/stacks/redux-loop/src/client/components/change.js
Normal file
@ -0,0 +1,14 @@
|
||||
const React = require('react');
|
||||
|
||||
module.exports = ({
|
||||
price,
|
||||
currency,
|
||||
product,
|
||||
id
|
||||
}) => {
|
||||
return (
|
||||
<p>
|
||||
{product.artist}: {product.title} - {product.currencr}{product.price} > {currency}{price}
|
||||
</p>
|
||||
);
|
||||
};
|
32
spikes/stacks/redux-loop/src/client/components/changes.js
Normal file
32
spikes/stacks/redux-loop/src/client/components/changes.js
Normal file
@ -0,0 +1,32 @@
|
||||
const ReactRouter = require('react-router');
|
||||
const React = require('react');
|
||||
|
||||
const {
|
||||
Link
|
||||
} = ReactRouter;
|
||||
|
||||
module.exports = ({
|
||||
changes = [],
|
||||
pathname
|
||||
}) => {
|
||||
const lis = changes.map(({
|
||||
price,
|
||||
currency,
|
||||
product,
|
||||
id
|
||||
}) => {
|
||||
return (
|
||||
<li key={id}>
|
||||
<Link to={`${pathname}/${id}`}>
|
||||
{product.artist}: {product.title} - {product.currencr}{product.price} > {currency}{price}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{lis}
|
||||
</ul>
|
||||
);
|
||||
};
|
51
spikes/stacks/redux-loop/src/client/components/loader.js
Normal file
51
spikes/stacks/redux-loop/src/client/components/loader.js
Normal file
@ -0,0 +1,51 @@
|
||||
const React = require('react');
|
||||
const Loader = require('react-loader');
|
||||
|
||||
module.exports = React.createClass({
|
||||
fetch: function() {
|
||||
const {
|
||||
fetch,
|
||||
loading,
|
||||
loaded
|
||||
} = this.props;
|
||||
|
||||
if (fetch && !loading && !loaded) {
|
||||
fetch();
|
||||
}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this.fetch();
|
||||
},
|
||||
componentDidUpdate: function(nextProps) {
|
||||
const updated = (
|
||||
nextProps.loaded !== this.props.loaded &&
|
||||
nextProps.loading !== this.props.loading
|
||||
);
|
||||
|
||||
if (!updated) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fetch();
|
||||
},
|
||||
render: function() {
|
||||
const {
|
||||
loading,
|
||||
loaded,
|
||||
render,
|
||||
children
|
||||
} = this.props;
|
||||
|
||||
const _loaded = !loading && !loaded;
|
||||
const component = _loaded ? (children || render()) : null;
|
||||
|
||||
return (
|
||||
<Loader
|
||||
{...this.props}
|
||||
loaded={_loaded}
|
||||
>
|
||||
{component}
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
});
|
39
spikes/stacks/redux-loop/src/client/components/printers.js
Normal file
39
spikes/stacks/redux-loop/src/client/components/printers.js
Normal file
@ -0,0 +1,39 @@
|
||||
const React = require('react');
|
||||
|
||||
module.exports = ({
|
||||
printers = [],
|
||||
locked = '',
|
||||
onClick
|
||||
}) => {
|
||||
const _onClick = (id) => {
|
||||
return () => {
|
||||
onClick(id);
|
||||
};
|
||||
};
|
||||
|
||||
const lis = printers.map(({
|
||||
name,
|
||||
lock,
|
||||
id
|
||||
}) => {
|
||||
const msg = (() => {
|
||||
if (!lock) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (locked === id) ? '(Locked to you)' : `(Locked to ${lock})`;
|
||||
})();
|
||||
|
||||
return (
|
||||
<li key={id}>
|
||||
<a onClick={_onClick(id)}>{name} {msg}</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{lis}
|
||||
</ul>
|
||||
);
|
||||
};
|
55
spikes/stacks/redux-loop/src/client/containers/app.js
Normal file
55
spikes/stacks/redux-loop/src/client/containers/app.js
Normal file
@ -0,0 +1,55 @@
|
||||
const ReactRedux = require('react-redux');
|
||||
const ReactRouter = require('react-router');
|
||||
const React = require('react');
|
||||
|
||||
const NotFound = require('./not-found');
|
||||
const Home = require('./home');
|
||||
const Print = require('./print');
|
||||
|
||||
const actions = require('../state/actions');
|
||||
|
||||
const {
|
||||
connect
|
||||
} = ReactRedux;
|
||||
|
||||
const {
|
||||
Miss,
|
||||
Match
|
||||
} = ReactRouter;
|
||||
|
||||
const {
|
||||
updateRouter
|
||||
} = actions;
|
||||
|
||||
const App = connect()(React.createClass({
|
||||
componentDidMount: function() {
|
||||
require('../worker').on('action', this.props.dispatch);
|
||||
},
|
||||
render: function() {
|
||||
const {
|
||||
children,
|
||||
router,
|
||||
dispatch
|
||||
} = this.props;
|
||||
|
||||
// ugly hack needed because of a limitation of react-router api
|
||||
// that doens't pass it's instance to matched routes
|
||||
dispatch(updateRouter(router));
|
||||
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
module.exports = (props) => {
|
||||
return (
|
||||
<App {...props}>
|
||||
<Match exactly pattern='/' component={Home} />
|
||||
<Match pattern='/print' component={Print} />
|
||||
<Miss component={NotFound}/>
|
||||
</App>
|
||||
);
|
||||
};
|
14
spikes/stacks/redux-loop/src/client/containers/home.js
Normal file
14
spikes/stacks/redux-loop/src/client/containers/home.js
Normal file
@ -0,0 +1,14 @@
|
||||
const ReactRouter = require('react-router');
|
||||
const React = require('react');
|
||||
|
||||
const {
|
||||
Link
|
||||
} = ReactRouter;
|
||||
|
||||
module.exports = () => {
|
||||
return (
|
||||
<div>
|
||||
<Link to='/print'>Start</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
const React = require('react');
|
||||
|
||||
module.exports = () => {
|
||||
return (
|
||||
<h1>Not found</h1>
|
||||
);
|
||||
};
|
136
spikes/stacks/redux-loop/src/client/containers/print.js
Normal file
136
spikes/stacks/redux-loop/src/client/containers/print.js
Normal file
@ -0,0 +1,136 @@
|
||||
const ReactRouter = require('react-router');
|
||||
const ReactRedux = require('react-redux');
|
||||
const React = require('react');
|
||||
const find = require('lodash.find');
|
||||
|
||||
const Loader = require('../components/loader');
|
||||
const Printers = require('../components/printers');
|
||||
const Changes = require('../components/changes');
|
||||
const Change = require('../components/change');
|
||||
|
||||
const actions = require('../state/actions');
|
||||
|
||||
const {
|
||||
fetchChanges,
|
||||
lockPrinter,
|
||||
print,
|
||||
transitionTo
|
||||
} = actions;
|
||||
|
||||
const {
|
||||
Match
|
||||
} = ReactRouter;
|
||||
|
||||
const {
|
||||
connect
|
||||
} = ReactRedux;
|
||||
|
||||
const Print = ({
|
||||
pathname,
|
||||
printers = [],
|
||||
changes = [],
|
||||
lockPrinter,
|
||||
fetchChanges,
|
||||
onPrint,
|
||||
loaded,
|
||||
loading,
|
||||
locked,
|
||||
router
|
||||
}) => {
|
||||
const allChanges = () => {
|
||||
return (
|
||||
<div>
|
||||
<p>Changes</p>
|
||||
<Loader
|
||||
loaded={loaded}
|
||||
loading={loading}
|
||||
fetch={fetchChanges}
|
||||
>
|
||||
<Changes
|
||||
pathname={pathname}
|
||||
changes={changes}
|
||||
/>
|
||||
</Loader>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const singleChange = ({
|
||||
params
|
||||
}) => {
|
||||
const change = find(changes, (change) => {
|
||||
return change.id === params.id;
|
||||
});
|
||||
|
||||
if (!change) {
|
||||
return (
|
||||
<p>Change not found</p>
|
||||
);
|
||||
}
|
||||
|
||||
const _onPrint = (id) => {
|
||||
return () => {
|
||||
return onPrint(id);
|
||||
};
|
||||
};
|
||||
|
||||
// TODO: don't load all changes
|
||||
return (
|
||||
<div>
|
||||
<p>Change</p>
|
||||
<Loader
|
||||
loaded={loaded}
|
||||
loading={loading}
|
||||
fetch={fetchChanges}
|
||||
>
|
||||
<Change {...change} />
|
||||
</Loader>
|
||||
<button onClick={_onPrint(params.id)}>Print</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<p>Printers</p>
|
||||
<Printers
|
||||
printers={printers}
|
||||
onClick={lockPrinter}
|
||||
locked={locked}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Match pattern={`${pathname}/:id`} render={singleChange} />
|
||||
<Match exactly pattern={pathname} render={allChanges} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
loaded: state.ui.changes.loaded,
|
||||
loading: state.ui.changes.loading,
|
||||
changes: state.data.changes,
|
||||
printers: state.data.printers,
|
||||
locked: state.ui.printers.locked
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
lockPrinter: (id) => {
|
||||
return dispatch(lockPrinter(id));
|
||||
},
|
||||
fetchChanges: () => {
|
||||
return dispatch(fetchChanges());
|
||||
},
|
||||
onPrint: (id) => {
|
||||
return dispatch(print(id)).then(() => {
|
||||
return dispatch(transitionTo('/print'));
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = connect(mapStateToProps, mapDispatchToProps)(Print);
|
19
spikes/stacks/redux-loop/src/client/index.js
Normal file
19
spikes/stacks/redux-loop/src/client/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
require('./worker'); // singleton
|
||||
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
|
||||
const render = () => {
|
||||
const Root = require('./root');
|
||||
|
||||
ReactDOM.render(
|
||||
<Root />,
|
||||
document.getElementById('root')
|
||||
);
|
||||
};
|
||||
|
||||
render();
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./root', render);
|
||||
}
|
3
spikes/stacks/redux-loop/src/client/intl.json
Normal file
3
spikes/stacks/redux-loop/src/client/intl.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
|
||||
}
|
46
spikes/stacks/redux-loop/src/client/root.js
Normal file
46
spikes/stacks/redux-loop/src/client/root.js
Normal file
@ -0,0 +1,46 @@
|
||||
const React = require('react');
|
||||
const ReactHotLoader = require('react-hot-loader');
|
||||
const ReactRouter = require('react-router');
|
||||
const ReactRedux = require('react-redux');
|
||||
const ReactIntl = require('react-intl');
|
||||
|
||||
const App = require('./containers/app');
|
||||
const store = require('./state/store');
|
||||
|
||||
const {
|
||||
AppContainer
|
||||
} = ReactHotLoader;
|
||||
|
||||
const {
|
||||
BrowserRouter
|
||||
} = ReactRouter;
|
||||
|
||||
const {
|
||||
Provider
|
||||
} = ReactRedux;
|
||||
|
||||
const {
|
||||
IntlProvider
|
||||
} = ReactIntl;
|
||||
|
||||
// http://stackoverflow.com/a/38150585
|
||||
const locale = (
|
||||
navigator.languages && navigator.languages[0] || // Chrome / Firefox
|
||||
navigator.language || // All browsers
|
||||
navigator.userLanguage // IE <= 10
|
||||
);
|
||||
|
||||
module.exports = () => {
|
||||
return (
|
||||
<AppContainer>
|
||||
<Provider store={store()}>
|
||||
<IntlProvider
|
||||
locale={locale}
|
||||
defaultLocale='en'
|
||||
>
|
||||
<BrowserRouter>{App}</BrowserRouter>
|
||||
</IntlProvider>
|
||||
</Provider>
|
||||
</AppContainer>
|
||||
);
|
||||
};
|
25
spikes/stacks/redux-loop/src/client/state/actions/app.js
Normal file
25
spikes/stacks/redux-loop/src/client/state/actions/app.js
Normal file
@ -0,0 +1,25 @@
|
||||
const ReduxActions = require('redux-actions');
|
||||
const app = require('../../../../package.json').name;
|
||||
const selectors = require('../selectors');
|
||||
|
||||
const {
|
||||
router
|
||||
} = selectors;
|
||||
|
||||
const {
|
||||
createAction
|
||||
} = ReduxActions;
|
||||
|
||||
const UPDATE_ROUTER = `${app}/changes/UPDATE_ROUTER`;
|
||||
|
||||
const updateRouter = createAction(UPDATE_ROUTER);
|
||||
|
||||
const transitionTo = (pathname) => (dispatch, getState) => {
|
||||
return router(getState()).transitionTo(pathname);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
UPDATE_ROUTER,
|
||||
updateRouter,
|
||||
transitionTo
|
||||
};
|
28
spikes/stacks/redux-loop/src/client/state/actions/changes.js
Normal file
28
spikes/stacks/redux-loop/src/client/state/actions/changes.js
Normal file
@ -0,0 +1,28 @@
|
||||
const app = require('../../../../package.json').name;
|
||||
const api = require('../../api');
|
||||
|
||||
const FETCH_CHANGES = `${app}/changes/FETCH_CHANGES`;
|
||||
const REMOVE_CHANGE = `${app}/changes/REMOVE_CHANGE`;
|
||||
|
||||
const fetchChanges = () => {
|
||||
return {
|
||||
type: FETCH_CHANGES,
|
||||
payload: api.fetchChanges()
|
||||
};
|
||||
};
|
||||
|
||||
const removeChange = (id) => (dispatch) => {
|
||||
return dispatch({
|
||||
type: REMOVE_CHANGE,
|
||||
payload: api.removeChange(id)
|
||||
}).then(() => {
|
||||
return dispatch(fetchChanges());
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
FETCH_CHANGES,
|
||||
REMOVE_CHANGE,
|
||||
fetchChanges,
|
||||
removeChange
|
||||
};
|
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
...require('./app'),
|
||||
...require('./printers'),
|
||||
...require('./changes')
|
||||
};
|
159
spikes/stacks/redux-loop/src/client/state/actions/printers.js
Normal file
159
spikes/stacks/redux-loop/src/client/state/actions/printers.js
Normal file
@ -0,0 +1,159 @@
|
||||
const ReduxActions = require('redux-actions');
|
||||
const app = require('../../../../package.json').name;
|
||||
const find = require('lodash.find');
|
||||
const changes = require('./changes');
|
||||
|
||||
const {
|
||||
createAction
|
||||
} = ReduxActions;
|
||||
|
||||
const {
|
||||
removeChange
|
||||
} = changes;
|
||||
|
||||
const UPDATE_PRINTERS = `${app}/printers/UPDATE_PRINTERS`;
|
||||
const UPDATE_WORKER_ID = `${app}/printers/UPDATE_WORKER_ID`;
|
||||
const EMIT_LOCK_PRINTER = `${app}/printers/EMIT_LOCK_PRINTER`;
|
||||
const LOCK_PRINTER = `${app}/printers/LOCK_PRINTER`;
|
||||
const PRINTER_NOT_FOUND = `${app}/printers/PRINTER_NOT_FOUND`;
|
||||
const DO_PRINT = `${app}/printers/DO_PRINT`;
|
||||
const EMIT_PRINT_JOB = `${app}/printers/EMIT_PRINT_JOB`;
|
||||
const CONFIRM_OVERRIDE = `${app}/printers/CONFIRM_OVERRIDE`;
|
||||
const CONFIRM_LOCK = `${app}/printers/CONFIRM_LOCK`;
|
||||
const PROMPT_LOCK = `${app}/printers/PROMPT_LOCK`;
|
||||
const PROMPT_CONFIRM_LOCK = `${app}/printers/PROMPT_CONFIRM_LOCK`;
|
||||
const PRINT = `${app}/printers/PRINT`;
|
||||
|
||||
// confirm should be an async op,
|
||||
// let's mock it that way
|
||||
const confirm = (msg) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(window.confirm(msg));
|
||||
});
|
||||
};
|
||||
|
||||
// prompt should be an async op,
|
||||
// let's mock it that way
|
||||
const prompt = (msg) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(window.prompt(msg));
|
||||
});
|
||||
};
|
||||
|
||||
// alert should be an async op,
|
||||
// let's mock it that way
|
||||
const alert = (msg) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(window.alert(msg));
|
||||
});
|
||||
};
|
||||
|
||||
const confirmOverride = createAction(CONFIRM_OVERRIDE);
|
||||
const confirmLock = createAction(CONFIRM_LOCK);
|
||||
const promptLock = createAction(PROMPT_LOCK);
|
||||
const updatePrinters = createAction(UPDATE_PRINTERS);
|
||||
const updateWorkerId = createAction(UPDATE_WORKER_ID);
|
||||
const promptOrConfirmLock = createAction(PROMPT_CONFIRM_LOCK);
|
||||
const lockPrinter = createAction(LOCK_PRINTER);
|
||||
const printerNotFound = createAction(PRINTER_NOT_FOUND);
|
||||
const print = createAction(PRINT);
|
||||
|
||||
// const print = (changeId) => (dispatch, getState) => {
|
||||
// const {
|
||||
// ui,
|
||||
// data
|
||||
// } = getState();
|
||||
//
|
||||
// const {
|
||||
// printers
|
||||
// } = data;
|
||||
//
|
||||
// const {
|
||||
// printers: {
|
||||
// locked
|
||||
// }
|
||||
// } = ui;
|
||||
//
|
||||
// const worker = require('../../worker');
|
||||
//
|
||||
// const _print = () => {
|
||||
// return dispatch({
|
||||
// type: EMIT_PRINT_JOB,
|
||||
// payload: worker.dispatch({
|
||||
// type: 'PRINT',
|
||||
// payload: changeId
|
||||
// })
|
||||
// }).then(() => {
|
||||
// return dispatch(removeChange(changeId));
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// const lock = (printerId) => {
|
||||
// return dispatch({
|
||||
// type: EMIT_LOCK_PRINTER,
|
||||
// payload: worker.dispatch({
|
||||
// type: 'LOCK_PRINTER',
|
||||
// payload: printerId
|
||||
// })
|
||||
// }).then(_print);
|
||||
// };
|
||||
//
|
||||
// const askToOverride = (printerId) => {
|
||||
// const msg = `Printer ${printerId} already locked! Do you want to override?`;
|
||||
// return confirm(msg).then((yes) => {
|
||||
// return yes ? lock(printerId) : null;
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// const askToLock = () => {
|
||||
// const msg = `Please select a printer to lock: ${
|
||||
// printers.map(({
|
||||
// id,
|
||||
// name
|
||||
// }) => {
|
||||
// return `\n(${id}) ${name}`;
|
||||
// })
|
||||
// }`;
|
||||
//
|
||||
// return prompt(msg).then((printerId) => {
|
||||
// const printer = find(printers, ['id', printerId]);
|
||||
//
|
||||
// if (!printer) {
|
||||
// return alert(`Printer ${printerId} not found. Try again`).then(() => {
|
||||
// return print(printerId)(dispatch, getState);
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// return printer.lock ? askToOverride(printerId) : lock(printerId);
|
||||
// });
|
||||
// };
|
||||
//
|
||||
// return !locked ? askToLock() : _print();
|
||||
// };
|
||||
|
||||
module.exports = {
|
||||
UPDATE_PRINTERS,
|
||||
UPDATE_WORKER_ID,
|
||||
EMIT_LOCK_PRINTER,
|
||||
LOCK_PRINTER,
|
||||
PRINTER_NOT_FOUND,
|
||||
DO_PRINT,
|
||||
EMIT_PRINT_JOB,
|
||||
CONFIRM_OVERRIDE,
|
||||
CONFIRM_LOCK,
|
||||
PROMPT_LOCK,
|
||||
PROMPT_CONFIRM_LOCK,
|
||||
PRINT,
|
||||
PRINTER_NOT_FOUND,
|
||||
confirm,
|
||||
prompt,
|
||||
alert,
|
||||
updatePrinters,
|
||||
updateWorkerId,
|
||||
confirmOverride,
|
||||
confirmLock,
|
||||
promptLock,
|
||||
promptOrConfirmLock,
|
||||
lockPrinter,
|
||||
print
|
||||
};
|
39
spikes/stacks/redux-loop/src/client/state/loop-promise.js
Normal file
39
spikes/stacks/redux-loop/src/client/state/loop-promise.js
Normal file
@ -0,0 +1,39 @@
|
||||
const ReduxActions = require('redux-actions');
|
||||
const ReduxLoop = require('redux-loop');
|
||||
|
||||
const {
|
||||
Effects
|
||||
} = ReduxLoop;
|
||||
|
||||
const {
|
||||
createAction
|
||||
} = ReduxActions;
|
||||
|
||||
module.exports = ({
|
||||
type,
|
||||
payload,
|
||||
ctx
|
||||
}) => {
|
||||
return Effects.batch([
|
||||
Effects.constant({
|
||||
type: `${type}_PENDING`
|
||||
}),
|
||||
Effects.promise(() => {
|
||||
return payload
|
||||
.then((res) => {
|
||||
return {
|
||||
type: `${type}_FULFILLED`,
|
||||
payload: res,
|
||||
ctx
|
||||
};
|
||||
})
|
||||
.catch((err) => {
|
||||
return {
|
||||
type: `${type}_REJECTED`,
|
||||
payload: err,
|
||||
ctx
|
||||
};
|
||||
});
|
||||
})
|
||||
]);
|
||||
};
|
44
spikes/stacks/redux-loop/src/client/state/reduce-reducers.js
Normal file
44
spikes/stacks/redux-loop/src/client/state/reduce-reducers.js
Normal file
@ -0,0 +1,44 @@
|
||||
const ReduxLoop = require('redux-loop');
|
||||
|
||||
const {
|
||||
loop,
|
||||
isLoop,
|
||||
getEffect,
|
||||
getModel,
|
||||
Effects
|
||||
} = ReduxLoop;
|
||||
|
||||
const optimizeBatch = (effects) => {
|
||||
switch(effects.length) {
|
||||
case 0:
|
||||
return Effects.none();
|
||||
case 1:
|
||||
return effects[0];
|
||||
default:
|
||||
return Effects.batch(effects);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = (...reducers) => {
|
||||
return (state, action) => {
|
||||
const effects = [];
|
||||
|
||||
const next = reducers.reduce((sum, reducer) => {
|
||||
let state = reducer(sum, action);
|
||||
|
||||
if (!isLoop(state)) {
|
||||
return state;
|
||||
}
|
||||
|
||||
effects.push(getEffect(state));
|
||||
state = getModel(state);
|
||||
|
||||
return state;
|
||||
}, state);
|
||||
|
||||
return loop(
|
||||
next,
|
||||
optimizeBatch(effects)
|
||||
);
|
||||
};
|
||||
};
|
19
spikes/stacks/redux-loop/src/client/state/reducers/app.js
Normal file
19
spikes/stacks/redux-loop/src/client/state/reducers/app.js
Normal file
@ -0,0 +1,19 @@
|
||||
const ReduxActions = require('redux-actions');
|
||||
const actions = require('../actions');
|
||||
|
||||
const {
|
||||
handleActions
|
||||
} = ReduxActions;
|
||||
|
||||
const {
|
||||
UPDATE_ROUTER
|
||||
} = actions;
|
||||
|
||||
exports.ui = handleActions({
|
||||
[UPDATE_ROUTER]: (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
router: action.payload
|
||||
};
|
||||
}
|
||||
}, {});
|
@ -1,17 +1,13 @@
|
||||
const ReduxActions = require('redux-actions');
|
||||
const app = require('../../../package.json').name;
|
||||
const api = require('../api');
|
||||
const actions = require('../actions');
|
||||
|
||||
const {
|
||||
createAction,
|
||||
handleActions
|
||||
} = ReduxActions;
|
||||
|
||||
const {
|
||||
fetchChanges
|
||||
} = api;
|
||||
|
||||
const FETCH_CHANGES = `${app}/changes/FETCH_CHANGES`;
|
||||
FETCH_CHANGES
|
||||
} = actions;
|
||||
|
||||
exports.data = handleActions({
|
||||
[`${FETCH_CHANGES}_FULFILLED`]: (state, action) => {
|
||||
@ -45,12 +41,3 @@ exports.ui = handleActions({
|
||||
loading: false,
|
||||
loaded: false
|
||||
});
|
||||
|
||||
exports.actions = {
|
||||
fetchChanges: () => {
|
||||
return {
|
||||
type: FETCH_CHANGES,
|
||||
payload: fetchChanges()
|
||||
};
|
||||
}
|
||||
};
|
27
spikes/stacks/redux-loop/src/client/state/reducers/index.js
Normal file
27
spikes/stacks/redux-loop/src/client/state/reducers/index.js
Normal file
@ -0,0 +1,27 @@
|
||||
const reduceReducers = require('../reduce-reducers');
|
||||
const ReduxLoop = require('redux-loop');
|
||||
|
||||
const app = require('./app');
|
||||
const printers = require('./printers');
|
||||
const changes = require('./changes');
|
||||
|
||||
const {
|
||||
combineReducers
|
||||
} = ReduxLoop;
|
||||
|
||||
module.exports = () => reduceReducers(
|
||||
// app.global,
|
||||
printers.global,
|
||||
// changes.global
|
||||
combineReducers({
|
||||
data: combineReducers({
|
||||
printers: printers.data,
|
||||
changes: changes.data
|
||||
}),
|
||||
ui: combineReducers({
|
||||
changes: changes.ui,
|
||||
printers: printers.ui,
|
||||
app: app.ui
|
||||
})
|
||||
})
|
||||
);
|
238
spikes/stacks/redux-loop/src/client/state/reducers/printers.js
Normal file
238
spikes/stacks/redux-loop/src/client/state/reducers/printers.js
Normal file
@ -0,0 +1,238 @@
|
||||
const loopPromise = require('../loop-promise');
|
||||
const ReduxActions = require('redux-actions');
|
||||
const ReduxLoop = require('redux-loop');
|
||||
const find = require('lodash.find');
|
||||
const actions = require('../actions');
|
||||
|
||||
const {
|
||||
handleActions
|
||||
} = ReduxActions;
|
||||
|
||||
const {
|
||||
loop,
|
||||
Effects
|
||||
} = ReduxLoop;
|
||||
|
||||
const {
|
||||
UPDATE_WORKER_ID,
|
||||
UPDATE_PRINTERS,
|
||||
LOCK_PRINTER,
|
||||
PROMPT_CONFIRM_LOCK,
|
||||
CONFIRM_LOCK,
|
||||
PROMPT_LOCK,
|
||||
CONFIRM_OVERRIDE,
|
||||
EMIT_LOCK_PRINTER,
|
||||
PRINTER_NOT_FOUND,
|
||||
printerNotFound,
|
||||
confirmOverride,
|
||||
promptOrConfirmLock,
|
||||
confirmLock,
|
||||
promptLock,
|
||||
confirm,
|
||||
alert,
|
||||
prompt
|
||||
} = actions;
|
||||
|
||||
exports.data = handleActions({
|
||||
[UPDATE_PRINTERS]: (state, action) => {
|
||||
return action.payload;
|
||||
}
|
||||
}, []);
|
||||
|
||||
exports.ui = handleActions({
|
||||
[UPDATE_WORKER_ID]: (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
id: action.payload
|
||||
};
|
||||
},
|
||||
[UPDATE_PRINTERS]: (state, action) => {
|
||||
const locked = (find(action.payload, (printer) => {
|
||||
return (
|
||||
printer.lock &&
|
||||
printer.lock === state.id
|
||||
);
|
||||
}) || {}).id || '';
|
||||
|
||||
return {
|
||||
...state,
|
||||
locked
|
||||
};
|
||||
}
|
||||
}, {
|
||||
id: '',
|
||||
locked: ''
|
||||
});
|
||||
|
||||
exports.global = handleActions({
|
||||
[LOCK_PRINTER]: (state, {
|
||||
payload
|
||||
}) => {
|
||||
const {
|
||||
ui,
|
||||
data
|
||||
} = state;
|
||||
|
||||
const {
|
||||
printers
|
||||
} = data;
|
||||
|
||||
const {
|
||||
printers: {
|
||||
locked
|
||||
}
|
||||
} = ui;
|
||||
|
||||
if (locked === payload) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const printer = find(printers, ['id', payload]);
|
||||
|
||||
if (!printer) {
|
||||
return loop(
|
||||
state,
|
||||
Effects.constant(printerNotFound(payload))
|
||||
);
|
||||
}
|
||||
|
||||
const action = (printer.lock ? confirmOverride : promptOrConfirmLock)(payload);
|
||||
|
||||
return loop(
|
||||
state,
|
||||
Effects.constant(action)
|
||||
);
|
||||
},
|
||||
[PROMPT_CONFIRM_LOCK]: (state, {
|
||||
payload
|
||||
}) => {
|
||||
const action = (payload ? confirmLock : promptLock)(payload);
|
||||
|
||||
return loop(
|
||||
state,
|
||||
Effects.constant(action)
|
||||
);
|
||||
},
|
||||
[CONFIRM_LOCK]: (state, {
|
||||
payload
|
||||
}) => {
|
||||
const msg = `Do you want to lock printer ${payload}?`;
|
||||
|
||||
return loop(
|
||||
state,
|
||||
loopPromise({
|
||||
type: CONFIRM_LOCK,
|
||||
payload: confirm(msg),
|
||||
ctx: payload
|
||||
})
|
||||
);
|
||||
},
|
||||
[`${CONFIRM_LOCK}_FULFILLED`]: (state, {
|
||||
payload,
|
||||
ctx
|
||||
}) => {
|
||||
const worker = require('../../worker');
|
||||
|
||||
return !payload ? state : loop(
|
||||
state,
|
||||
loopPromise({
|
||||
type: EMIT_LOCK_PRINTER,
|
||||
payload: worker.dispatch({
|
||||
type: 'LOCK_PRINTER',
|
||||
payload: ctx
|
||||
})
|
||||
})
|
||||
);
|
||||
},
|
||||
[PROMPT_LOCK]: (state, action) => {
|
||||
const {
|
||||
ui,
|
||||
data
|
||||
} = state;
|
||||
|
||||
const {
|
||||
printers
|
||||
} = data;
|
||||
|
||||
const msg = `Please select a printer to lock: ${
|
||||
printers.map(({
|
||||
id,
|
||||
name
|
||||
}) => {
|
||||
return `\n(${id}) ${name}`;
|
||||
})
|
||||
}`;
|
||||
|
||||
return loop(
|
||||
state,
|
||||
loopPromise({
|
||||
type: PROMPT_LOCK,
|
||||
payload: prompt(msg)
|
||||
})
|
||||
);
|
||||
},
|
||||
[`${PROMPT_LOCK}_FULFILLED`]: (state, {
|
||||
payload
|
||||
}) => {
|
||||
const {
|
||||
data: {
|
||||
printers
|
||||
}
|
||||
} = state;
|
||||
|
||||
const printer = find(printers, ['id', payload]);
|
||||
|
||||
if (!printer) {
|
||||
return loop(
|
||||
state,
|
||||
Effects.constant(printerNotFound(payload))
|
||||
);
|
||||
}
|
||||
|
||||
const worker = require('../../worker');
|
||||
|
||||
return loop(
|
||||
state,
|
||||
loopPromise({
|
||||
type: EMIT_LOCK_PRINTER,
|
||||
payload: worker.dispatch({
|
||||
type: 'LOCK_PRINTER',
|
||||
payload: ctx
|
||||
})
|
||||
})
|
||||
);
|
||||
},
|
||||
[CONFIRM_OVERRIDE]: (state, {
|
||||
payload
|
||||
}) => {
|
||||
const msg = `Printer ${payload} already locked! Do you want to override?`;
|
||||
|
||||
return loop(
|
||||
state,
|
||||
loopPromise({
|
||||
type: CONFIRM_LOCK,
|
||||
payload: confirm(msg),
|
||||
ctx: payload
|
||||
})
|
||||
);
|
||||
},
|
||||
[PRINTER_NOT_FOUND]: (state, action) => {
|
||||
const msg = `Printer ${printerId} not found. Try again`;
|
||||
|
||||
return loop(
|
||||
state,
|
||||
loopPromise({
|
||||
type: PRINTER_NOT_FOUND,
|
||||
payload: alert(msg)
|
||||
})
|
||||
);
|
||||
},
|
||||
[`${PRINTER_NOT_FOUND}_FULFILLED`]: (state, action) => {
|
||||
return loop(
|
||||
state,
|
||||
Effects.constant({
|
||||
type: PROMPT_LOCK
|
||||
})
|
||||
);
|
||||
}
|
||||
}, {});
|
10
spikes/stacks/redux-loop/src/client/state/selectors.js
Normal file
10
spikes/stacks/redux-loop/src/client/state/selectors.js
Normal file
@ -0,0 +1,10 @@
|
||||
// const Reselect = require('reselect');
|
||||
const get = require('lodash.get');
|
||||
|
||||
const router = (state) => {
|
||||
return get(state, 'ui.app.router');
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
router
|
||||
};
|
30
spikes/stacks/redux-loop/src/client/state/store.js
Normal file
30
spikes/stacks/redux-loop/src/client/state/store.js
Normal file
@ -0,0 +1,30 @@
|
||||
const createReducer = require('./reducers');
|
||||
const enableBatching = require('redux-batched-actions').enableBatching;
|
||||
const ReduxLoop = require('redux-loop');
|
||||
const promiseMiddleware = require('redux-promise-middleware').default;
|
||||
const createLogger = require('redux-logger');
|
||||
const redux = require('redux');
|
||||
|
||||
const {
|
||||
install
|
||||
} = ReduxLoop;
|
||||
|
||||
const {
|
||||
createStore,
|
||||
compose,
|
||||
applyMiddleware
|
||||
} = redux;
|
||||
|
||||
module.exports = (initialState = Object.freeze({})) => {
|
||||
return createStore(
|
||||
enableBatching(createReducer()),
|
||||
initialState,
|
||||
compose(
|
||||
applyMiddleware(
|
||||
// createLogger(),
|
||||
promiseMiddleware()
|
||||
),
|
||||
install()
|
||||
)
|
||||
);
|
||||
};
|
144
spikes/stacks/redux-loop/src/client/worker.js
Normal file
144
spikes/stacks/redux-loop/src/client/worker.js
Normal file
@ -0,0 +1,144 @@
|
||||
const uuid = require('node-uuid');
|
||||
const Emitter = require('component-emitter');
|
||||
const crosstab = require('crosstab');
|
||||
const values = require('lodash.values');
|
||||
const actions = require('./state/actions');
|
||||
|
||||
const emitter = module.exports = new Emitter();
|
||||
let isMaster = crosstab.util.tabs['MASTER_TAB'].id === crosstab.id;
|
||||
const bridge = new Map();
|
||||
|
||||
const {
|
||||
updatePrinters,
|
||||
updateWorkerId
|
||||
} = actions;
|
||||
|
||||
const printers = {
|
||||
'1': {
|
||||
id: '1',
|
||||
name: 'Main printer',
|
||||
lock: ''
|
||||
},
|
||||
'2': {
|
||||
id: '2',
|
||||
name: 'Handled printer',
|
||||
lock: ''
|
||||
}
|
||||
};
|
||||
|
||||
const handlers = {
|
||||
'PRINT': (action, fn) => {
|
||||
fn();
|
||||
},
|
||||
'LOCK_PRINTER': (action, fn) => {
|
||||
const alreadyLocked = values(printers).filter((printer) => {
|
||||
return printer.lock === action._origin;
|
||||
});
|
||||
|
||||
alreadyLocked.forEach((printer) => {
|
||||
printers[printer.id] = {
|
||||
...printers[printer.id],
|
||||
lock: ''
|
||||
};
|
||||
});
|
||||
|
||||
printers[action.payload] = {
|
||||
...printers[action.payload],
|
||||
lock: action._origin
|
||||
};
|
||||
|
||||
fn();
|
||||
}
|
||||
};
|
||||
|
||||
crosstab.util.events.on('message', ({
|
||||
data,
|
||||
origin
|
||||
}) => {
|
||||
if (origin === crosstab.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data || !data.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data._id) {
|
||||
return emitter.emit('action', data);
|
||||
}
|
||||
|
||||
const b = bridge.get(data._id);
|
||||
|
||||
if (b) {
|
||||
data.error ? b.reject(new Error(data.error)) : b.resolve(data.payload);
|
||||
return bridge.delete(data._id);
|
||||
}
|
||||
|
||||
if (!handlers[data.type]) {
|
||||
return emitter.emit('action', data);
|
||||
}
|
||||
|
||||
handlers[data.type]({
|
||||
...data,
|
||||
_origin: origin
|
||||
}, (err, res) => {
|
||||
crosstab.broadcast('message', {
|
||||
...data,
|
||||
error: err && err.message
|
||||
}, origin);
|
||||
});
|
||||
});
|
||||
|
||||
crosstab.util.events.on(crosstab.util.eventTypes.becomeMaster, () => {
|
||||
isMaster = true;
|
||||
});
|
||||
|
||||
crosstab.util.events.on(crosstab.util.eventTypes.demoteFromMaster, () => {
|
||||
isMaster = false;
|
||||
});
|
||||
|
||||
const dispatch = module.exports.dispatch = (action, tab) => {
|
||||
if (isMaster && !tab) {
|
||||
if (handlers[action.type]) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
handlers[action.type]({
|
||||
...action,
|
||||
_origin: crosstab.id
|
||||
}, function(err, res) {
|
||||
return err ? reject(err) : resolve(res);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const id = uuid.v4();
|
||||
|
||||
const then = new Promise((resolve, reject) => {
|
||||
bridge.set(id, {
|
||||
resolve,
|
||||
reject
|
||||
});
|
||||
});
|
||||
|
||||
crosstab.broadcast('message', {
|
||||
...action,
|
||||
_id: id
|
||||
}, tab);
|
||||
|
||||
return then;
|
||||
};
|
||||
|
||||
setTimeout(function() {
|
||||
emitter.emit('action', updateWorkerId(crosstab.id));
|
||||
}, 450);
|
||||
|
||||
setInterval(() => {
|
||||
if (!isMaster) {
|
||||
return;
|
||||
}
|
||||
|
||||
const action = updatePrinters(values(printers));
|
||||
|
||||
emitter.emit('action', action);
|
||||
dispatch(action);
|
||||
}, 1000);
|
41
spikes/stacks/redux-loop/src/config/webpack.config.js
Normal file
41
spikes/stacks/redux-loop/src/config/webpack.config.js
Normal file
@ -0,0 +1,41 @@
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
debug: true,
|
||||
devtool: 'eval',
|
||||
context: path.join(__dirname, '../client'),
|
||||
entry: [
|
||||
'webpack-dev-server/client?http://localhost:3000',
|
||||
'webpack/hot/only-dev-server',
|
||||
'react-hot-loader/patch',
|
||||
'./index.js'
|
||||
],
|
||||
output: {
|
||||
path: path.join(__dirname, '../../static'),
|
||||
publicPath: '/static/',
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoErrorsPlugin()
|
||||
],
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /js?$/,
|
||||
exclude: /node_modules/,
|
||||
include: [
|
||||
path.join(__dirname, '../client')
|
||||
],
|
||||
loaders: ['babel']
|
||||
}, {
|
||||
test: /\.json?$/,
|
||||
exclude: /node_modules/,
|
||||
include: [
|
||||
path.join(__dirname, '../client'),
|
||||
path.join(__dirname, '../../') // for package.json
|
||||
],
|
||||
loaders: ['json']
|
||||
}]
|
||||
}
|
||||
};
|
57
spikes/stacks/redux-loop/src/server/data/changes.json
Normal file
57
spikes/stacks/redux-loop/src/server/data/changes.json
Normal file
@ -0,0 +1,57 @@
|
||||
{
|
||||
"1": {
|
||||
"id": "1",
|
||||
"format": "LP",
|
||||
"product": "2267150",
|
||||
"price": 19,
|
||||
"currency": "€"
|
||||
},
|
||||
"2": {
|
||||
"id": "2",
|
||||
"product": "5347347",
|
||||
"price": 16,
|
||||
"currency": "€"
|
||||
},
|
||||
"3": {
|
||||
"id": "3",
|
||||
"product": "2485033",
|
||||
"price": 20,
|
||||
"currency": "€"
|
||||
},
|
||||
"3": {
|
||||
"id": "3",
|
||||
"product": "4389520",
|
||||
"price": 17,
|
||||
"currency": "€"
|
||||
},
|
||||
"4": {
|
||||
"id": "4",
|
||||
"product": "2942482",
|
||||
"price": 15,
|
||||
"currency": "€"
|
||||
},
|
||||
"5": {
|
||||
"id": "5",
|
||||
"product": "6549215",
|
||||
"price": 12,
|
||||
"currency": "€"
|
||||
},
|
||||
"6": {
|
||||
"id": "6",
|
||||
"product": "4192697",
|
||||
"price": 8,
|
||||
"currency": "€"
|
||||
},
|
||||
"7": {
|
||||
"id": "7",
|
||||
"product": "1248296",
|
||||
"price": 27,
|
||||
"currency": "€"
|
||||
},
|
||||
"8": {
|
||||
"id": "8",
|
||||
"product": "1174296",
|
||||
"price": 17,
|
||||
"currency": "€"
|
||||
}
|
||||
}
|
83
spikes/stacks/redux-loop/src/server/data/products.json
Normal file
83
spikes/stacks/redux-loop/src/server/data/products.json
Normal file
@ -0,0 +1,83 @@
|
||||
{
|
||||
"2267150": {
|
||||
"artist": "The National",
|
||||
"title": "High Violet",
|
||||
"label": "4AD, 4AD",
|
||||
"format": "LP",
|
||||
"id": "2267150",
|
||||
"price": 24,
|
||||
"currency": "€"
|
||||
},
|
||||
"5347347": {
|
||||
"artist": "Sensible Soccers",
|
||||
"title": "8",
|
||||
"label": "Groovement Organic Series",
|
||||
"format": "LP",
|
||||
"id": "5347347",
|
||||
"price": 25,
|
||||
"currency": "€"
|
||||
},
|
||||
"2485033": {
|
||||
"artist": "Eddie Vedder",
|
||||
"title": "Into The Wild",
|
||||
"label": "Music On Vinyl",
|
||||
"format": "LP",
|
||||
"id": "2485033",
|
||||
"price": 33,
|
||||
"currency": "€"
|
||||
},
|
||||
"4389520": {
|
||||
"artist": "Daughter (2)",
|
||||
"title": "If You Leave",
|
||||
"label": "4AD",
|
||||
"format": "LP",
|
||||
"id": "4389520",
|
||||
"price": 22,
|
||||
"currency": "€"
|
||||
},
|
||||
"2942482": {
|
||||
"artist": "Bon Iver",
|
||||
"title": "Bon Iver, Bon Iver",
|
||||
"label": "4AD, Jagjaguwar",
|
||||
"format": "LP",
|
||||
"id": "2942482",
|
||||
"price": 29,
|
||||
"currency": "€"
|
||||
},
|
||||
"6549215": {
|
||||
"artist": "Sufjan Stevens",
|
||||
"title": "Carrie & Lowell",
|
||||
"label": "Asthmatic Kitty Records",
|
||||
"format": "LP",
|
||||
"id": "6549215",
|
||||
"price": 20,
|
||||
"currency": "€"
|
||||
},
|
||||
"4192697": {
|
||||
"artist": "Uzi & Ari",
|
||||
"title": "It Is Freezing Out",
|
||||
"label": "Own Records",
|
||||
"format": "LP",
|
||||
"id": "4192697",
|
||||
"price": 17,
|
||||
"currency": "€"
|
||||
},
|
||||
"1248296": {
|
||||
"artist": "Various",
|
||||
"title": "Juno (Music From The Motion Picture)",
|
||||
"label": "Rhino Records (2), Fox Music, Fox Searchlight Pictures",
|
||||
"format": "LP",
|
||||
"id": "1248296",
|
||||
"price": 36,
|
||||
"currency": "€"
|
||||
},
|
||||
"1174296": {
|
||||
"artist": "Radiohead",
|
||||
"title": "In Rainbows",
|
||||
"label": "XL Recordings",
|
||||
"format": "LP",
|
||||
"id": "1174296",
|
||||
"price": 19,
|
||||
"currency": "€"
|
||||
}
|
||||
}
|
28
spikes/stacks/redux-loop/src/server/index.js
Normal file
28
spikes/stacks/redux-loop/src/server/index.js
Normal file
@ -0,0 +1,28 @@
|
||||
const webpack = require('webpack');
|
||||
const webpackConfig = require('../config/webpack.config.js');
|
||||
const WebpackDevServer = require('webpack-dev-server');
|
||||
const graphqlHTTP = require('express-graphql');
|
||||
const schema = require('./schema');
|
||||
|
||||
// WebpackDevServer should only be used for dev
|
||||
const server = new WebpackDevServer(webpack(webpackConfig), {
|
||||
hot: true,
|
||||
historyApiFallback: {
|
||||
index: './static/index.html'
|
||||
},
|
||||
setup: function(app) {
|
||||
app.use('/graphql', graphqlHTTP({
|
||||
schema: schema,
|
||||
graphiql: true
|
||||
}));
|
||||
},
|
||||
publicPath: webpackConfig.output.publicPath
|
||||
});
|
||||
|
||||
server.listen(3000, 'localhost', (err) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
console.log('Listening at localhost:3000');
|
||||
});
|
142
spikes/stacks/redux-loop/src/server/schema.js
Normal file
142
spikes/stacks/redux-loop/src/server/schema.js
Normal file
@ -0,0 +1,142 @@
|
||||
const changes = require('./data/changes.json');
|
||||
const products = require('./data/products.json');
|
||||
const values = require('lodash.values');
|
||||
|
||||
const {
|
||||
GraphQLString,
|
||||
GraphQLObjectType,
|
||||
GraphQLInt,
|
||||
GraphQLID,
|
||||
GraphQLList,
|
||||
GraphQLSchema
|
||||
} = require('graphql');
|
||||
|
||||
const ProductType = new GraphQLObjectType({
|
||||
name: 'ProductType',
|
||||
fields: {
|
||||
id: {
|
||||
type: GraphQLID
|
||||
},
|
||||
artist: {
|
||||
type: GraphQLString
|
||||
},
|
||||
title: {
|
||||
type: GraphQLString
|
||||
},
|
||||
label: {
|
||||
type: GraphQLString
|
||||
},
|
||||
format: {
|
||||
type: GraphQLString
|
||||
},
|
||||
price: {
|
||||
type: GraphQLInt
|
||||
},
|
||||
currency: {
|
||||
type: GraphQLString
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const ChangeType = new GraphQLObjectType({
|
||||
name: 'ChangeType',
|
||||
fields: {
|
||||
id: {
|
||||
type: GraphQLID
|
||||
},
|
||||
product: {
|
||||
type: ProductType,
|
||||
resolve: (root, args) => {
|
||||
return products[root.product];
|
||||
}
|
||||
},
|
||||
price: {
|
||||
type: GraphQLInt
|
||||
},
|
||||
currency: {
|
||||
type: GraphQLString
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const query = new GraphQLObjectType({
|
||||
name: 'RootQueryType',
|
||||
fields: {
|
||||
products: {
|
||||
type: new GraphQLList(ProductType),
|
||||
args: {
|
||||
id: {
|
||||
type: GraphQLID
|
||||
}
|
||||
},
|
||||
resolve(root, args, ctx) {
|
||||
return args.id ? [products[args.id]] : values(products);
|
||||
}
|
||||
},
|
||||
changes: {
|
||||
type: new GraphQLList(ChangeType),
|
||||
args: {
|
||||
id: {
|
||||
type: GraphQLID
|
||||
},
|
||||
product: {
|
||||
type: GraphQLID
|
||||
}
|
||||
},
|
||||
resolve(root, args, ctx) {
|
||||
if (args.id) {
|
||||
return [changes[args.id]];
|
||||
}
|
||||
|
||||
if (!args.product) {
|
||||
return values(changes);
|
||||
}
|
||||
|
||||
return values(changes).filter((change) => {
|
||||
return change.product === args.product;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const mutation = new GraphQLObjectType({
|
||||
name: 'RootMutationType',
|
||||
fields: {
|
||||
removeChange: {
|
||||
type: ChangeType,
|
||||
args: {
|
||||
id: {
|
||||
type: GraphQLID
|
||||
},
|
||||
product: {
|
||||
type: GraphQLID
|
||||
}
|
||||
},
|
||||
resolve(root, args, ctx) {
|
||||
const ops = (() => {
|
||||
if (args.id) {
|
||||
return [args.id];
|
||||
}
|
||||
|
||||
if (!args.product) {
|
||||
return Object.keys(changes);
|
||||
}
|
||||
|
||||
return Object.keys(changes).filter((id) => {
|
||||
return changes[id].product === args.product;
|
||||
});
|
||||
})();
|
||||
|
||||
ops.forEach((id) => {
|
||||
delete changes[id];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = new GraphQLSchema({
|
||||
query,
|
||||
mutation
|
||||
});
|
10
spikes/stacks/redux-loop/static/index.html
Normal file
10
spikes/stacks/redux-loop/static/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<html lang='en-US'>
|
||||
<head>
|
||||
<title>React Boilerplate</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id='root'></div>
|
||||
<script src='/static/bundle.js'></script>
|
||||
</body>
|
||||
</html>
|
10
spikes/stacks/redux-loop/stories/index.js
Normal file
10
spikes/stacks/redux-loop/stories/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
const React = require('react');
|
||||
const Storybook = require('@kadira/storybook');
|
||||
|
||||
const Home = require('../src/client/containers/home');
|
||||
|
||||
const homeStories = Storybook.storiesOf('Home', module);
|
||||
|
||||
homeStories.add('with nothing', () => (
|
||||
<Home />
|
||||
));
|
13
spikes/stacks/redux-loop/test/index.js
Normal file
13
spikes/stacks/redux-loop/test/index.js
Normal file
@ -0,0 +1,13 @@
|
||||
const test = require('ava');
|
||||
const enzyme = require('enzyme');
|
||||
const React = require('react');
|
||||
|
||||
const {
|
||||
shallow
|
||||
} = enzyme;
|
||||
|
||||
test('renders <Home> without exploding', (t) => {
|
||||
const Home = require('../src/client/containers/home');
|
||||
const wrapper = shallow(<Home />);
|
||||
t.deepEqual(wrapper.length, 1);
|
||||
});
|
3
spikes/stacks/redux-thunk/.eslintignore
Normal file
3
spikes/stacks/redux-thunk/.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
/node_modules
|
||||
coverage
|
||||
.nyc_output
|
2
spikes/stacks/redux-thunk/.gitignore
vendored
2
spikes/stacks/redux-thunk/.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
/node_modules
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
npm-debug.log
|
||||
|
@ -31,6 +31,7 @@
|
||||
"graphql-fetch": "^1.0.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"lodash.find": "^4.6.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.values": "^4.3.0",
|
||||
"node-uuid": "^1.4.7",
|
||||
"react": "^15.3.2",
|
||||
@ -46,6 +47,7 @@
|
||||
"redux-logger": "^2.7.0",
|
||||
"redux-promise-middleware": "^4.1.0",
|
||||
"redux-thunk": "^2.1.0",
|
||||
"reselect": "^2.5.4",
|
||||
"webpack": "^1.13.2",
|
||||
"webpack-dev-server": "^1.16.1"
|
||||
},
|
||||
|
@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
...require('./reducers/printers').actions,
|
||||
...require('./reducers/changes').actions
|
||||
};
|
@ -24,3 +24,26 @@ exports.fetchChanges = () => {
|
||||
return data.changes;
|
||||
});
|
||||
};
|
||||
|
||||
exports.removeChange = (id) => {
|
||||
console.log(`
|
||||
mutation {
|
||||
removeChange(id: "${id}")
|
||||
}
|
||||
`);
|
||||
return fetch(`
|
||||
mutation {
|
||||
removeChange(id: "${id}") {
|
||||
id
|
||||
}
|
||||
}
|
||||
`).then(({
|
||||
errors
|
||||
}) => {
|
||||
if (!errors) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(errors[0].message);
|
||||
});
|
||||
};
|
||||
|
@ -7,15 +7,8 @@ const {
|
||||
|
||||
module.exports = ({
|
||||
changes = [],
|
||||
pathname,
|
||||
onClick
|
||||
pathname
|
||||
}) => {
|
||||
const _onClick = (id) => {
|
||||
return () => {
|
||||
onClick(id);
|
||||
};
|
||||
};
|
||||
|
||||
const lis = changes.map(({
|
||||
price,
|
||||
currency,
|
||||
|
@ -30,7 +30,6 @@ module.exports = React.createClass({
|
||||
},
|
||||
render: function() {
|
||||
const {
|
||||
fetch,
|
||||
loading,
|
||||
loaded,
|
||||
render,
|
||||
@ -38,7 +37,7 @@ module.exports = React.createClass({
|
||||
} = this.props;
|
||||
|
||||
const _loaded = !loading && !loaded;
|
||||
const component = _loaded ? (children ? children : render()) : null;
|
||||
const component = _loaded ? (children || render()) : null;
|
||||
|
||||
return (
|
||||
<Loader
|
||||
|
@ -2,6 +2,7 @@ const React = require('react');
|
||||
|
||||
module.exports = ({
|
||||
printers = [],
|
||||
locked = '',
|
||||
onClick
|
||||
}) => {
|
||||
const _onClick = (id) => {
|
||||
@ -12,11 +13,20 @@ module.exports = ({
|
||||
|
||||
const lis = printers.map(({
|
||||
name,
|
||||
lock,
|
||||
id
|
||||
}) => {
|
||||
const msg = (() => {
|
||||
if (!lock) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (locked === id) ? '(Locked to you)' : `(Locked to ${lock})`;
|
||||
})();
|
||||
|
||||
return (
|
||||
<li key={id}>
|
||||
<a onClick={_onClick(id)}>{name}</a>
|
||||
<a onClick={_onClick(id)}>{name} {msg}</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
@ -1,27 +1,55 @@
|
||||
const ReactRedux = require('react-redux');
|
||||
const ReactRouter = require('react-router');
|
||||
const React = require('react');
|
||||
|
||||
const NotFound = require('./not-found');
|
||||
const Home = require('./home');
|
||||
const Print = require('./print');
|
||||
|
||||
const actions = require('../state/actions');
|
||||
|
||||
const {
|
||||
connect
|
||||
} = ReactRedux;
|
||||
|
||||
const App = React.createClass({
|
||||
const {
|
||||
Miss,
|
||||
Match
|
||||
} = ReactRouter;
|
||||
|
||||
const {
|
||||
updateRouter
|
||||
} = actions;
|
||||
|
||||
const App = connect()(React.createClass({
|
||||
componentDidMount: function() {
|
||||
require('../worker').on('action', (action) => {
|
||||
this.props.dispatch(action);
|
||||
});
|
||||
require('../worker').on('action', this.props.dispatch);
|
||||
},
|
||||
render: function() {
|
||||
const {
|
||||
children
|
||||
children,
|
||||
router,
|
||||
dispatch
|
||||
} = this.props;
|
||||
|
||||
// ugly hack needed because of a limitation of react-router api
|
||||
// that doens't pass it's instance to matched routes
|
||||
dispatch(updateRouter(router));
|
||||
|
||||
return (
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = connect()(App);
|
||||
module.exports = (props) => {
|
||||
return (
|
||||
<App {...props}>
|
||||
<Match exactly pattern='/' component={Home} />
|
||||
<Match pattern='/print' component={Print} />
|
||||
<Miss component={NotFound}/>
|
||||
</App>
|
||||
);
|
||||
};
|
||||
|
@ -8,16 +8,17 @@ const Printers = require('../components/printers');
|
||||
const Changes = require('../components/changes');
|
||||
const Change = require('../components/change');
|
||||
|
||||
const actions = require('../actions');
|
||||
const actions = require('../state/actions');
|
||||
|
||||
const {
|
||||
fetchChanges
|
||||
fetchChanges,
|
||||
lockPrinter,
|
||||
print,
|
||||
transitionTo
|
||||
} = actions;
|
||||
|
||||
const {
|
||||
BrowserRouter,
|
||||
Miss,
|
||||
Match,
|
||||
Match
|
||||
} = ReactRouter;
|
||||
|
||||
const {
|
||||
@ -30,8 +31,11 @@ const Print = ({
|
||||
changes = [],
|
||||
lockPrinter,
|
||||
fetchChanges,
|
||||
onPrint,
|
||||
loaded,
|
||||
loading
|
||||
loading,
|
||||
locked,
|
||||
router
|
||||
}) => {
|
||||
const allChanges = () => {
|
||||
return (
|
||||
@ -58,6 +62,18 @@ const Print = ({
|
||||
return change.id === params.id;
|
||||
});
|
||||
|
||||
if (!change) {
|
||||
return (
|
||||
<p>Change not found</p>
|
||||
);
|
||||
}
|
||||
|
||||
const _onPrint = (id) => {
|
||||
return () => {
|
||||
return onPrint(id);
|
||||
};
|
||||
};
|
||||
|
||||
// TODO: don't load all changes
|
||||
return (
|
||||
<div>
|
||||
@ -69,6 +85,7 @@ const Print = ({
|
||||
>
|
||||
<Change {...change} />
|
||||
</Loader>
|
||||
<button onClick={_onPrint(params.id)}>Print</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -79,6 +96,8 @@ const Print = ({
|
||||
<p>Printers</p>
|
||||
<Printers
|
||||
printers={printers}
|
||||
onClick={lockPrinter}
|
||||
locked={locked}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -93,15 +112,23 @@ const mapStateToProps = (state) => {
|
||||
loaded: state.ui.changes.loaded,
|
||||
loading: state.ui.changes.loading,
|
||||
changes: state.data.changes,
|
||||
printers: state.data.printers
|
||||
printers: state.data.printers,
|
||||
locked: state.ui.printers.locked
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
lockPrinter: (id) => {},
|
||||
lockPrinter: (id) => {
|
||||
return dispatch(lockPrinter(id));
|
||||
},
|
||||
fetchChanges: () => {
|
||||
dispatch(fetchChanges());
|
||||
return dispatch(fetchChanges());
|
||||
},
|
||||
onPrint: (id) => {
|
||||
return dispatch(print(id)).then(() => {
|
||||
return dispatch(transitionTo('/print'));
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
const worker = require('./worker'); // singleton
|
||||
require('./worker'); // singleton
|
||||
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
@ -16,4 +16,4 @@ render();
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./root', render);
|
||||
}
|
||||
}
|
||||
|
3
spikes/stacks/redux-thunk/src/client/intl.json
Normal file
3
spikes/stacks/redux-thunk/src/client/intl.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
|
||||
}
|
@ -2,39 +2,44 @@ const React = require('react');
|
||||
const ReactHotLoader = require('react-hot-loader');
|
||||
const ReactRouter = require('react-router');
|
||||
const ReactRedux = require('react-redux');
|
||||
const ReactIntl = require('react-intl');
|
||||
|
||||
const App = require('./containers/app');
|
||||
const NotFound = require('./containers/not-found');
|
||||
const Home = require('./containers/home');
|
||||
const Print = require('./containers/print');
|
||||
|
||||
const store = require('./store');
|
||||
const store = require('./state/store');
|
||||
|
||||
const {
|
||||
AppContainer
|
||||
} = ReactHotLoader;
|
||||
|
||||
const {
|
||||
BrowserRouter,
|
||||
Miss,
|
||||
Match
|
||||
BrowserRouter
|
||||
} = ReactRouter;
|
||||
|
||||
const {
|
||||
Provider
|
||||
} = ReactRedux;
|
||||
|
||||
const {
|
||||
IntlProvider
|
||||
} = ReactIntl;
|
||||
|
||||
// http://stackoverflow.com/a/38150585
|
||||
const locale = (
|
||||
navigator.languages && navigator.languages[0] || // Chrome / Firefox
|
||||
navigator.language || // All browsers
|
||||
navigator.userLanguage // IE <= 10
|
||||
);
|
||||
|
||||
module.exports = () => {
|
||||
return (
|
||||
<AppContainer>
|
||||
<Provider store={store()}>
|
||||
<BrowserRouter>
|
||||
<App>
|
||||
<Match exactly pattern='/' component={Home} />
|
||||
<Match pattern='/print' component={Print} />
|
||||
<Miss component={NotFound}/>
|
||||
</App>
|
||||
</BrowserRouter>
|
||||
<IntlProvider
|
||||
locale={locale}
|
||||
defaultLocale='en'
|
||||
>
|
||||
<BrowserRouter>{App}</BrowserRouter>
|
||||
</IntlProvider>
|
||||
</Provider>
|
||||
</AppContainer>
|
||||
);
|
||||
|
25
spikes/stacks/redux-thunk/src/client/state/actions/app.js
Normal file
25
spikes/stacks/redux-thunk/src/client/state/actions/app.js
Normal file
@ -0,0 +1,25 @@
|
||||
const ReduxActions = require('redux-actions');
|
||||
const app = require('../../../../package.json').name;
|
||||
const selectors = require('../selectors');
|
||||
|
||||
const {
|
||||
router
|
||||
} = selectors;
|
||||
|
||||
const {
|
||||
createAction
|
||||
} = ReduxActions;
|
||||
|
||||
const UPDATE_ROUTER = `${app}/changes/UPDATE_ROUTER`;
|
||||
|
||||
const updateRouter = createAction(UPDATE_ROUTER);
|
||||
|
||||
const transitionTo = (pathname) => (dispatch, getState) => {
|
||||
return router(getState()).transitionTo(pathname);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
UPDATE_ROUTER,
|
||||
updateRouter,
|
||||
transitionTo
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
const app = require('../../../../package.json').name;
|
||||
const api = require('../../api');
|
||||
|
||||
const FETCH_CHANGES = `${app}/changes/FETCH_CHANGES`;
|
||||
const REMOVE_CHANGE = `${app}/changes/REMOVE_CHANGE`;
|
||||
|
||||
const fetchChanges = () => {
|
||||
return {
|
||||
type: FETCH_CHANGES,
|
||||
payload: api.fetchChanges()
|
||||
};
|
||||
};
|
||||
|
||||
const removeChange = (id) => (dispatch) => {
|
||||
return dispatch({
|
||||
type: REMOVE_CHANGE,
|
||||
payload: api.removeChange(id)
|
||||
}).then(() => {
|
||||
return dispatch(fetchChanges());
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
FETCH_CHANGES,
|
||||
REMOVE_CHANGE,
|
||||
fetchChanges,
|
||||
removeChange
|
||||
};
|
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
...require('./app'),
|
||||
...require('./printers'),
|
||||
...require('./changes')
|
||||
};
|
183
spikes/stacks/redux-thunk/src/client/state/actions/printers.js
Normal file
183
spikes/stacks/redux-thunk/src/client/state/actions/printers.js
Normal file
@ -0,0 +1,183 @@
|
||||
const ReduxActions = require('redux-actions');
|
||||
const app = require('../../../../package.json').name;
|
||||
const find = require('lodash.find');
|
||||
const changes = require('./changes');
|
||||
|
||||
const {
|
||||
createAction
|
||||
} = ReduxActions;
|
||||
|
||||
const {
|
||||
removeChange
|
||||
} = changes;
|
||||
|
||||
const UPDATE_PRINTERS = `${app}/printers/UPDATE_PRINTERS`;
|
||||
const UPDATE_WORKER_ID = `${app}/printers/UPDATE_WORKER_ID`;
|
||||
const LOCK_PRINTER = `${app}/printers/LOCK_PRINTER`;
|
||||
const PRINT = `${app}/printers/PRINT`;
|
||||
|
||||
// confirm should be an async op,
|
||||
// let's mock it that way
|
||||
const confirm = (msg) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(window.confirm(msg));
|
||||
});
|
||||
};
|
||||
|
||||
// prompt should be an async op,
|
||||
// let's mock it that way
|
||||
const prompt = (msg) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(window.prompt(msg));
|
||||
});
|
||||
};
|
||||
|
||||
// alert should be an async op,
|
||||
// let's mock it that way
|
||||
const alert = (msg) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(window.alert(msg));
|
||||
});
|
||||
};
|
||||
|
||||
const updatePrinters = createAction(UPDATE_PRINTERS);
|
||||
const updateWorkerId = createAction(UPDATE_WORKER_ID);
|
||||
|
||||
const lockPrinter = (id) => (dispatch, getState) => {
|
||||
const {
|
||||
ui,
|
||||
data
|
||||
} = getState();
|
||||
|
||||
const {
|
||||
printers
|
||||
} = data;
|
||||
|
||||
const {
|
||||
printers: {
|
||||
locked
|
||||
}
|
||||
} = ui;
|
||||
|
||||
if (locked === id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const printer = find(printers, ['id', id]);
|
||||
|
||||
if (!printer) {
|
||||
return window.alert(`Printer ${id} not found`);
|
||||
}
|
||||
|
||||
const worker = require('../../worker');
|
||||
|
||||
const lock = () => {
|
||||
return dispatch({
|
||||
type: LOCK_PRINTER,
|
||||
payload: worker.dispatch({
|
||||
type: 'LOCK_PRINTER',
|
||||
payload: id
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
const askToLock = () => {
|
||||
const msg = `Do you want to lock printer ${id}?`;
|
||||
return confirm(msg).then((yes) => {
|
||||
return yes ? lock(id) : null;
|
||||
});
|
||||
};
|
||||
|
||||
const askToOverride = () => {
|
||||
const msg = `Printer ${id} already locked! Do you want to override?`;
|
||||
return confirm(msg).then((yes) => {
|
||||
return yes ? lock(id) : null;
|
||||
});
|
||||
};
|
||||
|
||||
return printer.lock ? askToOverride() : askToLock();
|
||||
};
|
||||
|
||||
const print = (changeId) => (dispatch, getState) => {
|
||||
const {
|
||||
ui,
|
||||
data
|
||||
} = getState();
|
||||
|
||||
const {
|
||||
printers
|
||||
} = data;
|
||||
|
||||
const {
|
||||
printers: {
|
||||
locked
|
||||
}
|
||||
} = ui;
|
||||
|
||||
const worker = require('../../worker');
|
||||
|
||||
const _print = () => {
|
||||
return dispatch({
|
||||
type: PRINT,
|
||||
payload: worker.dispatch({
|
||||
type: 'PRINT',
|
||||
payload: changeId
|
||||
})
|
||||
}).then(() => {
|
||||
return dispatch(removeChange(changeId));
|
||||
});
|
||||
};
|
||||
|
||||
const lock = (printerId) => {
|
||||
return dispatch({
|
||||
type: LOCK_PRINTER,
|
||||
payload: worker.dispatch({
|
||||
type: 'LOCK_PRINTER',
|
||||
payload: printerId
|
||||
})
|
||||
}).then(_print);
|
||||
};
|
||||
|
||||
const askToOverride = (printerId) => {
|
||||
const msg = `Printer ${printerId} already locked! Do you want to override?`;
|
||||
return confirm(msg).then((yes) => {
|
||||
return yes ? lock(printerId) : null;
|
||||
});
|
||||
};
|
||||
|
||||
const askToLock = () => {
|
||||
const msg = `Please select a printer to lock: ${
|
||||
printers.map(({
|
||||
id,
|
||||
name
|
||||
}) => {
|
||||
return `\n(${id}) ${name}`;
|
||||
})
|
||||
}`;
|
||||
|
||||
return prompt(msg).then((printerId) => {
|
||||
const printer = find(printers, ['id', printerId]);
|
||||
|
||||
if (!printer) {
|
||||
return alert(`Printer ${printerId} not found. Try again`).then(() => {
|
||||
return print(printerId)(dispatch, getState);
|
||||
});
|
||||
}
|
||||
|
||||
return printer.lock ? askToOverride(printerId) : lock(printerId);
|
||||
});
|
||||
};
|
||||
|
||||
return !locked ? askToLock() : _print();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
UPDATE_PRINTERS,
|
||||
UPDATE_WORKER_ID,
|
||||
LOCK_PRINTER,
|
||||
PRINT,
|
||||
updatePrinters,
|
||||
updateWorkerId,
|
||||
lockPrinter,
|
||||
print
|
||||
};
|
19
spikes/stacks/redux-thunk/src/client/state/reducers/app.js
Normal file
19
spikes/stacks/redux-thunk/src/client/state/reducers/app.js
Normal file
@ -0,0 +1,19 @@
|
||||
const ReduxActions = require('redux-actions');
|
||||
const actions = require('../actions');
|
||||
|
||||
const {
|
||||
handleActions
|
||||
} = ReduxActions;
|
||||
|
||||
const {
|
||||
UPDATE_ROUTER
|
||||
} = actions;
|
||||
|
||||
exports.ui = handleActions({
|
||||
[UPDATE_ROUTER]: (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
router: action.payload
|
||||
};
|
||||
}
|
||||
}, {});
|
@ -0,0 +1,43 @@
|
||||
const ReduxActions = require('redux-actions');
|
||||
const actions = require('../actions');
|
||||
|
||||
const {
|
||||
handleActions
|
||||
} = ReduxActions;
|
||||
|
||||
const {
|
||||
FETCH_CHANGES
|
||||
} = actions;
|
||||
|
||||
exports.data = handleActions({
|
||||
[`${FETCH_CHANGES}_FULFILLED`]: (state, action) => {
|
||||
return action.payload;
|
||||
}
|
||||
}, []);
|
||||
|
||||
exports.ui = handleActions({
|
||||
[`${FETCH_CHANGES}_PENDING`]: (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
loading: true
|
||||
};
|
||||
},
|
||||
[`${FETCH_CHANGES}_FULFILLED`]: (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
loaded: false
|
||||
};
|
||||
},
|
||||
[`${FETCH_CHANGES}_REJECTED`]: (state, action) => {
|
||||
// TODO: deal with error
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
loaded: false
|
||||
};
|
||||
}
|
||||
}, {
|
||||
loading: false,
|
||||
loaded: false
|
||||
});
|
@ -1,6 +1,7 @@
|
||||
// const reduceReducers = require('reduce-reducers');
|
||||
const Redux = require('redux');
|
||||
|
||||
const app = require('./app');
|
||||
const printers = require('./printers');
|
||||
const changes = require('./changes');
|
||||
|
||||
@ -15,7 +16,9 @@ module.exports = () => {
|
||||
changes: changes.data
|
||||
}),
|
||||
ui: combineReducers({
|
||||
changes: changes.ui
|
||||
changes: changes.ui,
|
||||
printers: printers.ui,
|
||||
app: app.ui
|
||||
})
|
||||
});
|
||||
};
|
@ -1,14 +1,15 @@
|
||||
const ReduxActions = require('redux-actions');
|
||||
const app = require('../../../package.json').name;
|
||||
const find = require('lodash.find');
|
||||
const actions = require('../actions');
|
||||
|
||||
const {
|
||||
createAction,
|
||||
handleActions
|
||||
} = ReduxActions;
|
||||
|
||||
const UPDATE_PRINTERS = `${app}/printers/UPDATE_PRINTERS`;
|
||||
const UPDATE_WORKER_ID = `${app}/printers/UPDATE_WORKER_ID`;
|
||||
const {
|
||||
UPDATE_WORKER_ID,
|
||||
UPDATE_PRINTERS
|
||||
} = actions;
|
||||
|
||||
exports.data = handleActions({
|
||||
[UPDATE_PRINTERS]: (state, action) => {
|
||||
@ -27,8 +28,7 @@ exports.ui = handleActions({
|
||||
const locked = (find(action.payload, (printer) => {
|
||||
return (
|
||||
printer.lock &&
|
||||
state.locked &&
|
||||
printer.lock === state.locked
|
||||
printer.lock === state.id
|
||||
);
|
||||
}) || {}).id || '';
|
||||
|
||||
@ -41,8 +41,3 @@ exports.ui = handleActions({
|
||||
id: '',
|
||||
locked: ''
|
||||
});
|
||||
|
||||
exports.actions = {
|
||||
updatePrinters: createAction(UPDATE_PRINTERS),
|
||||
updateWorkerId: createAction(UPDATE_WORKER_ID)
|
||||
};
|
10
spikes/stacks/redux-thunk/src/client/state/selectors.js
Normal file
10
spikes/stacks/redux-thunk/src/client/state/selectors.js
Normal file
@ -0,0 +1,10 @@
|
||||
// const Reselect = require('reselect');
|
||||
const get = require('lodash.get');
|
||||
|
||||
const router = (state) => {
|
||||
return get(state, 'ui.app.router');
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
router
|
||||
};
|
@ -2,7 +2,7 @@ const createReducer = require('./reducers');
|
||||
const enableBatching = require('redux-batched-actions').enableBatching;
|
||||
const thunk = require('redux-thunk').default;
|
||||
const promiseMiddleware = require('redux-promise-middleware').default;
|
||||
const createLogger = require('redux-logger');
|
||||
// const createLogger = require('redux-logger');
|
||||
const redux = require('redux');
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ module.exports = (state = Object.freeze({})) => {
|
||||
state,
|
||||
compose(
|
||||
applyMiddleware(
|
||||
createLogger(),
|
||||
// createLogger(),
|
||||
promiseMiddleware(),
|
||||
thunk
|
||||
)
|
@ -2,19 +2,51 @@ const uuid = require('node-uuid');
|
||||
const Emitter = require('component-emitter');
|
||||
const crosstab = require('crosstab');
|
||||
const values = require('lodash.values');
|
||||
const actions = require('./actions');
|
||||
const actions = require('./state/actions');
|
||||
|
||||
const emitter = module.exports = new Emitter();
|
||||
let isMaster = crosstab.util.tabs['MASTER_TAB'].id === crosstab.id;
|
||||
const bridge = new Map();
|
||||
|
||||
const {
|
||||
updatePrinters
|
||||
updatePrinters,
|
||||
updateWorkerId
|
||||
} = actions;
|
||||
|
||||
const printers = {
|
||||
'1': {
|
||||
id: '1',
|
||||
name: 'Main printer',
|
||||
lock: ''
|
||||
},
|
||||
'2': {
|
||||
id: '2',
|
||||
name: 'Handled printer',
|
||||
lock: ''
|
||||
}
|
||||
};
|
||||
|
||||
const handlers = {
|
||||
'PRINT': (action, fn) => {},
|
||||
'PRINT': (action, fn) => {
|
||||
fn();
|
||||
},
|
||||
'LOCK_PRINTER': (action, fn) => {
|
||||
const alreadyLocked = values(printers).filter((printer) => {
|
||||
return printer.lock === action._origin;
|
||||
});
|
||||
|
||||
alreadyLocked.forEach((printer) => {
|
||||
printers[printer.id] = {
|
||||
...printers[printer.id],
|
||||
lock: ''
|
||||
};
|
||||
});
|
||||
|
||||
printers[action.payload] = {
|
||||
...printers[action.payload],
|
||||
lock: action._origin
|
||||
};
|
||||
|
||||
fn();
|
||||
}
|
||||
};
|
||||
@ -46,7 +78,10 @@ crosstab.util.events.on('message', ({
|
||||
return emitter.emit('action', data);
|
||||
}
|
||||
|
||||
handlers[data.type](data, (err, res) => {
|
||||
handlers[data.type]({
|
||||
...data,
|
||||
_origin: origin
|
||||
}, (err, res) => {
|
||||
crosstab.broadcast('message', {
|
||||
...data,
|
||||
error: err && err.message
|
||||
@ -66,7 +101,10 @@ const dispatch = module.exports.dispatch = (action, tab) => {
|
||||
if (isMaster && !tab) {
|
||||
if (handlers[action.type]) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
handlers[action.type](action, function(err, res) {
|
||||
handlers[action.type]({
|
||||
...action,
|
||||
_origin: crosstab.id
|
||||
}, function(err, res) {
|
||||
return err ? reject(err) : resolve(res);
|
||||
});
|
||||
});
|
||||
@ -90,20 +128,9 @@ const dispatch = module.exports.dispatch = (action, tab) => {
|
||||
return then;
|
||||
};
|
||||
|
||||
const printers = {
|
||||
'1': {
|
||||
id: '1',
|
||||
name: 'Main printer',
|
||||
lock: ''
|
||||
},
|
||||
'2': {
|
||||
id: '2',
|
||||
name: 'Handled printer',
|
||||
lock: ''
|
||||
}
|
||||
};
|
||||
|
||||
emitter.emit('action', updateWorkerId(crosstab.id));
|
||||
setTimeout(function() {
|
||||
emitter.emit('action', updateWorkerId(crosstab.id));
|
||||
}, 450);
|
||||
|
||||
setInterval(() => {
|
||||
if (!isMaster) {
|
||||
|
@ -47,7 +47,7 @@ const ChangeType = new GraphQLObjectType({
|
||||
product: {
|
||||
type: ProductType,
|
||||
resolve: (root, args) => {
|
||||
return products[root.product]
|
||||
return products[root.product];
|
||||
}
|
||||
},
|
||||
price: {
|
||||
@ -70,7 +70,7 @@ const query = new GraphQLObjectType({
|
||||
}
|
||||
},
|
||||
resolve(root, args, ctx) {
|
||||
return args.id ? [products[args.id]] : values(products)
|
||||
return args.id ? [products[args.id]] : values(products);
|
||||
}
|
||||
},
|
||||
changes: {
|
||||
@ -114,7 +114,7 @@ const mutation = new GraphQLObjectType({
|
||||
}
|
||||
},
|
||||
resolve(root, args, ctx) {
|
||||
const changes = (() => {
|
||||
const ops = (() => {
|
||||
if (args.id) {
|
||||
return [args.id];
|
||||
}
|
||||
@ -128,7 +128,7 @@ const mutation = new GraphQLObjectType({
|
||||
});
|
||||
})();
|
||||
|
||||
changes.forEach((id) => {
|
||||
ops.forEach((id) => {
|
||||
delete changes[id];
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user