working redux-thunk

This commit is contained in:
Sérgio Ramos 2016-10-16 22:35:09 +01:00
parent 2a4dad3bf5
commit 7eb639297f
14 changed files with 384 additions and 55 deletions

View File

@ -1,4 +1,5 @@
module.exports = {
...require('./reducers/app').actions,
...require('./reducers/printers').actions,
...require('./reducers/changes').actions
};

View File

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

View File

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

View File

@ -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 (
<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>
);
};

View File

@ -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 (
<p>Change not found</p>
);
}
const _onPrint = (id) => {
return () => {
return onPrint(id);
};
};
// TODO: don't load all changes
return (
<div>
@ -69,6 +88,7 @@ const Print = ({
>
<Change {...change} />
</Loader>
<button onClick={_onPrint(params.id)}>Print</button>
</div>
);
};
@ -79,6 +99,8 @@ const Print = ({
<p>Printers</p>
<Printers
printers={printers}
onClick={lockPrinter}
locked={locked}
/>
</div>
@ -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);

View File

@ -0,0 +1,3 @@
{
}

View File

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

View File

@ -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());
});
}
};

View File

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

View File

@ -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();
}
};

View File

@ -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 (
<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>
);

View File

@ -18,7 +18,7 @@ module.exports = (state = Object.freeze({})) => {
state,
compose(
applyMiddleware(
createLogger(),
// createLogger(),
promiseMiddleware(),
thunk
)

View File

@ -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) {

View File

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