From a248600c21111bc4198de7d7f23b22625b8805a7 Mon Sep 17 00:00:00 2001 From: brian Date: Wed, 17 Mar 2021 09:21:23 -0400 Subject: [PATCH] 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); };