1
0
mirror of https://github.com/yldio/copilot.git synced 2024-09-21 05:43:52 +03:00

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 = { module.exports = {
...require('./reducers/app').actions,
...require('./reducers/printers').actions, ...require('./reducers/printers').actions,
...require('./reducers/changes').actions ...require('./reducers/changes').actions
}; };

View File

@ -24,3 +24,26 @@ exports.fetchChanges = () => {
return data.changes; 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 = ({ module.exports = ({
printers = [], printers = [],
locked = '',
onClick onClick
}) => { }) => {
const _onClick = (id) => { const _onClick = (id) => {
@ -12,11 +13,20 @@ module.exports = ({
const lis = printers.map(({ const lis = printers.map(({
name, name,
lock,
id id
}) => { }) => {
const msg = (() => {
if (!lock) {
return '';
}
return (locked === id) ? '(Locked to you)' : `(Locked to ${lock})`;
})();
return ( return (
<li key={id}> <li key={id}>
<a onClick={_onClick(id)}>{name}</a> <a onClick={_onClick(id)}>{name} {msg}</a>
</li> </li>
); );
}); });

View File

@ -1,27 +1,55 @@
const ReactRedux = require('react-redux'); const ReactRedux = require('react-redux');
const ReactRouter = require('react-router');
const React = require('react'); const React = require('react');
const NotFound = require('./not-found');
const Home = require('./home');
const Print = require('./print');
const actions = require('../actions');
const { const {
connect connect
} = ReactRedux; } = ReactRedux;
const App = React.createClass({ const {
Miss,
Match
} = ReactRouter;
const {
updateRouter
} = actions
const App = connect()(React.createClass({
componentDidMount: function() { componentDidMount: function() {
require('../worker').on('action', (action) => { require('../worker').on('action', this.props.dispatch);
this.props.dispatch(action);
});
}, },
render: function() { render: function() {
const { const {
children children,
router,
dispatch
} = this.props; } = 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 ( return (
<div> <div>
{children} {children}
</div> </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 actions = require('../actions');
const { const {
fetchChanges fetchChanges,
lockPrinter,
print,
transitionTo
} = actions; } = actions;
const { const {
BrowserRouter, BrowserRouter,
Miss, Miss,
Match, Match,
Router
} = ReactRouter; } = ReactRouter;
const { const {
@ -30,8 +34,11 @@ const Print = ({
changes = [], changes = [],
lockPrinter, lockPrinter,
fetchChanges, fetchChanges,
onPrint,
loaded, loaded,
loading loading,
locked,
router
}) => { }) => {
const allChanges = () => { const allChanges = () => {
return ( return (
@ -58,6 +65,18 @@ const Print = ({
return change.id === params.id; 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 // TODO: don't load all changes
return ( return (
<div> <div>
@ -69,6 +88,7 @@ const Print = ({
> >
<Change {...change} /> <Change {...change} />
</Loader> </Loader>
<button onClick={_onPrint(params.id)}>Print</button>
</div> </div>
); );
}; };
@ -79,6 +99,8 @@ const Print = ({
<p>Printers</p> <p>Printers</p>
<Printers <Printers
printers={printers} printers={printers}
onClick={lockPrinter}
locked={locked}
/> />
</div> </div>
@ -93,17 +115,25 @@ const mapStateToProps = (state) => {
loaded: state.ui.changes.loaded, loaded: state.ui.changes.loaded,
loading: state.ui.changes.loading, loading: state.ui.changes.loading,
changes: state.data.changes, changes: state.data.changes,
printers: state.data.printers printers: state.data.printers,
locked: state.ui.printers.locked
}; };
}; };
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return { return {
lockPrinter: (id) => {}, lockPrinter: (id) => {
return dispatch(lockPrinter(id));
},
fetchChanges: () => { 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; } = ReduxActions;
const { const {
fetchChanges fetchChanges,
removeChange
} = api; } = api;
const FETCH_CHANGES = `${app}/changes/FETCH_CHANGES`; const FETCH_CHANGES = `${app}/changes/FETCH_CHANGES`;
const REMOVE_CHANGE = `${app}/changes/REMOVE_CHANGE`;
exports.data = handleActions({ exports.data = handleActions({
[`${FETCH_CHANGES}_FULFILLED`]: (state, action) => { [`${FETCH_CHANGES}_FULFILLED`]: (state, action) => {
@ -46,11 +48,19 @@ exports.ui = handleActions({
loaded: false loaded: false
}); });
exports.actions = { const actions = exports.actions = {
fetchChanges: () => { fetchChanges: () => {
return { return {
type: FETCH_CHANGES, type: FETCH_CHANGES,
payload: fetchChanges() 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 reduceReducers = require('reduce-reducers');
const Redux = require('redux'); const Redux = require('redux');
const app = require('./app');
const printers = require('./printers'); const printers = require('./printers');
const changes = require('./changes'); const changes = require('./changes');
@ -15,7 +16,9 @@ module.exports = () => {
changes: changes.data changes: changes.data
}), }),
ui: combineReducers({ 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 ReduxActions = require('redux-actions');
const app = require('../../../package.json').name; const app = require('../../../package.json').name;
const find = require('lodash.find'); const find = require('lodash.find');
const changes = require('./changes');
const { const {
createAction, createAction,
handleActions handleActions
} = ReduxActions; } = ReduxActions;
const {
actions: {
removeChange
}
} = changes;
const UPDATE_PRINTERS = `${app}/printers/UPDATE_PRINTERS`; const UPDATE_PRINTERS = `${app}/printers/UPDATE_PRINTERS`;
const UPDATE_WORKER_ID = `${app}/printers/UPDATE_WORKER_ID`; const UPDATE_WORKER_ID = `${app}/printers/UPDATE_WORKER_ID`;
const LOCK_PRINTER = `${app}/printers/LOCK_PRINTER`;
const PRINT = `${app}/printers/PRINT`;
exports.data = handleActions({ exports.data = handleActions({
[UPDATE_PRINTERS]: (state, action) => { [UPDATE_PRINTERS]: (state, action) => {
@ -27,8 +36,7 @@ exports.ui = handleActions({
const locked = (find(action.payload, (printer) => { const locked = (find(action.payload, (printer) => {
return ( return (
printer.lock && printer.lock &&
state.locked && printer.lock === state.id
printer.lock === state.locked
); );
}) || {}).id || ''; }) || {}).id || '';
@ -42,7 +50,157 @@ exports.ui = handleActions({
locked: '' locked: ''
}); });
exports.actions = { // confirm should be an async op,
updatePrinters: createAction(UPDATE_PRINTERS), // let's mock it that way
updateWorkerId: createAction(UPDATE_WORKER_ID) 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 ReactHotLoader = require('react-hot-loader');
const ReactRouter = require('react-router'); const ReactRouter = require('react-router');
const ReactRedux = require('react-redux'); const ReactRedux = require('react-redux');
const ReactIntl = require('react-intl');
const App = require('./containers/app'); 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('./store');
@ -16,25 +14,33 @@ const {
const { const {
BrowserRouter, BrowserRouter,
Miss,
Match
} = ReactRouter; } = ReactRouter;
const { const {
Provider Provider
} = ReactRedux; } = 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 = () => { module.exports = () => {
return ( return (
<AppContainer> <AppContainer>
<Provider store={store()}> <Provider store={store()}>
<BrowserRouter> <IntlProvider
<App> locale={locale}
<Match exactly pattern='/' component={Home} /> defaultLocale='en'
<Match pattern='/print' component={Print} /> >
<Miss component={NotFound}/> <BrowserRouter>{App}</BrowserRouter>
</App> </IntlProvider>
</BrowserRouter>
</Provider> </Provider>
</AppContainer> </AppContainer>
); );

View File

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

View File

@ -9,12 +9,44 @@ let isMaster = crosstab.util.tabs['MASTER_TAB'].id === crosstab.id;
const bridge = new Map(); const bridge = new Map();
const { const {
updatePrinters updatePrinters,
updateWorkerId
} = actions; } = actions;
const printers = {
'1': {
id: '1',
name: 'Main printer',
lock: ''
},
'2': {
id: '2',
name: 'Handled printer',
lock: ''
}
};
const handlers = { const handlers = {
'PRINT': (action, fn) => {}, 'PRINT': (action, fn) => {
fn();
},
'LOCK_PRINTER': (action, 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(); fn();
} }
}; };
@ -46,7 +78,10 @@ crosstab.util.events.on('message', ({
return emitter.emit('action', data); return emitter.emit('action', data);
} }
handlers[data.type](data, (err, res) => { handlers[data.type]({
...data,
_origin: origin
}, (err, res) => {
crosstab.broadcast('message', { crosstab.broadcast('message', {
...data, ...data,
error: err && err.message error: err && err.message
@ -66,7 +101,10 @@ const dispatch = module.exports.dispatch = (action, tab) => {
if (isMaster && !tab) { if (isMaster && !tab) {
if (handlers[action.type]) { if (handlers[action.type]) {
return new Promise(function(resolve, reject) { 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); return err ? reject(err) : resolve(res);
}); });
}); });
@ -90,20 +128,9 @@ const dispatch = module.exports.dispatch = (action, tab) => {
return then; return then;
}; };
const printers = { setTimeout(function() {
'1': { emitter.emit('action', updateWorkerId(crosstab.id));
id: '1', }, 450);
name: 'Main printer',
lock: ''
},
'2': {
id: '2',
name: 'Handled printer',
lock: ''
}
};
emitter.emit('action', updateWorkerId(crosstab.id));
setInterval(() => { setInterval(() => {
if (!isMaster) { if (!isMaster) {

View File

@ -114,7 +114,7 @@ const mutation = new GraphQLObjectType({
} }
}, },
resolve(root, args, ctx) { resolve(root, args, ctx) {
const changes = (() => { const ops = (() => {
if (args.id) { if (args.id) {
return [args.id]; return [args.id];
} }
@ -128,7 +128,7 @@ const mutation = new GraphQLObjectType({
}); });
})(); })();
changes.forEach((id) => { ops.forEach((id) => {
delete changes[id]; delete changes[id];
}); });
} }