author | Alan Dipert
<alan@dipert.org> 2021-06-13 05:37:45 UTC |
committer | Alan Dipert
<alan@dipert.org> 2021-06-13 05:37:45 UTC |
parent | 8b2bf36ce1904f5a1efa058584043ad43c653135 |
el.mjs | +22 | -13 |
hntr2.mjs | +87 | -8 |
reactives.mjs | +0 | -65 |
scratch.js | +65 | -0 |
diff --git a/el.mjs b/el.mjs index 399def1..1cc9517 100644 --- a/el.mjs +++ b/el.mjs @@ -15,7 +15,9 @@ function el(tag, attrs, kids) { el.setAttribute(k, v); } } - for (const kid of kids) { + let offset = 0; + for (let i = 0; i < kids.length; i++) { + let kid = kids[i]; if (kid instanceof Input || kid instanceof Formula) { let node, placeholder = document.createComment("placeholder"); @@ -39,12 +41,19 @@ function el(tag, attrs, kids) { } })(kid); el.appendChild(node); + } else if (kid instanceof Promise) { + el.appendChild(placeholder); + kid.then(x => el.replaceChild(x, placeholder)); } else if (kid instanceof EntityNodes) { // TODO error if any EntityNodes siblings for now - kid.bind(el); + kid.bind(el, offset); + if ((kids.length - i) > 1) { + throw new Error(`entities() must be last child`); + } } else { el.appendChild(kid); } + offset++; } return el; } @@ -104,16 +113,16 @@ function getComparator(name) { } } -function splice(el, i, node) { - if (el.children.length === 0 || el.children.length === i) { +function splice(el, offset, i, node) { + if (el.children.length === offset || el.children.length === offset + i) { el.appendChild(node); } else { - el.insertBefore(node, el.children[i]); + el.insertBefore(node, el.children[offset + i]); } } -function binaryInsert(el, nodeToAttrs, sortAttr, node, cmpFunc) { - let left = 0, +function binaryInsert(el, offset, nodeToAttrs, sortAttr, node, cmpFunc) { + let left = offset, right = el.children.length, middle, cmp; @@ -134,7 +143,7 @@ function binaryInsert(el, nodeToAttrs, sortAttr, node, cmpFunc) { throw new Error(`Bad compare value: ${cmp}`); } } - splice(el, left, node) + splice(el, offset, left, node); } function asCell(val, defaultVal) { @@ -154,7 +163,7 @@ class EntityNodes { this.sortDir = asCell(sortDir, "asc"); this.makeNode = makeNode; } - bind(el) { + bind(el, offset = 0) { let eidToNode = new Map(), nodeToEidCell = new Map(), nodeToAttrs = new Map(), @@ -198,19 +207,19 @@ class EntityNodes { // binaryInsert(el, nodeToAttrs, this.sortAttr.value, node, getComparator(this.sortDir.value)); // })(nodeToAttrs.get(node)[this.sortAttr.value])); eidToInserter.set(eid, watch(() => { - binaryInsert(el, nodeToAttrs, this.sortAttr.value, node, getComparator(this.sortDir.value)); + binaryInsert(el, offset, nodeToAttrs, this.sortAttr.value, node, getComparator(this.sortDir.value)); })(nodeToAttrs.get(node)[this.sortAttr.value])); } })(view({find: [_.eid], where: [[_.eid]]})(this.db)); formula((sortAttr, sortDir) => { - while (el.children.length) { - el.removeChild(el.children[0]); + while (el.children.length - offset) { + el.removeChild(el.children[offset]); } for (let [eid, f] of [...eidToInserter]) { let node = eidToNode.get(eid); f?.detach(); eidToInserter.set(eid, watch(() => { - binaryInsert(el, nodeToAttrs, sortAttr, node, getComparator(sortDir)); + binaryInsert(el, offset, nodeToAttrs, sortAttr, node, getComparator(sortDir)); })(nodeToAttrs.get(node)[sortAttr])); } })(this.sortAttr, this.sortDir); diff --git a/hntr2.mjs b/hntr2.mjs index e4be3b2..0f7a30c 100644 --- a/hntr2.mjs +++ b/hntr2.mjs @@ -1,21 +1,100 @@ -import { input, formula, cond, not, database, transaction } from './reactives.mjs'; +import { input, formula, watch, cond, not, database, transaction } from './reactives.mjs'; import { $el, nbsp, entities } from './el.mjs'; let user = input(), - loaded = input(false); + loaded = input(false), + db = database([ + // [0, "site", "foo.com"] + ]); + +watch((added, removed) => { + console.log("added", [...added]); + console.log("removed", [...removed]); +})(db); firebase.auth().onAuthStateChanged(result => { if (result) user.set(result) loaded.set(true); }); +function bindTo(input, cell) { + input.addEventListener("change", ({target: {value}}) => { + cell.set(value); + }) + watch((oldv, newv) => { + if (!newv) input.value = ""; + })(cell); + return input; +} + +function record(...fields) { + return Object.fromEntries(fields.map(f => [f, input()])); +} + +function recordClear(rec) { + transaction(() => { + Object.values(rec).forEach(c => c.set()); + }); +} + +function recordTuples(rec, eid) { + return Object.entries(rec) + .filter(([f, c]) => c.value) + .map(([f, c]) => [eid, f, c.value]); +} + +function deleteCredential(eid) { + db.remove([...db].filter(([e]) => e === eid)); +} + function authed() { - return $el.p( - formula(u => `Signed in as ${u?.email}`)(user), - nbsp, - $el.a({href: "#", onclick: () => { - firebase.auth().signOut().then(() => user.set()); - }}, "Sign out") + let rec = record("site", "username", "password", "note") + return $el.div( + $el.p( + formula(u => `Signed in as ${u?.email}`)(user), + nbsp, + $el.a({href: "#", onclick: () => { + firebase.auth().signOut().then(() => user.set()); + }}, "Sign out") + ), + $el.div( + $el.table( + $el.tr( + $el.th("Action"), + $el.th("Site"), + $el.th("Username"), + $el.th("Password"), + $el.th("Note") + ), + $el.tr( + $el.td($el.input({ + type: "button", + value: "Add", + onclick: () => { + db.add(recordTuples(rec, db.maxEid + 1)); + recordClear(rec); + } + })), + $el.td(bindTo($el.input({type: "text"}), rec.site)), + $el.td(bindTo($el.input({type: "text"}), rec.username)), + $el.td(bindTo($el.input({type: "text"}), rec.password)), + $el.td(bindTo($el.input({type: "text"}), rec.note)) + ), + entities(db, "_eid", "asc", attrs => { + return $el.tr( + $el.td($el.input({ + type: "button", + value: "Delete", + onclick: () => deleteCredential(attrs._eid.value) + })), + $el.td(attrs.site), + $el.td(attrs.username), + $el.td(attrs.password), + $el.td(attrs.note) + ) + }) + ) + ) ); } diff --git a/reactives.mjs b/reactives.mjs index b5931fb..61e0ef3 100644 --- a/reactives.mjs +++ b/reactives.mjs @@ -340,68 +340,3 @@ function and(...xs) { } export { Input, Formula, transaction, captureCreated, input, formula, database, view, watch, cond, not }; - -// let A = input(100), -// B = input(200), -// C = formula(console.log)(A, B); - -// console.log("Setting A and B in succession") -// A.set(A.value + 1); -// B.set(B.value + 1); - -// setTimeout(() => transaction(() => { -// console.log("Setting A and B in a transaction") -// A.set(A.value + 1); -// B.set(B.value + 1); -// }), 1000); - -// class Table extends Vertex { -// constructor(columnNames) { -// this.columnNames = columnNames; -// this.rows = new Map(); -// this.lastAdded = new Set(); -// this.lastRemoved = new Set(); -// } -// insert(values) { -// // TODO JSON encode values and set rows to [...values] => {col1: val1...} -// // Populate .lastAdded if appropriate -// // TODO Might need to modify propagate to call .flush or similar on walked, to clear lastAdded -// // and lastRemoved after a propagation cycle -// // TODO values can be Refs -// return this; -// } -// select(columnNames, wherePredicate) { -// // TODO -// // Return a View wired up to this table to receive updates -// } -// sum(columnName) { -// // TODO -// // Return a Formula -// } -// } - -// class View extends Vertex { -// } - -// A -// / \ -// B C--F--Q -// \ / \/ -// D E -// let A = new Input(0), -// B = new Formula([A], () => { -// console.log("B updated"); -// }), -// C = new Formula([A], () => { -// console.log("C updated"); -// }), -// D = new Formula([B, C], () => { -// console.log("D updated"); -// }), -// E = new Formula([C], () => { -// console.log("E updated"); -// }), -// Q = new Input(0), -// F = new Formula([C, E, Q], () => { -// console.log("F updated"); -// }); diff --git a/scratch.js b/scratch.js new file mode 100644 index 0000000..455669b --- /dev/null +++ b/scratch.js @@ -0,0 +1,65 @@ + +// let A = input(100), +// B = input(200), +// C = formula(console.log)(A, B); + +// console.log("Setting A and B in succession") +// A.set(A.value + 1); +// B.set(B.value + 1); + +// setTimeout(() => transaction(() => { +// console.log("Setting A and B in a transaction") +// A.set(A.value + 1); +// B.set(B.value + 1); +// }), 1000); + +// class Table extends Vertex { +// constructor(columnNames) { +// this.columnNames = columnNames; +// this.rows = new Map(); +// this.lastAdded = new Set(); +// this.lastRemoved = new Set(); +// } +// insert(values) { +// // TODO JSON encode values and set rows to [...values] => {col1: val1...} +// // Populate .lastAdded if appropriate +// // TODO Might need to modify propagate to call .flush or similar on walked, to clear lastAdded +// // and lastRemoved after a propagation cycle +// // TODO values can be Refs +// return this; +// } +// select(columnNames, wherePredicate) { +// // TODO +// // Return a View wired up to this table to receive updates +// } +// sum(columnName) { +// // TODO +// // Return a Formula +// } +// } + +// class View extends Vertex { +// } + +// A +// / \ +// B C--F--Q +// \ / \/ +// D E +// let A = new Input(0), +// B = new Formula([A], () => { +// console.log("B updated"); +// }), +// C = new Formula([A], () => { +// console.log("C updated"); +// }), +// D = new Formula([B, C], () => { +// console.log("D updated"); +// }), +// E = new Formula([C], () => { +// console.log("E updated"); +// }), +// Q = new Input(0), +// F = new Formula([C, E, Q], () => { +// console.log("F updated"); +// });