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];
});
}