diff --git a/spikes/stacks/redux-thunk/src/client/actions.js b/spikes/stacks/redux-thunk/src/client/actions.js index c25f3629..b870d56a 100644 --- a/spikes/stacks/redux-thunk/src/client/actions.js +++ b/spikes/stacks/redux-thunk/src/client/actions.js @@ -1,4 +1,5 @@ module.exports = { + ...require('./reducers/app').actions, ...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 index 1714d755..6f8e6afc 100644 --- a/spikes/stacks/redux-thunk/src/client/api.js +++ b/spikes/stacks/redux-thunk/src/client/api.js @@ -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); + }); +}; diff --git a/spikes/stacks/redux-thunk/src/client/components/printers.js b/spikes/stacks/redux-thunk/src/client/components/printers.js index 1e8f4209..c5e71317 100644 --- a/spikes/stacks/redux-thunk/src/client/components/printers.js +++ b/spikes/stacks/redux-thunk/src/client/components/printers.js @@ -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 (
  • - {name} + {name} {msg}
  • ); }); diff --git a/spikes/stacks/redux-thunk/src/client/containers/app.js b/spikes/stacks/redux-thunk/src/client/containers/app.js index b00d5e80..eebb35cc 100644 --- a/spikes/stacks/redux-thunk/src/client/containers/app.js +++ b/spikes/stacks/redux-thunk/src/client/containers/app.js @@ -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('../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 (
    {children}
    ); } -}); +})); -module.exports = connect()(App); +module.exports = (props) => { + return ( + + + + + + ); +}; diff --git a/spikes/stacks/redux-thunk/src/client/containers/print.js b/spikes/stacks/redux-thunk/src/client/containers/print.js index 9f3d9686..e226d38e 100644 --- a/spikes/stacks/redux-thunk/src/client/containers/print.js +++ b/spikes/stacks/redux-thunk/src/client/containers/print.js @@ -11,13 +11,17 @@ const Change = require('../components/change'); const actions = require('../actions'); const { - fetchChanges + fetchChanges, + lockPrinter, + print, + transitionTo } = actions; const { BrowserRouter, Miss, Match, + Router } = ReactRouter; const { @@ -30,8 +34,11 @@ const Print = ({ changes = [], lockPrinter, fetchChanges, + onPrint, loaded, - loading + loading, + locked, + router }) => { const allChanges = () => { return ( @@ -58,6 +65,18 @@ const Print = ({ return change.id === params.id; }); + if (!change) { + return ( +

    Change not found

    + ); + } + + const _onPrint = (id) => { + return () => { + return onPrint(id); + }; + }; + // TODO: don't load all changes return (
    @@ -69,6 +88,7 @@ const Print = ({ > +
    ); }; @@ -79,6 +99,8 @@ const Print = ({

    Printers

    @@ -93,17 +115,25 @@ 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')); + }); } }; }; -module.exports = connect(mapStateToProps, mapDispatchToProps)(Print); +module.exports = connect(mapStateToProps, mapDispatchToProps, )(Print); diff --git a/spikes/stacks/redux-thunk/src/client/intl.json b/spikes/stacks/redux-thunk/src/client/intl.json new file mode 100644 index 00000000..077404aa --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/intl.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/spikes/stacks/redux-thunk/src/client/reducers/app.js b/spikes/stacks/redux-thunk/src/client/reducers/app.js new file mode 100644 index 00000000..afedb378 --- /dev/null +++ b/spikes/stacks/redux-thunk/src/client/reducers/app.js @@ -0,0 +1,30 @@ +const ReduxActions = require('redux-actions'); +const app = require('../../../package.json').name; + +const { + createAction, + handleActions +} = ReduxActions; + +const UPDATE_ROUTER = `${app}/changes/UPDATE_ROUTER`; + +exports.ui = handleActions({ + [UPDATE_ROUTER]: (state, action) => { + return { + ...state, + router: action.payload + }; + } +}, {}); + +const actions = exports.actions = { + updateRouter: (router) => { + return { + type: UPDATE_ROUTER, + payload: router + }; + }, + transitionTo: (pathname) => (dispatch, getState) => { + return getState().ui.app.router.transitionTo(pathname); + } +}; diff --git a/spikes/stacks/redux-thunk/src/client/reducers/changes.js b/spikes/stacks/redux-thunk/src/client/reducers/changes.js index ab191a59..4e300f29 100644 --- a/spikes/stacks/redux-thunk/src/client/reducers/changes.js +++ b/spikes/stacks/redux-thunk/src/client/reducers/changes.js @@ -8,10 +8,12 @@ const { } = ReduxActions; const { - fetchChanges + fetchChanges, + removeChange } = api; const FETCH_CHANGES = `${app}/changes/FETCH_CHANGES`; +const REMOVE_CHANGE = `${app}/changes/REMOVE_CHANGE`; exports.data = handleActions({ [`${FETCH_CHANGES}_FULFILLED`]: (state, action) => { @@ -46,11 +48,19 @@ exports.ui = handleActions({ loaded: false }); -exports.actions = { +const actions = exports.actions = { fetchChanges: () => { return { type: FETCH_CHANGES, payload: fetchChanges() }; + }, + removeChange: (id) => (dispatch) => { + return dispatch({ + type: REMOVE_CHANGE, + payload: removeChange(id) + }).then(() => { + return dispatch(actions.fetchChanges()); + }); } }; diff --git a/spikes/stacks/redux-thunk/src/client/reducers/index.js b/spikes/stacks/redux-thunk/src/client/reducers/index.js index d5565b04..19bc79f9 100644 --- a/spikes/stacks/redux-thunk/src/client/reducers/index.js +++ b/spikes/stacks/redux-thunk/src/client/reducers/index.js @@ -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 }) }); }; diff --git a/spikes/stacks/redux-thunk/src/client/reducers/printers.js b/spikes/stacks/redux-thunk/src/client/reducers/printers.js index d44b332c..4c2b2e68 100644 --- a/spikes/stacks/redux-thunk/src/client/reducers/printers.js +++ b/spikes/stacks/redux-thunk/src/client/reducers/printers.js @@ -1,14 +1,23 @@ const ReduxActions = require('redux-actions'); const app = require('../../../package.json').name; const find = require('lodash.find'); +const changes = require('./changes'); const { createAction, handleActions } = ReduxActions; +const { + actions: { + 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`; exports.data = handleActions({ [UPDATE_PRINTERS]: (state, action) => { @@ -27,8 +36,7 @@ exports.ui = handleActions({ const locked = (find(action.payload, (printer) => { return ( printer.lock && - state.locked && - printer.lock === state.locked + printer.lock === state.id ); }) || {}).id || ''; @@ -42,7 +50,157 @@ exports.ui = handleActions({ locked: '' }); -exports.actions = { - updatePrinters: createAction(UPDATE_PRINTERS), - updateWorkerId: createAction(UPDATE_WORKER_ID) +// 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 actions = exports.actions = { + updatePrinters: createAction(UPDATE_PRINTERS), + updateWorkerId: createAction(UPDATE_WORKER_ID), + 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(); + }, + 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 actions.print(printerId)(dispatch, getState); + }); + } + + return printer.lock ? askToOverride(printerId) : lock(printerId); + }); + }; + + return !locked ? askToLock() : print(); + } }; diff --git a/spikes/stacks/redux-thunk/src/client/root.js b/spikes/stacks/redux-thunk/src/client/root.js index 8fceb79f..9f77b3d9 100644 --- a/spikes/stacks/redux-thunk/src/client/root.js +++ b/spikes/stacks/redux-thunk/src/client/root.js @@ -2,11 +2,9 @@ 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'); @@ -16,25 +14,33 @@ const { const { BrowserRouter, - Miss, - Match } = 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 ( - - - - - - - + + {App} + ); diff --git a/spikes/stacks/redux-thunk/src/client/store.js b/spikes/stacks/redux-thunk/src/client/store.js index a50f4fce..d18102b3 100644 --- a/spikes/stacks/redux-thunk/src/client/store.js +++ b/spikes/stacks/redux-thunk/src/client/store.js @@ -18,7 +18,7 @@ module.exports = (state = Object.freeze({})) => { state, compose( applyMiddleware( - createLogger(), +// createLogger(), promiseMiddleware(), thunk ) diff --git a/spikes/stacks/redux-thunk/src/client/worker.js b/spikes/stacks/redux-thunk/src/client/worker.js index 3fe1b68d..c3a472a8 100644 --- a/spikes/stacks/redux-thunk/src/client/worker.js +++ b/spikes/stacks/redux-thunk/src/client/worker.js @@ -9,12 +9,44 @@ 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) { diff --git a/spikes/stacks/redux-thunk/src/server/schema.js b/spikes/stacks/redux-thunk/src/server/schema.js index 9e73a578..f9ae591c 100644 --- a/spikes/stacks/redux-thunk/src/server/schema.js +++ b/spikes/stacks/redux-thunk/src/server/schema.js @@ -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]; }); }