From dffdcfcd606b26985e88558b687bb503e6e6e197 Mon Sep 17 00:00:00 2001 From: brian Date: Mon, 15 Mar 2021 18:04:18 -0400 Subject: [PATCH 1/4] fixed syncing a note's temp_text with its actual text, in different sitations; updating text updates 'modified' time on the mock server --- Note.js | 2 +- api-stub.js | 1 + handlers/App.js | 4 ++-- handlers/Note.js | 4 ++-- index.js | 8 ++++---- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Note.js b/Note.js index 6b8323e..a887b7b 100644 --- a/Note.js +++ b/Note.js @@ -8,7 +8,7 @@ function Note(vnode_init){ const {is_editing, note} = note_state; if(is_editing){ return m('.note', [ - m('textarea', {key: 'textarea', onchange: text_change_handler.bind(null, note_state, dispatch) }, note.text), + m('textarea', {key: 'textarea', onchange: text_change_handler.bind(null, note_state, dispatch) }, note_state.temp_text), m('.buttons', {key: 'editing-buttons'}, [ m('button', {key: 'cancel-button', onclick: cancel_handler.bind(null, note_state, dispatch) }, 'Cancel'), m('button', {key: 'save-button', onclick: save_handler.bind(null, note_state, dispatch) }, 'Save') diff --git a/api-stub.js b/api-stub.js index b704c18..d264224 100644 --- a/api-stub.js +++ b/api-stub.js @@ -35,6 +35,7 @@ endpoints.post['/save'] = function(resolve, reject, body){ const note = state.notes.find( n => n.id===note_id ); if(note){ note.text = text; + note.modified = Date.now(); } else{ state.notes.push({id: note_id, text, modified: Date.now()}); diff --git a/handlers/App.js b/handlers/App.js index 6633404..7c48668 100644 --- a/handlers/App.js +++ b/handlers/App.js @@ -1,6 +1,6 @@ import nanoid from '../nanoid.min.js'; -import api from '../api.js'; -//import api from '../api-stub.js'; +//import api from '../api.js'; +import api from '../api-stub.js'; const load_notes = function(state, dispatch){ api.post('/load-notes', {bin_id: state.bin.id}) diff --git a/handlers/Note.js b/handlers/Note.js index a48f899..911be87 100644 --- a/handlers/Note.js +++ b/handlers/Note.js @@ -1,5 +1,5 @@ -import api from '../api.js'; -//import api from '../api-stub.js'; +//import api from '../api.js'; +import api from '../api-stub.js'; const edit_handler = function(note_state, dispatch){ dispatch({type: 'update-note-editing', note_id: note_state.note.id, is_editing: true}); diff --git a/index.js b/index.js index 09fe7a7..4c71024 100644 --- a/index.js +++ b/index.js @@ -12,7 +12,7 @@ function search_reducer(old_state, new_state, action){ new_state.search_term = action.search_term; } else if(action.type === 'update-search-results'){ - new_state.notes = action.notes.map(note=>({is_editing: false, note})); + new_state.notes = action.notes.map(note=>({is_editing: false, temp_text: '', note})); new_state.search_term = old_state.search_term; } else{ @@ -22,10 +22,10 @@ function search_reducer(old_state, new_state, action){ function notes_reducer(old_state, new_state, action){ if(action.type === 'add-note'){ - new_state.notes = old_state.notes.concat([{is_editing: true, note: {id: action.id, text: '', modified: Date.now()}}]) + new_state.notes = ([{is_editing: true, note: {id: action.id, text: '', modified: Date.now()}}]).concat(old_state.notes) } else if(action.type === 'notes-loaded'){ - new_state.notes = action.notes.map(note=>({is_editing: false, note})); + new_state.notes = action.notes.map(note=>({is_editing: false, temp_text: note.text, note})); } else if(action.type === 'update-note-text'){ const i = old_state.notes.findIndex(note_state => note_state.note.id === action.note_id); @@ -35,7 +35,7 @@ function notes_reducer(old_state, new_state, action){ else if(action.type === 'update-note-editing'){ const i = old_state.notes.findIndex(note_state => note_state.note.id === action.note_id); new_state.notes = old_state.notes.slice(); - new_state.notes[i] = {...new_state.notes[i], is_editing: action.is_editing}; + new_state.notes[i] = {...new_state.notes[i], is_editing: action.is_editing, temp_text: new_state.notes[i].note.text}; } else{ new_state.notes = old_state.notes; From 8bac25ab2c2bfbc91d9e01f8200c57be8b7a861e Mon Sep 17 00:00:00 2001 From: brian Date: Mon, 15 Mar 2021 20:19:00 -0400 Subject: [PATCH 2/4] can create new bins --- App.js | 8 ++++---- api-stub.js | 30 ++++++++++++++++++++++-------- handlers/App.js | 11 ++++++++--- handlers/Note.js | 2 +- index.js | 15 ++++++++++----- 5 files changed, 45 insertions(+), 21 deletions(-) diff --git a/App.js b/App.js index 5c5353f..2b7721e 100644 --- a/App.js +++ b/App.js @@ -1,10 +1,10 @@ import Note from './Note.js'; -import {new_note_handler, search_term_change_handler, sorting_change_handler, load_notes} from './handlers/App.js'; +import {new_note_handler, search_term_change_handler, sorting_change_handler, load_notes, new_bin_handler} from './handlers/App.js'; function App(vnode_init){ const {state, dispatch} = vnode_init.attrs; - load_notes(state, dispatch); + //load_notes(state, dispatch); return { view: function(vnode){ const s = vnode.attrs.state; @@ -20,12 +20,12 @@ function App(vnode_init){ ]), m('.top-right', {key: 'top-right'}, [ m('.bin-id', {key: 'bin-id'}, s.bin.id), - m('button', {key: 'new-bin-button'}, 'New Bin...') + m('button', {key: 'new-bin-button', onclick: new_bin_handler.bind(null, s, dispatch)}, 'New Bin...') ]) ]), m('.main', {key: 'main'}, [ m('.notes', s.notes.map(note_state => - m(Note, {note_state, dispatch, key: note_state.note.id}) + m(Note, {state, note_state, dispatch, key: note_state.note.id}) )) ]) ]); diff --git a/api-stub.js b/api-stub.js index d264224..334bf83 100644 --- a/api-stub.js +++ b/api-stub.js @@ -4,10 +4,15 @@ const api_stub = {}; // for mocking persistent state, for use by the fake endpoints, just as a real server endpoint would have DB access for state: const state = { - notes: [ - {id: nanoid(), text: 'Note one', modified: Date.now()}, - {id: nanoid(), text: 'Note two', modified: Date.now()}, - {id: nanoid(), text: 'Note three', modified: Date.now()} + bins: [ + { + id: nanoid(), + notes: [ + {id: nanoid(), text: 'Note one', modified: Date.now()}, + {id: nanoid(), text: 'Note two', modified: Date.now()}, + {id: nanoid(), text: 'Note three', modified: Date.now()} + ] + } ] }; @@ -16,11 +21,15 @@ const endpoints = { }; endpoints.post['/load-notes'] = function(resolve, reject, body){ - resolve( {status: 'ok', notes: state.notes} ); + let i = 0; + if(body.bin_id){ + i = state.bins.findIndex(b=>b.id===body.bin_id); + } + resolve( {status: 'ok', notes: state.bins[i].notes} ); }; endpoints.post['/search'] = function(resolve, reject, body){ - const notes = state.notes.filter(n => n.text.indexOf(body.search_term) !== -1 ) + const notes = state.bins.find(b=>b.id===body.bin_id).notes.filter(n => n.text.indexOf(body.search_term) !== -1 ) if(body.sorting==='new->old'){ notes.sort((a,b) => a.modified-b.modified); } @@ -32,13 +41,18 @@ endpoints.post['/search'] = function(resolve, reject, body){ endpoints.post['/save'] = function(resolve, reject, body){ const {note_id, text} = body; - const note = state.notes.find( n => n.id===note_id ); + let bin = state.bins.find(b=>b.id===body.bin_id); + if(!bin){ + bin = {id: body.bin_id, notes: []}; + state.bins.push(bin); + } + const note = bin.notes.find( n => n.id===note_id ); if(note){ note.text = text; note.modified = Date.now(); } else{ - state.notes.push({id: note_id, text, modified: Date.now()}); + bin.notes.push({id: note_id, text, modified: Date.now()}); } resolve( {status: 'ok'} ); }; diff --git a/handlers/App.js b/handlers/App.js index 7c48668..40733b5 100644 --- a/handlers/App.js +++ b/handlers/App.js @@ -5,11 +5,11 @@ import api from '../api-stub.js'; const load_notes = function(state, dispatch){ api.post('/load-notes', {bin_id: state.bin.id}) .then(res=>{ - dispatch({type: 'notes-loaded', notes: res.notes}); + dispatch({type: 'notes-loaded', notes: res.notes, bin_id: state.bin.id}); }); }; const runSearch = function(state, dispatch){ - api.post('/search', {search_term: state.search_term, sorting: state.sorting}) + api.post('/search', {search_term: state.search_term, sorting: state.sorting, bin_id: state.bin.id}) .then(res=>{ dispatch({type:'update-search-results', notes: res.notes}); }); @@ -29,5 +29,10 @@ const sorting_change_handler = function(state, dispatch, e){ runSearch(state, dispatch); dispatch({type:'update-sorting', sorting: e.target.value}); }; +const new_bin_handler = function(state, dispatch){ + const id = nanoid(); + dispatch({type: 'new-bin', bin: {id}}); + dispatch({type: 'notes-loaded', notes: [], bin_id: id}); + }; -export {new_note_handler, search_term_change_handler, sorting_change_handler, load_notes}; \ No newline at end of file +export {new_note_handler, search_term_change_handler, sorting_change_handler, load_notes, new_bin_handler}; \ No newline at end of file diff --git a/handlers/Note.js b/handlers/Note.js index 911be87..4f0636c 100644 --- a/handlers/Note.js +++ b/handlers/Note.js @@ -15,7 +15,7 @@ const save_handler = function(note_state, dispatch){ dispatch({type: 'update-note-text', note_id: note_state.note.id, text: note_state.temp_text}); //note.text = temp_text; dispatch({type: 'update-note-editing', note_id: note_state.note.id, is_editing: false}); - api.post('/save', {note_id: note_state.note.id, text: note_state.temp_text}) + api.post('/save', {bin_id: note_state.bin_id, note_id: note_state.note.id, text: note_state.temp_text}) }; export { edit_handler, cancel_handler, text_change_handler, save_handler }; \ No newline at end of file diff --git a/index.js b/index.js index 4c71024..9b40569 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,12 @@ import nanoid from './nanoid.min.js'; var root = document.body; function bin_reducer(old_state, new_state, action){ - new_state.bin = old_state.bin; + if(action.type === 'new-bin'){ + new_state.bin = action.bin; + } + else{ + new_state.bin = old_state.bin; + } } function search_reducer(old_state, new_state, action){ @@ -12,7 +17,7 @@ function search_reducer(old_state, new_state, action){ new_state.search_term = action.search_term; } else if(action.type === 'update-search-results'){ - new_state.notes = action.notes.map(note=>({is_editing: false, temp_text: '', note})); + new_state.notes = action.notes.map(note=>({is_editing: false, temp_text: '', bin_id: old_state.bin.id, note})); new_state.search_term = old_state.search_term; } else{ @@ -22,10 +27,10 @@ function search_reducer(old_state, new_state, action){ function notes_reducer(old_state, new_state, action){ if(action.type === 'add-note'){ - new_state.notes = ([{is_editing: true, note: {id: action.id, text: '', modified: Date.now()}}]).concat(old_state.notes) + new_state.notes = ([{is_editing: true, temp_text: '', bin_id: old_state.bin.id, note: {id: action.id, text: '', modified: Date.now()}}]).concat(old_state.notes) } else if(action.type === 'notes-loaded'){ - new_state.notes = action.notes.map(note=>({is_editing: false, temp_text: note.text, note})); + new_state.notes = action.notes.map(note=>({is_editing: false, temp_text: note.text, bin_id: action.bin_id, note})); } else if(action.type === 'update-note-text'){ const i = old_state.notes.findIndex(note_state => note_state.note.id === action.note_id); @@ -64,7 +69,7 @@ function reducer(old_state, action){ const store = Redux.createStore(reducer, /* preloadedState, */ { bin: {id: nanoid()}, notes: [ - //{is_editing: false, note: {id: nanoid(), text: 'Note one', modified: 1}}, + //{is_editing: false, temp_text: '', bin_id: '', note: {id: nanoid(), text: 'Note one', modified: 1}}, ], search_term: '', sorting: 'new->old' From a248600c21111bc4198de7d7f23b22625b8805a7 Mon Sep 17 00:00:00 2001 From: brian Date: Wed, 17 Mar 2021 09:21:23 -0400 Subject: [PATCH 3/4] switch to immer.js --- handlers/App.js | 15 +++---- handlers/Note.js | 9 ++--- immer.min.js | 2 + index.html | 2 +- index.js | 100 ++++++++++++++--------------------------------- 5 files changed, 44 insertions(+), 84 deletions(-) create mode 100644 immer.min.js diff --git a/handlers/App.js b/handlers/App.js index 40733b5..ad24fb6 100644 --- a/handlers/App.js +++ b/handlers/App.js @@ -5,34 +5,35 @@ import api from '../api-stub.js'; const load_notes = function(state, dispatch){ api.post('/load-notes', {bin_id: state.bin.id}) .then(res=>{ - dispatch({type: 'notes-loaded', notes: res.notes, bin_id: state.bin.id}); + dispatch({type: 'notes-loaded', payload: res.notes}); }); }; const runSearch = function(state, dispatch){ api.post('/search', {search_term: state.search_term, sorting: state.sorting, bin_id: state.bin.id}) .then(res=>{ - dispatch({type:'update-search-results', notes: res.notes}); + dispatch({type:'update-search-results', payload: res.notes}); }); }; const new_note_handler = function(state, dispatch){ - dispatch({type:'add-note', id: nanoid()}); + dispatch({type:'add-note', payload:{id: nanoid(), date: Date.now()}}); }; const search_term_change_handler = function(state, dispatch, e){ if(e.code === 'Enter'){ runSearch(state, dispatch); } else{ - dispatch({type:'update-search-term', search_term: e.target.value}); + dispatch({type:'update-search-term', payload: e.target.value}); } }; const sorting_change_handler = function(state, dispatch, e){ runSearch(state, dispatch); - dispatch({type:'update-sorting', sorting: e.target.value}); + dispatch({type:'update-sorting', payload: e.target.value}); }; const new_bin_handler = function(state, dispatch){ const id = nanoid(); - dispatch({type: 'new-bin', bin: {id}}); - dispatch({type: 'notes-loaded', notes: [], bin_id: id}); + // TODO: consolidate: this will cause two redraws: + dispatch({type: 'new-bin', payload:{id}}); + dispatch({type: 'notes-loaded', payload: []}); }; export {new_note_handler, search_term_change_handler, sorting_change_handler, load_notes, new_bin_handler}; \ No newline at end of file diff --git a/handlers/Note.js b/handlers/Note.js index 4f0636c..4759926 100644 --- a/handlers/Note.js +++ b/handlers/Note.js @@ -2,19 +2,16 @@ import api from '../api-stub.js'; const edit_handler = function(note_state, dispatch){ - dispatch({type: 'update-note-editing', note_id: note_state.note.id, is_editing: true}); + dispatch({type: 'update-note-editing', payload:{id: note_state.note.id, is_editing: true}}); }; const cancel_handler = function(note_state, dispatch){ - dispatch({type: 'update-note-editing', note_id: note_state.note.id, is_editing: false}); + dispatch({type: 'update-note-editing', payload:{id: note_state.note.id, is_editing: false}}); }; const text_change_handler = function(note_state, dispatch, e){ note_state.temp_text = e.target.value; }; const save_handler = function(note_state, dispatch){ - // TODO: consolidate: this will cause two redraws: - dispatch({type: 'update-note-text', note_id: note_state.note.id, text: note_state.temp_text}); - //note.text = temp_text; - dispatch({type: 'update-note-editing', note_id: note_state.note.id, is_editing: false}); + dispatch({type: 'save-note-edit', payload:{id: note_state.note.id, text: note_state.temp_text}}); api.post('/save', {bin_id: note_state.bin_id, note_id: note_state.note.id, text: note_state.temp_text}) }; diff --git a/immer.min.js b/immer.min.js new file mode 100644 index 0000000..b2fe444 --- /dev/null +++ b/immer.min.js @@ -0,0 +1,2 @@ +!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((n=n||self).immer={})}(this,(function(n){function t(n){for(var t=arguments.length,r=Array(t>1?t-1:0),e=1;e3?t.t-4:t.t:Array.isArray(n)?1:v(n)?2:s(n)?3:0}function o(n,t){return 2===u(n)?n.has(t):Object.prototype.hasOwnProperty.call(n,t)}function f(n,t){return 2===u(n)?n.get(t):n[t]}function a(n,t,r){var e=u(n);2===e?n.set(t,r):3===e?(n.delete(t),n.add(r)):n[t]=r}function c(n,t){return n===t?0!==n||1/n==1/t:n!=n&&t!=t}function v(n){return X&&n instanceof Map}function s(n){return q&&n instanceof Set}function l(n){return n.i||n.u}function p(n){if(Array.isArray(n))return Array.prototype.slice.call(n);var t=U(n);delete t[L];for(var r=T(t),e=0;e1&&(n.set=n.add=n.clear=n.delete=d),Object.freeze(n),t&&i(n,(function(n,t){return h(t,!0)}),!0),n)}function d(){t(2)}function y(n){return null==n||"object"!=typeof n||Object.isFrozen(n)}function _(n){var r=V[n];return r||t(18,n),r}function b(n,t){V[n]||(V[n]=t)}function m(){return N}function j(n,t){t&&(_("Patches"),n.o=[],n.v=[],n.s=t)}function O(n){w(n),n.l.forEach(M),n.l=null}function w(n){n===N&&(N=n.p)}function S(n){return N={l:[],p:N,h:n,_:!0,m:0}}function M(n){var t=n[L];0===t.t||1===t.t?t.j():t.O=!0}function P(n,r){r.m=r.l.length;var i=r.l[0],u=void 0!==n&&n!==i;return r.h.S||_("ES5").M(r,n,u),u?(i[L].P&&(O(r),t(4)),e(n)&&(n=g(r,n),r.p||x(r,n)),r.o&&_("Patches").g(i[L],n,r.o,r.v)):n=g(r,i,[]),O(r),r.o&&r.s(r.o,r.v),n!==G?n:void 0}function g(n,t,r){if(y(t))return t;var e=t[L];if(!e)return i(t,(function(i,u){return A(n,e,t,i,u,r)}),!0),t;if(e.A!==n)return t;if(!e.P)return x(n,e.u,!0),e.u;if(!e.R){e.R=!0,e.A.m--;var u=4===e.t||5===e.t?e.i=p(e.k):e.i;i(3===e.t?new Set(u):u,(function(t,i){return A(n,e,u,t,i,r)})),x(n,u,!1),r&&n.o&&_("Patches").F(e,r,n.o,n.v)}return e.i}function A(n,t,i,u,f,c){if(r(f)){var v=g(n,f,c&&t&&3!==t.t&&!o(t.D,u)?c.concat(u):void 0);if(a(i,u,v),!r(v))return;n._=!1}if(e(f)&&!y(f)){if(!n.h.K&&n.m<1)return;g(n,f),t&&t.A.p||x(n,f)}}function x(n,t,r){void 0===r&&(r=!1),n.h.K&&n._&&h(t,r)}function z(n,t){var r=n[L];return(r?l(r):n)[t]}function E(n,t){if(t in n)for(var r=Object.getPrototypeOf(n);r;){var e=Object.getOwnPropertyDescriptor(r,t);if(e)return e;r=Object.getPrototypeOf(r)}}function R(n){n.P||(n.P=!0,n.p&&R(n.p))}function k(n){n.i||(n.i=p(n.u))}function F(n,t,r){var e=v(t)?_("MapSet").$(t,r):s(t)?_("MapSet").C(t,r):n.S?function(n,t){var r=Array.isArray(n),e={t:r?1:0,A:t?t.A:m(),P:!1,R:!1,D:{},p:t,u:n,k:null,i:null,j:null,I:!1},i=e,u=Y;r&&(i=[e],u=Z);var o=Proxy.revocable(i,u),f=o.revoke,a=o.proxy;return e.k=a,e.j=f,a}(t,r):_("ES5").J(t,r);return(r?r.A:m()).l.push(e),e}function D(n){return r(n)||t(22,n),function n(t){if(!e(t))return t;var r,o=t[L],c=u(t);if(o){if(!o.P&&(o.t<4||!_("ES5").N(o)))return o.u;o.R=!0,r=K(t,c),o.R=!1}else r=K(t,c);return i(r,(function(t,e){o&&f(o.u,t)===e||a(r,t,n(e))})),3===c?new Set(r):r}(n)}function K(n,t){switch(t){case 2:return new Map(n);case 3:return Array.from(n)}return p(n)}function $(){function n(n,t){var r=f[n];return r?r.enumerable=t:f[n]=r={configurable:!0,enumerable:t,get:function(){return Y.get(this[L],n)},set:function(t){Y.set(this[L],n,t)}},r}function t(n){for(var t=n.length-1;t>=0;t--){var r=n[t][L];if(!r.P)switch(r.t){case 5:u(r)&&R(r);break;case 4:e(r)&&R(r)}}}function e(n){for(var t=n.u,r=n.k,e=T(r),i=e.length-1;i>=0;i--){var u=e[i];if(u!==L){var f=t[u];if(void 0===f&&!o(t,u))return!0;var a=r[u],v=a&&a[L];if(v?v.u!==f:!c(a,f))return!0}}var s=!!t[L];return e.length!==T(t).length+(s?0:1)}function u(n){var t=n.k;if(t.length!==n.u.length)return!0;var r=Object.getOwnPropertyDescriptor(t,t.length-1);return!(!r||r.get)}var f={};b("ES5",{J:function(t,r){var e=Array.isArray(t),i=function(t,r){if(t){for(var e=Array(r.length),i=0;i1?e-1:0),f=1;f1?r-1:0),u=1;u=0;e--){var i=t[e];if(0===i.path.length&&"replace"===i.op){n=i.value;break}}var u=_("Patches").W;return r(n)?u(n,t):this.produce(n,(function(n){return u(n,t.slice(e+1))}))},n}(),tn=new nn,rn=tn.produce,en=tn.produceWithPatches.bind(tn),un=tn.setAutoFreeze.bind(tn),on=tn.setUseProxies.bind(tn),fn=tn.applyPatches.bind(tn),an=tn.createDraft.bind(tn),cn=tn.finishDraft.bind(tn);n.Immer=nn,n.applyPatches=fn,n.castDraft=function(n){return n},n.castImmutable=function(n){return n},n.createDraft=an,n.current=D,n.default=rn,n.enableAllPlugins=function(){$(),I(),C()},n.enableES5=$,n.enableMapSet=I,n.enablePatches=C,n.finishDraft=cn,n.freeze=h,n.immerable=H,n.isDraft=r,n.isDraftable=e,n.nothing=G,n.original=function(n){return r(n)||t(23,n),n[L].u},n.produce=rn,n.produceWithPatches=en,n.setAutoFreeze=un,n.setUseProxies=on,Object.defineProperty(n,"__esModule",{value:!0})})); +//# sourceMappingURL=immer.umd.production.min.js.map diff --git a/index.html b/index.html index 90a6b7e..20a687b 100644 --- a/index.html +++ b/index.html @@ -8,9 +8,9 @@ - + diff --git a/index.js b/index.js index 9b40569..5b3018b 100644 --- a/index.js +++ b/index.js @@ -1,80 +1,40 @@ import App from './App.js'; import nanoid from './nanoid.min.js'; +const produce = immer.produce; +immer.setAutoFreeze(false); // needed for high-frequency updated values, like onkeyup->note.temp_text; only once 'save' is called will it produce a new immutable state tree var root = document.body; -function bin_reducer(old_state, new_state, action){ - if(action.type === 'new-bin'){ - new_state.bin = action.bin; - } - else{ - new_state.bin = old_state.bin; - } - } - -function search_reducer(old_state, new_state, action){ - if(action.type === 'update-search-term'){ - new_state.search_term = action.search_term; - } - else if(action.type === 'update-search-results'){ - new_state.notes = action.notes.map(note=>({is_editing: false, temp_text: '', bin_id: old_state.bin.id, note})); - new_state.search_term = old_state.search_term; - } - else{ - new_state.search_term = old_state.search_term; - } - } - -function notes_reducer(old_state, new_state, action){ - if(action.type === 'add-note'){ - new_state.notes = ([{is_editing: true, temp_text: '', bin_id: old_state.bin.id, note: {id: action.id, text: '', modified: Date.now()}}]).concat(old_state.notes) - } - else if(action.type === 'notes-loaded'){ - new_state.notes = action.notes.map(note=>({is_editing: false, temp_text: note.text, bin_id: action.bin_id, note})); - } - else if(action.type === 'update-note-text'){ - const i = old_state.notes.findIndex(note_state => note_state.note.id === action.note_id); - new_state.notes = old_state.notes.slice(); - new_state.notes[i] = {...new_state.notes[i], note: {...new_state.notes[i].note, text: action.text, modified: Date.now()}}; - } - else if(action.type === 'update-note-editing'){ - const i = old_state.notes.findIndex(note_state => note_state.note.id === action.note_id); - new_state.notes = old_state.notes.slice(); - new_state.notes[i] = {...new_state.notes[i], is_editing: action.is_editing, temp_text: new_state.notes[i].note.text}; - } - else{ - new_state.notes = old_state.notes; - } - } - -function sorting_reducer(old_state, new_state, action){ - if(action.type === 'update-sorting'){ - new_state.sorting = action.sorting; - } - else{ - new_state.sorting = old_state.sorting; - } - } - -function reducer(old_state, action){ - const new_state = {}; - bin_reducer(old_state, new_state, action); - notes_reducer(old_state, new_state, action); - search_reducer(old_state, new_state, action); - sorting_reducer(old_state, new_state, action); - return new_state; - } +/* Ruthlessly taken from [https://gist.github.com/kitze/fb65f527803a93fb2803ce79a792fff8]: */ +const handleActions = (actionsMap, defaultState) => + (state=defaultState, {type, payload}) => + produce(state, draft => { + const action = actionsMap[type]; + action && action(draft, payload); + }); + +const reducer = handleActions({ + 'new-bin': (s, bin) => s.bin=bin, + 'update-search-term': (s, search_term) => { s.search_term=search_term; }, + 'update-search-results': (s, _notes) => { s.notes =_notes.map(n=>({is_editing: false, temp_text: '', bin_id: s.bin.id, note:n})); }, + 'add-note': (s, {id, date}) => { s.notes.unshift({is_editing: true, temp_text: '', bin_id: s.bin.id, note: {id: id, text: '', modified: date}}); }, + 'notes-loaded': (s, _notes) => { s.notes = _notes.map(n=>({is_editing: false, temp_text: n.text, bin_id: s.bin_id, note:n})); }, + 'update-note-text': (s, {id, text}) => { const note_s=s.notes.find(n=>n.note.id===id); note_s.note.text=text; }, // updates underlying note text (i.e. the "model", not the app note_state) "in the background" (e.g. from a server-pushed update), regardless of whether it's being edited; "save" is a separate action, below + 'update-note-editing': (s, {id, is_editing}) => { const note_s=s.notes.find(n=>n.note.id===id); note_s.is_editing=is_editing; note_s.temp_text=note_s.note.text; }, + 'save-note-edit': (s, {id, text}) => { const note_s=s.notes.find(n=>n.note.id===id); note_s.note.text=text; note_s.temp_text=text; note_s.is_editing=false; }, + 'update-sorting': (s, sorting) => { s.sorting=sorting; } + }, { + bin: {id: nanoid()}, + notes: [ + //{is_editing: false, temp_text: '', bin_id: '', note: {id: nanoid(), text: 'Note one', modified: 1}}, + ], + search_term: '', + sorting: 'new->old' + //search_result_notes: [] + }); // create Redux store, with Redux DevTools enabled: -const store = Redux.createStore(reducer, /* preloadedState, */ { - bin: {id: nanoid()}, - notes: [ - //{is_editing: false, temp_text: '', bin_id: '', note: {id: nanoid(), text: 'Note one', modified: 1}}, - ], - search_term: '', - sorting: 'new->old' - //search_result_notes: [] - }, +const store = Redux.createStore(reducer, /* preloadedState, */ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()); const dispatch = function(...args){ store.dispatch(...args); }; From 82fc9b3dc37366c62a12f38e9a89e1eae56277cc Mon Sep 17 00:00:00 2001 From: brian Date: Wed, 17 Mar 2021 09:44:23 -0400 Subject: [PATCH 4/4] streamlined dispatch api --- handlers/App.js | 14 +++++++------- handlers/Note.js | 6 +++--- index.js | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/handlers/App.js b/handlers/App.js index ad24fb6..22d8b66 100644 --- a/handlers/App.js +++ b/handlers/App.js @@ -5,35 +5,35 @@ import api from '../api-stub.js'; const load_notes = function(state, dispatch){ api.post('/load-notes', {bin_id: state.bin.id}) .then(res=>{ - dispatch({type: 'notes-loaded', payload: res.notes}); + dispatch('notes-loaded', res.notes); }); }; const runSearch = function(state, dispatch){ api.post('/search', {search_term: state.search_term, sorting: state.sorting, bin_id: state.bin.id}) .then(res=>{ - dispatch({type:'update-search-results', payload: res.notes}); + dispatch('update-search-results', res.notes); }); }; const new_note_handler = function(state, dispatch){ - dispatch({type:'add-note', payload:{id: nanoid(), date: Date.now()}}); + dispatch('add-note', {id: nanoid(), date: Date.now()}); }; const search_term_change_handler = function(state, dispatch, e){ if(e.code === 'Enter'){ runSearch(state, dispatch); } else{ - dispatch({type:'update-search-term', payload: e.target.value}); + dispatch('update-search-term', e.target.value); } }; const sorting_change_handler = function(state, dispatch, e){ runSearch(state, dispatch); - dispatch({type:'update-sorting', payload: e.target.value}); + dispatch('update-sorting', e.target.value); }; const new_bin_handler = function(state, dispatch){ const id = nanoid(); // TODO: consolidate: this will cause two redraws: - dispatch({type: 'new-bin', payload:{id}}); - dispatch({type: 'notes-loaded', payload: []}); + dispatch('new-bin', {id}); + dispatch('notes-loaded', []); }; export {new_note_handler, search_term_change_handler, sorting_change_handler, load_notes, new_bin_handler}; \ No newline at end of file diff --git a/handlers/Note.js b/handlers/Note.js index 4759926..e69de5d 100644 --- a/handlers/Note.js +++ b/handlers/Note.js @@ -2,16 +2,16 @@ import api from '../api-stub.js'; const edit_handler = function(note_state, dispatch){ - dispatch({type: 'update-note-editing', payload:{id: note_state.note.id, is_editing: true}}); + dispatch('update-note-editing', {id: note_state.note.id, is_editing: true}); }; const cancel_handler = function(note_state, dispatch){ - dispatch({type: 'update-note-editing', payload:{id: note_state.note.id, is_editing: false}}); + dispatch('update-note-editing', {id: note_state.note.id, is_editing: false}); }; const text_change_handler = function(note_state, dispatch, e){ note_state.temp_text = e.target.value; }; const save_handler = function(note_state, dispatch){ - dispatch({type: 'save-note-edit', payload:{id: note_state.note.id, text: note_state.temp_text}}); + dispatch('save-note-edit', {id: note_state.note.id, text: note_state.temp_text}); api.post('/save', {bin_id: note_state.bin_id, note_id: note_state.note.id, text: note_state.temp_text}) }; diff --git a/index.js b/index.js index 5b3018b..79f8250 100644 --- a/index.js +++ b/index.js @@ -14,7 +14,7 @@ const handleActions = (actionsMap, defaultState) => }); const reducer = handleActions({ - 'new-bin': (s, bin) => s.bin=bin, + 'new-bin': (s, bin) => { s.bin=bin; }, 'update-search-term': (s, search_term) => { s.search_term=search_term; }, 'update-search-results': (s, _notes) => { s.notes =_notes.map(n=>({is_editing: false, temp_text: '', bin_id: s.bin.id, note:n})); }, 'add-note': (s, {id, date}) => { s.notes.unshift({is_editing: true, temp_text: '', bin_id: s.bin.id, note: {id: id, text: '', modified: date}}); }, @@ -36,7 +36,7 @@ const reducer = handleActions({ // create Redux store, with Redux DevTools enabled: const store = Redux.createStore(reducer, /* preloadedState, */ window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()); -const dispatch = function(...args){ store.dispatch(...args); }; +const dispatch = function(action_type, payload){ store.dispatch({type:action_type, payload}); }; store.subscribe(()=>{ m.render(root, m(App, {state: store.getState(), dispatch}));