local DB row store on frontend
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import Note from './Note.js';
|
import Note from './Note.js';
|
||||||
import {
|
import {
|
||||||
new_note_handler,
|
new_note_handler,
|
||||||
|
new_note_by_dblclick_handler,
|
||||||
search_term_change_handler,
|
search_term_change_handler,
|
||||||
sorting_change_handler,
|
sorting_change_handler,
|
||||||
load_notes,
|
load_notes,
|
||||||
@@ -38,7 +39,7 @@ function App(vnode_init){
|
|||||||
(s.login.showing
|
(s.login.showing
|
||||||
? (s.login.is_logged_in
|
? (s.login.is_logged_in
|
||||||
? m('.login-info', {key:'login-info'}, [
|
? m('.login-info', {key:'login-info'}, [
|
||||||
m('.text', 'Logged in as '+s.login.user.username),
|
m('.text', 'Logged in as '+s.db[s.login.user_id].model.username),
|
||||||
m('button', {onclick: o(logout_request_handler)}, 'Logout')
|
m('button', {onclick: o(logout_request_handler)}, 'Logout')
|
||||||
])
|
])
|
||||||
: m('.login-form', {key:'login-form'}, [
|
: m('.login-form', {key:'login-form'}, [
|
||||||
@@ -52,7 +53,7 @@ function App(vnode_init){
|
|||||||
m('.buttons', {key:'buttons'}, [
|
m('.buttons', {key:'buttons'}, [
|
||||||
(s.is_editing_bin_name
|
(s.is_editing_bin_name
|
||||||
? m('input.bin-name-textbox', {key: 'bin-name-textbox', value: s.temp_bin_name, onchange: o(bin_name_change_handler)})
|
? m('input.bin-name-textbox', {key: 'bin-name-textbox', value: s.temp_bin_name, onchange: o(bin_name_change_handler)})
|
||||||
: m('.bin-name', {key: 'bin-name'}, s.bin.name)
|
: m('.bin-name', {key: 'bin-name'}, s.db[s.bin_id].model.name)
|
||||||
),
|
),
|
||||||
m('button', {key: 'new-bin-button', onclick: o(new_bin_handler)}, 'New Bin...'),
|
m('button', {key: 'new-bin-button', onclick: o(new_bin_handler)}, 'New Bin...'),
|
||||||
(s.is_editing_bin_name
|
(s.is_editing_bin_name
|
||||||
@@ -60,14 +61,14 @@ function App(vnode_init){
|
|||||||
: m('button', {key:'rename-bin-button', onclick: o(bin_name_editing_toggle_button_handler)}, 'Rename')
|
: m('button', {key:'rename-bin-button', onclick: o(bin_name_editing_toggle_button_handler)}, 'Rename')
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
m('ul.bin-list', {key:'bin-list'}, s.login.bins.map(b=>
|
m('ul.bin-list', {key:'bin-list'}, s.login.bin_ids.map(bin_id=>
|
||||||
m('li.bin', {key:b.id, onclick:o1(choose_bin_handler, b.id)}, b.name)
|
m('li.bin', {key:bin_id, onclick:o1(choose_bin_handler, bin_id)}, s.db[bin_id].model.name)
|
||||||
))
|
))
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
m('.main', {key: 'main'}, [
|
m('.main', {key: 'main'}, [
|
||||||
m('.notes', s.notes.map(note_state =>
|
m('.notes', {ondblclick: o(new_note_by_dblclick_handler)}, s.notes.map(note_state =>
|
||||||
m(Note, {state:s, note_state, dispatch, key: note_state.note.id})
|
m(Note, {state:s, note_state, dispatch, key: note_state.note_id})
|
||||||
))
|
))
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ function Note(vnode_init){
|
|||||||
const {dispatch} = vnode_init.attrs;
|
const {dispatch} = vnode_init.attrs;
|
||||||
return {
|
return {
|
||||||
view: function(vnode){
|
view: function(vnode){
|
||||||
const {note_state} = vnode.attrs;
|
const {state, note_state} = vnode.attrs;
|
||||||
const {is_editing, is_focused, note} = note_state;
|
const {is_editing, is_focused, note_id} = note_state;
|
||||||
|
const note = state.db[note_id].model;
|
||||||
if(is_editing){
|
if(is_editing){
|
||||||
return m('.note', [
|
return m('.note', [
|
||||||
m('textarea', {key: 'textarea', onchange: text_change_handler.bind(null, note_state, dispatch), oncreate: is_focused?({dom})=>{ dom.focus(); delete note_state.is_focused; }:null }, note_state.temp_text),
|
m('textarea', {key: 'textarea', onchange: text_change_handler.bind(null, note_state, dispatch), oncreate: is_focused?({dom})=>{ dom.focus(); delete note_state.is_focused; }:null }, note_state.temp_text),
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import nanoid from '../nanoid.min.js';
|
|||||||
import api from '../api.js';
|
import api from '../api.js';
|
||||||
|
|
||||||
const load_notes = function(state, dispatch){
|
const load_notes = function(state, dispatch){
|
||||||
api.post('/load-notes', {bin_id: state.bin.id})
|
api.post('/load-notes', {bin_id: state.bin_id})
|
||||||
.then(res=>{
|
.then(res=>{
|
||||||
dispatch('notes-loaded', res.notes);
|
dispatch('notes-loaded', res.notes);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const runSearch = function(state, dispatch){
|
const runSearch = function(state, dispatch){
|
||||||
api.post('/search', {search_term: state.search_term, sorting: state.sorting, bin_id: state.bin.id})
|
api.post('/search', {search_term: state.search_term, sorting: state.sorting, bin_id: state.bin_id})
|
||||||
.then(res=>{
|
.then(res=>{
|
||||||
dispatch('update-search-results', res.notes);
|
dispatch('update-search-results', res.notes);
|
||||||
});
|
});
|
||||||
@@ -16,6 +16,10 @@ const runSearch = function(state, dispatch){
|
|||||||
const new_note_handler = function(state, dispatch){
|
const new_note_handler = function(state, dispatch){
|
||||||
dispatch('add-note', {id: nanoid(), date: Date.now()});
|
dispatch('add-note', {id: nanoid(), date: Date.now()});
|
||||||
};
|
};
|
||||||
|
const new_note_by_dblclick_handler = function(state, dispatch, e){
|
||||||
|
e.preventDefault(); // for nothing gets highlighted
|
||||||
|
dispatch('add-note', {id: nanoid(), date: Date.now()});
|
||||||
|
};
|
||||||
const search_term_change_handler = function(state, dispatch, e){
|
const search_term_change_handler = function(state, dispatch, e){
|
||||||
if(e.code === 'Enter'){
|
if(e.code === 'Enter'){
|
||||||
runSearch(state, dispatch);
|
runSearch(state, dispatch);
|
||||||
@@ -51,8 +55,8 @@ const login_request_handler = function(state, dispatch, e){
|
|||||||
else{
|
else{
|
||||||
dispatch('login-failed');
|
dispatch('login-failed');
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
.catch(e=>{ dispatch('login-failed'); });
|
e=>{ dispatch('login-failed'); });
|
||||||
};
|
};
|
||||||
const logout_request_handler = function(state, dispatch, e){
|
const logout_request_handler = function(state, dispatch, e){
|
||||||
api.post('/logout', {});
|
api.post('/logout', {});
|
||||||
@@ -68,7 +72,7 @@ const bin_name_change_handler = function(state, dispatch, e){
|
|||||||
dispatch('update-bin-name', e.target.value);
|
dispatch('update-bin-name', e.target.value);
|
||||||
};
|
};
|
||||||
const bin_name_commit_handler = function(state, dispatch){
|
const bin_name_commit_handler = function(state, dispatch){
|
||||||
api.post('/bin-rename', {bin_id:state.bin.id, name:state.temp_bin_name});
|
api.post('/bin-rename', {bin_id:state.bin_id, name:state.temp_bin_name});
|
||||||
dispatch('commit-bin-name');
|
dispatch('commit-bin-name');
|
||||||
};
|
};
|
||||||
const choose_bin_handler = function(state, dispatch, bin_id){
|
const choose_bin_handler = function(state, dispatch, bin_id){
|
||||||
@@ -85,6 +89,7 @@ const choose_bin_handler = function(state, dispatch, bin_id){
|
|||||||
};
|
};
|
||||||
|
|
||||||
export {new_note_handler,
|
export {new_note_handler,
|
||||||
|
new_note_by_dblclick_handler,
|
||||||
search_term_change_handler,
|
search_term_change_handler,
|
||||||
sorting_change_handler,
|
sorting_change_handler,
|
||||||
load_notes,
|
load_notes,
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import api from '../api.js';
|
import api from '../api.js';
|
||||||
|
|
||||||
const edit_handler = function(note_state, dispatch){
|
const edit_handler = function(note_state, dispatch){
|
||||||
dispatch('update-note-editing', {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){
|
const cancel_handler = function(note_state, dispatch){
|
||||||
dispatch('update-note-editing', {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){
|
const text_change_handler = function(note_state, dispatch, e){
|
||||||
note_state.temp_text = e.target.value;
|
note_state.temp_text = e.target.value;
|
||||||
};
|
};
|
||||||
const save_handler = function(note_state, dispatch){
|
const save_handler = function(note_state, dispatch){
|
||||||
dispatch('save-note-edit', {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})
|
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 };
|
export { edit_handler, cancel_handler, text_change_handler, save_handler };
|
||||||
@@ -15,7 +15,7 @@ const hash_change_handler = function(state, dispatch, e){
|
|||||||
// get bin id from URL
|
// get bin id from URL
|
||||||
let bin_id = window.location.hash.substring(1); // extract the leading '#'
|
let bin_id = window.location.hash.substring(1); // extract the leading '#'
|
||||||
if(bin_id === ''){
|
if(bin_id === ''){
|
||||||
const old_bin_id = state.bin.id;
|
const old_bin_id = state.bin_id;
|
||||||
window.history.replaceState(null,'', '#'+old_bin_id);
|
window.history.replaceState(null,'', '#'+old_bin_id);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|||||||
+80
-19
@@ -24,45 +24,100 @@ if(bin_id === ''){
|
|||||||
}
|
}
|
||||||
// the actual loading from server is done later, after store and dispatch are defined
|
// the actual loading from server is done later, after store and dispatch are defined
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Integrates a database row from the server into the local store: */
|
||||||
|
const integrate = function(s, model_object){
|
||||||
|
const id = model_object.id;
|
||||||
|
s.db[id] = s.db[id] || {ref_count: 0, model: null};
|
||||||
|
s.db[id].model = model_object;
|
||||||
|
};
|
||||||
|
const i = integrate;
|
||||||
|
|
||||||
|
/* integrates a list of model objects, as if 'integrate' were called on each element. */
|
||||||
|
const integrateAll = function(s, model_objects){
|
||||||
|
model_objects.forEach(mo=>{
|
||||||
|
integrate(s, mo);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Register a reference-by-id: */
|
||||||
|
const ref = function(s, o, key, new_model_id){
|
||||||
|
const old_model_id = o[key];
|
||||||
|
s.db[new_model_id].ref_count++;
|
||||||
|
if(old_model_id && s.db[old_model_id]){
|
||||||
|
s.db[old_model_id].ref_count--;
|
||||||
|
if(s.db[old_model_id].ref_count === 0){
|
||||||
|
delete s.db[old_model_id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// this needs to come after the above 'if' statement:
|
||||||
|
o[key] = new_model_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Register references in-bulk: */
|
||||||
|
/* o[key] refers to an array-of-ids */
|
||||||
|
const refAll = function(s, o, key, new_model_ids){
|
||||||
|
const old_model_ids = o[key];
|
||||||
|
// ref the new refs:
|
||||||
|
o[key] = new_model_ids;
|
||||||
|
new_model_ids.forEach(new_model_id=>{
|
||||||
|
s.db[new_model_id].ref_count++;
|
||||||
|
});
|
||||||
|
// de-ref the existing refs, deleting no-longer-referred-to models:
|
||||||
|
old_model_ids.forEach(old_model_id=>{
|
||||||
|
s.db[old_model_id].ref_count--;
|
||||||
|
// if this is the last reference, delete it from the store:
|
||||||
|
if(s.db[old_model_id].ref_count === 0){
|
||||||
|
delete s.db[old_model_id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/* TODO: routine to de-ref and ref in bulk when the id is in an object which is in an array (as opposed to refAll, where the id itself is in the array)
|
||||||
|
This routine would need to be called for state.notes, which contains refs of the form: `state.notes[n].note_id`
|
||||||
|
*/
|
||||||
|
|
||||||
function addToBinListIfLoggedIn(state){
|
function addToBinListIfLoggedIn(state){
|
||||||
const s = state;
|
const s = state;
|
||||||
// if user is logged in:
|
// if user is logged in:
|
||||||
if(s.login.is_logged_in){
|
if(s.login.is_logged_in){
|
||||||
// if bin is not already in the list:
|
// if bin is not already in the list:
|
||||||
if(s.login.bins.filter(b=>b.id===s.bin.id).length===0){
|
if(s.login.bin_ids.filter(bin_id=>bin_id===s.bin_id).length===0){
|
||||||
s.login.bins.push({id: s.bin.id, name: s.bin.name});
|
i(s, {id: s.bin_id, name: s.db[s.bin_id].model.name});
|
||||||
|
s.login.bin_ids.push(s.bin_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const reducer = handleActions({
|
const reducer = handleActions({
|
||||||
'new-bin': (s, bin) => { s.bin=bin; s.notes=[]; s.temp_bin_name=bin.id; },
|
'new-bin': (s, bin) => { i(s,bin); ref(s,s,'bin_id',bin.id); s.notes=[]; s.temp_bin_name=bin.id; },
|
||||||
'bin-requested': (s, bin_id) => { s.bin={id:bin_id}; s.notes = []; },
|
'bin-requested': (s, bin_id) => { i(s,{id:bin_id}); ref(s,s,'bin_id',bin_id); s.notes = []; },
|
||||||
'bin-loaded': (s, bin) => { s.bin=bin; s.temp_bin_name=bin.name; },
|
'bin-loaded': (s, bin) => { i(s,bin); ref(s,s,'bin_id',bin.id); s.temp_bin_name=bin.name; },
|
||||||
'update-search-term': (s, search_term) => { s.search_term=search_term; },
|
'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})); },
|
'update-search-results': (s, _notes) => { integrateAll(s,s.notes,'note_id',_notes); s.notes =_notes.map(n=>({is_editing: false, temp_text: '', bin_id: s.bin_id, note_id:n.id})); },
|
||||||
'add-note': (s, {id, date}) => { s.notes.unshift({is_editing: true, temp_text: '', bin_id: s.bin.id, is_focused:true, note: {id: id, text: '', modified: date}}); },
|
'add-note': (s, {id, date}) => { i(s, {id: id, text: '', modified: date}); s.notes.unshift({is_editing: true, temp_text: '', bin_id: s.bin_id, is_focused:true, note_id: id}); },
|
||||||
'notes-loaded': (s, _notes) => { s.notes = _notes.map(n=>({is_editing: false, temp_text: n.text, bin_id: s.bin.id, note:n})); },
|
'notes-loaded': (s, _notes) => { integrateAll(s,_notes); s.notes = _notes.map(n=>({is_editing: false, temp_text: n.text, bin_id: s.bin_id, note_id:n.id})); },
|
||||||
'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-text': (s, {id, text}) => { const note_s=s.notes.find(n=>n.note_id===id); s.db[note_s.note_id].model.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; },
|
'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=s.db[note_s.note_id].model.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; },
|
'save-note-edit': (s, {id, text}) => { const note_s=s.notes.find(n=>n.note_id===id); s.db[note_s.note_id].model.text=text; note_s.temp_text=text; note_s.is_editing=false; },
|
||||||
'update-sorting': (s, sorting) => { s.sorting=sorting; },
|
'update-sorting': (s, sorting) => { s.sorting=sorting; },
|
||||||
'update-username': (s, username) => { s.login.username=username; },
|
'update-username': (s, username) => { s.login.username=username; },
|
||||||
'update-password': (s, password) => { s.login.password=password; },
|
'update-password': (s, password) => { s.login.password=password; },
|
||||||
'login-requested': (s) => { s.login.showing=false; },
|
'login-requested': (s) => { s.login.showing=false; },
|
||||||
'login-succeeded': (s, {user, session_id}) => { s.login.is_logged_in=true; s.login.showing=true; s.login.password=''; s.login.user=user; s.login.session_id=session_id; },
|
'login-succeeded': (s, {user, session_id}) => { i(s, user); s.login.is_logged_in=true; s.login.showing=true; s.login.password=''; ref(s, s.login, 'user_id', user.id); s.login.session_id=session_id; },
|
||||||
'login-failed': (s) => { s.login.showing=true; s.login.password=''; },
|
'login-failed': (s) => { s.login.showing=true; s.login.password=''; },
|
||||||
'logout-requested': (s) => { s.login.is_logged_in=false; s.login.showing=true; s.login.username=''; s.login.password=''; s.login.session_id=''; s.login.bins=[]; },
|
'logout-requested': (s) => { s.login.is_logged_in=false; s.login.showing=true; s.login.username=''; s.login.password=''; s.login.session_id=''; refAll(s, s.login, 'bin_ids', []); },
|
||||||
'user-bin-list-loaded': (s, bins) => { s.login.bins=bins; },
|
'user-bin-list-loaded': (s, bins) => { s.login.bins=bins; integrateAll(s, bins); refAll(s, s.login, 'bin_ids', bins.map(b=>b.id)); },
|
||||||
'update-bin-name-editing': (s, is_editing) => { s.is_editing_bin_name=is_editing; },
|
'update-bin-name-editing': (s, is_editing) => { s.is_editing_bin_name=is_editing; },
|
||||||
'update-bin-name': (s, name) => { s.temp_bin_name=name; },
|
'update-bin-name': (s, name) => { s.temp_bin_name=name; },
|
||||||
'commit-bin-name': (s) => { s.bin.name=s.temp_bin_name; s.is_editing_bin_name=false; addToBinListIfLoggedIn(s); }
|
'commit-bin-name': (s) => { s.db[s.bin_id].model.name=s.temp_bin_name; s.is_editing_bin_name=false; addToBinListIfLoggedIn(s); }
|
||||||
}, {
|
}, {
|
||||||
bin: {id: bin_id, name: bin_id, user_id: ''},
|
bin_id: bin_id,
|
||||||
is_editing_bin_name: false,
|
is_editing_bin_name: false,
|
||||||
temp_bin_name: bin_id,
|
temp_bin_name: bin_id,
|
||||||
notes: [
|
notes: [
|
||||||
//{is_editing: false, temp_text: '', bin_id: '', note: {id: nanoid(), text: 'Note one', modified: 1}},
|
//{is_editing: false, temp_text: '', bin_id: '', note_id: ''},
|
||||||
],
|
],
|
||||||
search_term: '',
|
search_term: '',
|
||||||
sorting: 'new->old',
|
sorting: 'new->old',
|
||||||
@@ -71,12 +126,18 @@ const reducer = handleActions({
|
|||||||
username: '', // value of textbox
|
username: '', // value of textbox
|
||||||
password: '', // value of textbox
|
password: '', // value of textbox
|
||||||
is_logged_in: false,
|
is_logged_in: false,
|
||||||
user: null /*{
|
user_id: '' /*{
|
||||||
id: '',
|
id: '',
|
||||||
username: ''
|
username: ''
|
||||||
}*/,
|
}*/,
|
||||||
session_id: '',
|
session_id: '',
|
||||||
bins: []
|
bin_ids: []
|
||||||
|
},
|
||||||
|
db: {
|
||||||
|
[bin_id]: {
|
||||||
|
ref_count: 1,
|
||||||
|
model: {id: bin_id, name: bin_id, user_id: ''}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//search_result_notes: []
|
//search_result_notes: []
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user