From 50a265da8c521f2937bccade3e3e9ceaa4d7f19b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Ramos?= Date: Fri, 14 Oct 2016 10:45:12 +0100 Subject: [PATCH] redux-thunk spike wip --- spikes/stacks/redux-thunk/.babelrc | 15 ++ spikes/stacks/redux-thunk/.eslintrc | 29 ++++ spikes/stacks/redux-thunk/.gitignore | 4 + .../stacks/redux-thunk/.storybook/config.js | 5 + spikes/stacks/redux-thunk/package.json | 73 +++++++++ .../stacks/redux-thunk/src/client/actions.js | 4 + spikes/stacks/redux-thunk/src/client/api.js | 26 ++++ .../src/client/components/change.js | 14 ++ .../src/client/components/changes.js | 39 +++++ .../src/client/components/loader.js | 52 +++++++ .../src/client/components/printers.js | 29 ++++ .../redux-thunk/src/client/containers/app.js | 27 ++++ .../redux-thunk/src/client/containers/home.js | 14 ++ .../src/client/containers/not-found.js | 7 + .../src/client/containers/print.js | 109 ++++++++++++++ spikes/stacks/redux-thunk/src/client/index.js | 19 +++ .../src/client/reducers/changes.js | 56 +++++++ .../redux-thunk/src/client/reducers/index.js | 21 +++ .../src/client/reducers/printers.js | 48 ++++++ spikes/stacks/redux-thunk/src/client/root.js | 41 +++++ spikes/stacks/redux-thunk/src/client/store.js | 27 ++++ .../stacks/redux-thunk/src/client/worker.js | 117 +++++++++++++++ .../redux-thunk/src/config/webpack.config.js | 41 +++++ .../redux-thunk/src/server/data/changes.json | 57 +++++++ .../redux-thunk/src/server/data/products.json | 83 ++++++++++ spikes/stacks/redux-thunk/src/server/index.js | 28 ++++ .../stacks/redux-thunk/src/server/schema.js | 142 ++++++++++++++++++ spikes/stacks/redux-thunk/static/index.html | 10 ++ spikes/stacks/redux-thunk/stories/index.js | 10 ++ spikes/stacks/redux-thunk/test/index.js | 13 ++ 30 files changed, 1160 insertions(+) create mode 100644 spikes/stacks/redux-thunk/.babelrc create mode 100644 spikes/stacks/redux-thunk/.eslintrc create mode 100644 spikes/stacks/redux-thunk/.gitignore create mode 100644 spikes/stacks/redux-thunk/.storybook/config.js create mode 100644 spikes/stacks/redux-thunk/package.json create mode 100644 spikes/stacks/redux-thunk/src/client/actions.js create mode 100644 spikes/stacks/redux-thunk/src/client/api.js create mode 100644 spikes/stacks/redux-thunk/src/client/components/change.js create mode 100644 spikes/stacks/redux-thunk/src/client/components/changes.js create mode 100644 spikes/stacks/redux-thunk/src/client/components/loader.js create mode 100644 spikes/stacks/redux-thunk/src/client/components/printers.js create mode 100644 spikes/stacks/redux-thunk/src/client/containers/app.js create mode 100644 spikes/stacks/redux-thunk/src/client/containers/home.js create mode 100644 spikes/stacks/redux-thunk/src/client/containers/not-found.js create mode 100644 spikes/stacks/redux-thunk/src/client/containers/print.js create mode 100644 spikes/stacks/redux-thunk/src/client/index.js create mode 100644 spikes/stacks/redux-thunk/src/client/reducers/changes.js create mode 100644 spikes/stacks/redux-thunk/src/client/reducers/index.js create mode 100644 spikes/stacks/redux-thunk/src/client/reducers/printers.js create mode 100644 spikes/stacks/redux-thunk/src/client/root.js create mode 100644 spikes/stacks/redux-thunk/src/client/store.js create mode 100644 spikes/stacks/redux-thunk/src/client/worker.js create mode 100644 spikes/stacks/redux-thunk/src/config/webpack.config.js create mode 100644 spikes/stacks/redux-thunk/src/server/data/changes.json create mode 100644 spikes/stacks/redux-thunk/src/server/data/products.json create mode 100644 spikes/stacks/redux-thunk/src/server/index.js create mode 100644 spikes/stacks/redux-thunk/src/server/schema.js create mode 100644 spikes/stacks/redux-thunk/static/index.html create mode 100644 spikes/stacks/redux-thunk/stories/index.js create mode 100644 spikes/stacks/redux-thunk/test/index.js diff --git a/spikes/stacks/redux-thunk/.babelrc b/spikes/stacks/redux-thunk/.babelrc new file mode 100644 index 00000000..82cc857a --- /dev/null +++ b/spikes/stacks/redux-thunk/.babelrc @@ -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" +} diff --git a/spikes/stacks/redux-thunk/.eslintrc b/spikes/stacks/redux-thunk/.eslintrc new file mode 100644 index 00000000..19bd88dc --- /dev/null +++ b/spikes/stacks/redux-thunk/.eslintrc @@ -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 + }] + } +} \ No newline at end of file diff --git a/spikes/stacks/redux-thunk/.gitignore b/spikes/stacks/redux-thunk/.gitignore new file mode 100644 index 00000000..52bbeb64 --- /dev/null +++ b/spikes/stacks/redux-thunk/.gitignore @@ -0,0 +1,4 @@ +/node_modules +coverage +.nyc_output + diff --git a/spikes/stacks/redux-thunk/.storybook/config.js b/spikes/stacks/redux-thunk/.storybook/config.js new file mode 100644 index 00000000..d17d412a --- /dev/null +++ b/spikes/stacks/redux-thunk/.storybook/config.js @@ -0,0 +1,5 @@ +const Storybook = require('@kadira/storybook'); + +Storybook.configure(() => { + require('../stories'); +}, module); diff --git a/spikes/stacks/redux-thunk/package.json b/spikes/stacks/redux-thunk/package.json new file mode 100644 index 00000000..27e8365f --- /dev/null +++ b/spikes/stacks/redux-thunk/package.json @@ -0,0 +1,73 @@ +{ + "name": "redux-thunks-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.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.5", + "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-promise-middleware": "^4.1.0", + "redux-thunk": "^2.1.0", + "webpack": "^1.13.2", + "webpack-dev-server": "^1.16.1" + }, + "devDependencies": { + "@kadira/storybook": "^2.21.0", + "ava": "^0.16.0", + "babel-register": "^6.16.3", + "enzyme": "^2.4.1", + "eslint": "^3.7.0", + "eslint-config-semistandard": "^7.0.0", + "eslint-config-standard": "^6.2.0", + "eslint-plugin-babel": "^3.3.0", + "eslint-plugin-promise": "^2.0.1", + "eslint-plugin-react": "^6.3.0", + "eslint-plugin-standard": "^2.0.1", + "nyc": "^8.3.1", + "react-addons-test-utils": "^15.3.2" + }, + "ava": { + "require": [ + "babel-register" + ], + "babel": "inherit" + } +} diff --git a/spikes/stacks/redux-thunk/src/client/actions.js b/spikes/stacks/redux-thunk/src/client/actions.js new file mode 100644 index 00000000..c25f3629 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/actions.js @@ -0,0 +1,4 @@ +module.exports = { + ...require('./reducers/printers').actions, + ...require('./reducers/changes').actions +}; diff --git a/spikes/stacks/redux-thunk/src/client/api.js b/spikes/stacks/redux-thunk/src/client/api.js new file mode 100644 index 00000000..1714d755 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/api.js @@ -0,0 +1,26 @@ +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; + }); +}; diff --git a/spikes/stacks/redux-thunk/src/client/components/change.js b/spikes/stacks/redux-thunk/src/client/components/change.js new file mode 100644 index 00000000..3a317d26 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/components/change.js @@ -0,0 +1,14 @@ +const React = require('react'); + +module.exports = ({ + price, + currency, + product, + id +}) => { + return ( +

+ {product.artist}: {product.title} - {product.currencr}{product.price} > {currency}{price} +

+ ); +}; diff --git a/spikes/stacks/redux-thunk/src/client/components/changes.js b/spikes/stacks/redux-thunk/src/client/components/changes.js new file mode 100644 index 00000000..216cfe4d --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/components/changes.js @@ -0,0 +1,39 @@ +const ReactRouter = require('react-router'); +const React = require('react'); + +const { + Link +} = ReactRouter; + +module.exports = ({ + changes = [], + pathname, + onClick +}) => { + const _onClick = (id) => { + return () => { + onClick(id); + }; + }; + + const lis = changes.map(({ + price, + currency, + product, + id + }) => { + return ( +
  • + + {product.artist}: {product.title} - {product.currencr}{product.price} > {currency}{price} + +
  • + ); + }); + + return ( + + ); +}; diff --git a/spikes/stacks/redux-thunk/src/client/components/loader.js b/spikes/stacks/redux-thunk/src/client/components/loader.js new file mode 100644 index 00000000..0bc9c9c7 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/components/loader.js @@ -0,0 +1,52 @@ +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 { + fetch, + loading, + loaded, + render, + children + } = this.props; + + const _loaded = !loading && !loaded; + const component = _loaded ? (children ? children : render()) : null; + + return ( + + {component} + + ); + } +}); diff --git a/spikes/stacks/redux-thunk/src/client/components/printers.js b/spikes/stacks/redux-thunk/src/client/components/printers.js new file mode 100644 index 00000000..1e8f4209 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/components/printers.js @@ -0,0 +1,29 @@ +const React = require('react'); + +module.exports = ({ + printers = [], + onClick +}) => { + const _onClick = (id) => { + return () => { + onClick(id); + }; + }; + + const lis = printers.map(({ + name, + id + }) => { + return ( +
  • + {name} +
  • + ); + }); + + return ( + + ); +}; diff --git a/spikes/stacks/redux-thunk/src/client/containers/app.js b/spikes/stacks/redux-thunk/src/client/containers/app.js new file mode 100644 index 00000000..b00d5e80 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/containers/app.js @@ -0,0 +1,27 @@ +const ReactRedux = require('react-redux'); +const React = require('react'); + +const { + connect +} = ReactRedux; + +const App = React.createClass({ + componentDidMount: function() { + require('../worker').on('action', (action) => { + this.props.dispatch(action); + }); + }, + render: function() { + const { + children + } = this.props; + + return ( +
    + {children} +
    + ); + } +}); + +module.exports = connect()(App); diff --git a/spikes/stacks/redux-thunk/src/client/containers/home.js b/spikes/stacks/redux-thunk/src/client/containers/home.js new file mode 100644 index 00000000..952a726a --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/containers/home.js @@ -0,0 +1,14 @@ +const ReactRouter = require('react-router'); +const React = require('react'); + +const { + Link +} = ReactRouter; + +module.exports = () => { + return ( +
    + Start +
    + ); +}; diff --git a/spikes/stacks/redux-thunk/src/client/containers/not-found.js b/spikes/stacks/redux-thunk/src/client/containers/not-found.js new file mode 100644 index 00000000..ed7e90b8 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/containers/not-found.js @@ -0,0 +1,7 @@ +const React = require('react'); + +module.exports = () => { + return ( +

    Not found

    + ); +}; diff --git a/spikes/stacks/redux-thunk/src/client/containers/print.js b/spikes/stacks/redux-thunk/src/client/containers/print.js new file mode 100644 index 00000000..9f3d9686 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/containers/print.js @@ -0,0 +1,109 @@ +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('../actions'); + +const { + fetchChanges +} = actions; + +const { + BrowserRouter, + Miss, + Match, +} = ReactRouter; + +const { + connect +} = ReactRedux; + +const Print = ({ + pathname, + printers = [], + changes = [], + lockPrinter, + fetchChanges, + loaded, + loading +}) => { + const allChanges = () => { + return ( +
    +

    Changes

    + + + +
    + ); + }; + + const singleChange = ({ + params + }) => { + const change = find(changes, (change) => { + return change.id === params.id; + }); + + // TODO: don't load all changes + return ( +
    +

    Change

    + + + +
    + ); + }; + + return ( +
    +
    +

    Printers

    + +
    + + + +
    + ); +}; + +const mapStateToProps = (state) => { + return { + loaded: state.ui.changes.loaded, + loading: state.ui.changes.loading, + changes: state.data.changes, + printers: state.data.printers + }; +}; + +const mapDispatchToProps = (dispatch) => { + return { + lockPrinter: (id) => {}, + fetchChanges: () => { + dispatch(fetchChanges()); + } + }; +}; + +module.exports = connect(mapStateToProps, mapDispatchToProps)(Print); diff --git a/spikes/stacks/redux-thunk/src/client/index.js b/spikes/stacks/redux-thunk/src/client/index.js new file mode 100644 index 00000000..6ebb1c56 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/index.js @@ -0,0 +1,19 @@ +const worker = require('./worker'); // singleton + +const React = require('react'); +const ReactDOM = require('react-dom'); + +const render = () => { + const Root = require('./root'); + + ReactDOM.render( + , + document.getElementById('root') + ); +}; + +render(); + +if (module.hot) { + module.hot.accept('./root', render); +} \ No newline at end of file diff --git a/spikes/stacks/redux-thunk/src/client/reducers/changes.js b/spikes/stacks/redux-thunk/src/client/reducers/changes.js new file mode 100644 index 00000000..ab191a59 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/reducers/changes.js @@ -0,0 +1,56 @@ +const ReduxActions = require('redux-actions'); +const app = require('../../../package.json').name; +const api = require('../api'); + +const { + createAction, + handleActions +} = ReduxActions; + +const { + fetchChanges +} = api; + +const FETCH_CHANGES = `${app}/changes/FETCH_CHANGES`; + +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 +}); + +exports.actions = { + fetchChanges: () => { + return { + type: FETCH_CHANGES, + payload: fetchChanges() + }; + } +}; diff --git a/spikes/stacks/redux-thunk/src/client/reducers/index.js b/spikes/stacks/redux-thunk/src/client/reducers/index.js new file mode 100644 index 00000000..d5565b04 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/reducers/index.js @@ -0,0 +1,21 @@ +// const reduceReducers = require('reduce-reducers'); +const Redux = require('redux'); + +const printers = require('./printers'); +const changes = require('./changes'); + +const { + combineReducers +} = Redux; + +module.exports = () => { + return combineReducers({ + data: combineReducers({ + printers: printers.data, + changes: changes.data + }), + ui: combineReducers({ + changes: changes.ui + }) + }); +}; diff --git a/spikes/stacks/redux-thunk/src/client/reducers/printers.js b/spikes/stacks/redux-thunk/src/client/reducers/printers.js new file mode 100644 index 00000000..d44b332c --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/reducers/printers.js @@ -0,0 +1,48 @@ +const ReduxActions = require('redux-actions'); +const app = require('../../../package.json').name; +const find = require('lodash.find'); + +const { + createAction, + handleActions +} = ReduxActions; + +const UPDATE_PRINTERS = `${app}/printers/UPDATE_PRINTERS`; +const UPDATE_WORKER_ID = `${app}/printers/UPDATE_WORKER_ID`; + +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 && + state.locked && + printer.lock === state.locked + ); + }) || {}).id || ''; + + return { + ...state, + locked + }; + } +}, { + id: '', + locked: '' +}); + +exports.actions = { + updatePrinters: createAction(UPDATE_PRINTERS), + updateWorkerId: createAction(UPDATE_WORKER_ID) +}; diff --git a/spikes/stacks/redux-thunk/src/client/root.js b/spikes/stacks/redux-thunk/src/client/root.js new file mode 100644 index 00000000..8fceb79f --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/root.js @@ -0,0 +1,41 @@ +const React = require('react'); +const ReactHotLoader = require('react-hot-loader'); +const ReactRouter = require('react-router'); +const ReactRedux = require('react-redux'); + +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 { + AppContainer +} = ReactHotLoader; + +const { + BrowserRouter, + Miss, + Match +} = ReactRouter; + +const { + Provider +} = ReactRedux; + +module.exports = () => { + return ( + + + + + + + + + + + + ); +}; diff --git a/spikes/stacks/redux-thunk/src/client/store.js b/spikes/stacks/redux-thunk/src/client/store.js new file mode 100644 index 00000000..a50f4fce --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/store.js @@ -0,0 +1,27 @@ +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 redux = require('redux'); + + +const { + createStore, + compose, + applyMiddleware +} = redux; + +module.exports = (state = Object.freeze({})) => { + return createStore( + enableBatching(createReducer()), + state, + compose( + applyMiddleware( + createLogger(), + promiseMiddleware(), + thunk + ) + ) + ); +}; diff --git a/spikes/stacks/redux-thunk/src/client/worker.js b/spikes/stacks/redux-thunk/src/client/worker.js new file mode 100644 index 00000000..3fe1b68d --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/worker.js @@ -0,0 +1,117 @@ +const uuid = require('node-uuid'); +const Emitter = require('component-emitter'); +const crosstab = require('crosstab'); +const values = require('lodash.values'); +const actions = require('./actions'); + +const emitter = module.exports = new Emitter(); +let isMaster = crosstab.util.tabs['MASTER_TAB'].id === crosstab.id; +const bridge = new Map(); + +const { + updatePrinters +} = actions; + +const handlers = { + 'PRINT': (action, fn) => {}, + 'LOCK_PRINTER': (action, fn) => { + 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, (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, 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; +}; + +const printers = { + '1': { + id: '1', + name: 'Main printer', + lock: '' + }, + '2': { + id: '2', + name: 'Handled printer', + lock: '' + } +}; + +emitter.emit('action', updateWorkerId(crosstab.id)); + +setInterval(() => { + if (!isMaster) { + return; + } + + const action = updatePrinters(values(printers)); + + emitter.emit('action', action); + dispatch(action); +}, 500); diff --git a/spikes/stacks/redux-thunk/src/config/webpack.config.js b/spikes/stacks/redux-thunk/src/config/webpack.config.js new file mode 100644 index 00000000..98343222 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/config/webpack.config.js @@ -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'] + }] + } +}; diff --git a/spikes/stacks/redux-thunk/src/server/data/changes.json b/spikes/stacks/redux-thunk/src/server/data/changes.json new file mode 100644 index 00000000..fb3f42b7 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/server/data/changes.json @@ -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": "€" + } +} diff --git a/spikes/stacks/redux-thunk/src/server/data/products.json b/spikes/stacks/redux-thunk/src/server/data/products.json new file mode 100644 index 00000000..a683d718 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/server/data/products.json @@ -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": "€" + } +} diff --git a/spikes/stacks/redux-thunk/src/server/index.js b/spikes/stacks/redux-thunk/src/server/index.js new file mode 100644 index 00000000..a503007e --- /dev/null +++ b/spikes/stacks/redux-thunk/src/server/index.js @@ -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'); +}); diff --git a/spikes/stacks/redux-thunk/src/server/schema.js b/spikes/stacks/redux-thunk/src/server/schema.js new file mode 100644 index 00000000..9e73a578 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/server/schema.js @@ -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 changes = (() => { + 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; + }); + })(); + + changes.forEach((id) => { + delete changes[id]; + }); + } + } + } +}); + +module.exports = new GraphQLSchema({ + query, + mutation +}); diff --git a/spikes/stacks/redux-thunk/static/index.html b/spikes/stacks/redux-thunk/static/index.html new file mode 100644 index 00000000..d36d2c24 --- /dev/null +++ b/spikes/stacks/redux-thunk/static/index.html @@ -0,0 +1,10 @@ + + + + React Boilerplate + + +
    + + + diff --git a/spikes/stacks/redux-thunk/stories/index.js b/spikes/stacks/redux-thunk/stories/index.js new file mode 100644 index 00000000..3b098180 --- /dev/null +++ b/spikes/stacks/redux-thunk/stories/index.js @@ -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', () => ( + +)); diff --git a/spikes/stacks/redux-thunk/test/index.js b/spikes/stacks/redux-thunk/test/index.js new file mode 100644 index 00000000..62cf17c3 --- /dev/null +++ b/spikes/stacks/redux-thunk/test/index.js @@ -0,0 +1,13 @@ +const test = require('ava'); +const enzyme = require('enzyme'); +const React = require('react'); + +const { + shallow +} = enzyme; + +test('renders without exploding', (t) => { + const Home = require('../src/client/containers/home'); + const wrapper = shallow(); + t.deepEqual(wrapper.length, 1); +});