diff --git a/static/js/app.mjs b/static/js/app.mjs index 16dab2b..45cd85b 100644 --- a/static/js/app.mjs +++ b/static/js/app.mjs @@ -6,13 +6,13 @@ import { Node } from 'node' export class App { constructor() {// {{{ this.currentNode = null - this.treeNative = new N2Tree() + this.tree = new N2Tree() this.crumbs = new N2Crumbs() this.crumbsElement = document.getElementById('crumbs') this.nodeUI = document.getElementById('note') _mbus.subscribe('TREE_TRUNK_FETCHED', async () => { - document.getElementById('tree').append(this.treeNative.render()) + document.getElementById('tree').append(this.tree.render()) document.getElementById('tree-nodes')?.focus() const startNode = await this.getStartNode() @@ -188,7 +188,7 @@ export class App { node.reset() // any modifications are discarded. this.currentNode = node - this.treeNative.setSelected(node, dontExpand) + this.tree.setSelected(node, dontExpand) const ancestors = await nodeStore.getNodeAncestry(node) _mbus.dispatch('CRUMBS_SET', ancestors, () => this.crumbsElement.replaceChildren(this.crumbs.render())) @@ -196,7 +196,7 @@ export class App { _mbus.dispatch('NODE_UNMODIFIED') // Scrolls node into view. - this.treeNative.makeVisible(node) + this.tree.makeVisible(node) }//}}} } diff --git a/static/js/node.mjs b/static/js/node.mjs index 949724c..d611c64 100644 --- a/static/js/node.mjs +++ b/static/js/node.mjs @@ -54,7 +54,7 @@ export class Node { if (a.data.Name > b.data.Name) return 0 return 0 }//}}} - static create(name, parentUUID) { + static create(name, parentUUID) {// {{{ return new Node({ UUID: uuidv7(), Created: (new Date()).toISOString(), @@ -64,7 +64,7 @@ export class Node { Markdown: false, History: false, }) - } + }// }}} constructor(nodeData, level) {//{{{ @@ -123,9 +123,6 @@ export class Node { return this._children_fetched }//}}} async fetchChildren() {//{{{ - if (this._children_fetched) - return this.Children - this.Children = await nodeStore.getTreeNodes(this.UUID, this.Level + 1) this._children_fetched = true diff --git a/static/js/node_store.mjs b/static/js/node_store.mjs index 98642d1..e849e29 100644 --- a/static/js/node_store.mjs +++ b/static/js/node_store.mjs @@ -159,6 +159,7 @@ export class NodeStore { }) }//}}} + /* upsertNodeRecords(records) {//{{{ return new Promise((resolve, reject) => { const t = this.db.transaction('nodes', 'readwrite') @@ -187,16 +188,10 @@ export class NodeStore { record.modified = 0 addReq = nodeStore.put(record) } - addReq.onsuccess = () => { - console.debug(`${op} ${record.UUID} (${record.Name})`) - } - addReq.onerror = (event) => { - console.log(`error ${op} ${record.UUID}`, event.target.error) - reject(event.target.error) - } } }) }//}}} + */ getTreeNodes(parent, newLevel) {//{{{ return new Promise((resolve, reject) => { // Parent of toplevel nodes is ROOT_NODE in indexedDB. @@ -219,7 +214,7 @@ export class NodeStore { req.onerror = (event) => reject(event.target.error) }) }//}}} - async search(searchfor, parent) {//{{{ + search(searchfor, parent) {//{{{ return new Promise((resolve, reject) => { const trx = this.db.transaction('nodes', 'readonly') const nodeStore = trx.objectStore('nodes') @@ -249,43 +244,55 @@ export class NodeStore { }) }//}}} - add(records) {//{{{ + add(records, objstore) {//{{{ return new Promise((resolve, reject) => { try { - const t = this.db.transaction('nodes', 'readwrite') - const nodeStore = t.objectStore('nodes') - t.onerror = (event) => { - console.error('transaction error', event.target.error) - reject(event.target.error) + // A nodestore can be provided in order to + // avoid creating new transactions. + let nodeStore = objstore + let t + + if (nodeStore === undefined) { + t = this.db.transaction('nodes', 'readwrite') + nodeStore = t.objectStore('nodes') + + t.oncomplete = (_event) => { + resolve() + } + + t.onerror = (event) => { + console.error('transaction error', event.target.error) + reject(event.target.error) + } } // records is an object, not an array. - const promises = [] for (const recordIdx in records) { const record = records[recordIdx] - const addReq = nodeStore.put(record.data) - - const promise = new Promise((resolve, reject) => { - addReq.onsuccess = () => resolve() - addReq.onerror = (event) => { - console.error('Error!', event.target.error, record.ID) - reject(event.target.error) - } - }) - promises.push(promise) + nodeStore.put(record.data) } - Promise.all(promises).then(() => resolve()) + resolve() } catch (e) { - console.log(e) + console.error(e) + reject(e) } }) }//}}} - get(uuid) {//{{{ + get(uuid, suppliedNodestore) {//{{{ return new Promise((resolve, reject) => { - const trx = this.db.transaction('nodes', 'readonly') - const nodeStore = trx.objectStore('nodes') + // A nodestore can be provided in order to + // avoid creating new transactions. + let trx + let nodeStore = suppliedNodestore + + if (nodeStore === undefined) { + trx = this.db.transaction('nodes', 'readonly') + nodeStore = trx.objectStore('nodes') + } + const getRequest = nodeStore.get(uuid) + getRequest.onsuccess = (event) => { // Node not found in IndexedDB. if (event.target.result === undefined) { @@ -328,6 +335,9 @@ export class NodeStore { }) }//}}} + newTransaction(objectStore, mode) {// {{{ + return this.db.transaction(objectStore, mode) + }// }}} nodeCount() {//{{{ return new Promise((resolve, reject) => { diff --git a/static/js/sync.mjs b/static/js/sync.mjs index fd606f3..9b58cf7 100644 --- a/static/js/sync.mjs +++ b/static/js/sync.mjs @@ -65,9 +65,16 @@ export class Sync { * sync be preserved in the backend. */ let backendNode = null + + // Create a single transaction to be used in the chain of + // this sync. Otherwise it would take more time to create + // transactions for each node. + const trx = nodeStore.newTransaction('nodes', 'readwrite') + const objstore = trx.objectStore('nodes') + for (const i in res.Nodes) { backendNode = new Node(res.Nodes[i], -1) - await window._sync.handleNode(backendNode) + await this.handleNode(backendNode, objstore) handled++ if (handled % 100 === 0) @@ -88,16 +95,16 @@ export class Sync { } return (syncEnd - syncStart) }//}}} - async handleNode(backendNode) {//{{{ + async handleNode(backendNode, objstore) {//{{{ try { /* Retrieving the local copy of this node from IndexedDB. * The backend node can be discarded if it is older than * the local copy since it is considered history preserved * in the backend. */ - return nodeStore.get(backendNode.UUID) - .then(async localNode => { + return nodeStore.get(backendNode.UUID, objstore) + .then(localNode => { if (localNode.updated() >= backendNode.updated()) { - console.log(`History from backend: ${backendNode.UUID}`) + console.debug(`History from backend: ${backendNode.UUID}`) return } @@ -107,12 +114,12 @@ export class Sync { * * If the local node has seen change, the change is already * placed into the send_queue anyway. */ - return nodeStore.add([backendNode]) + return nodeStore.add([backendNode], objstore) }) - .catch(async () => { + .catch(() => { // Not found in IndexedDB - OK to just insert since it only exists in backend. - return nodeStore.add([backendNode]) + return nodeStore.add([backendNode], objstore) }) } catch (e) { console.error(e) @@ -198,10 +205,7 @@ export class N2SyncProgress extends CustomHTMLElement { break // Reload the tree nodes to reflect the new/updated nodes. - if (window._notes2?.current?.reloadTree.value !== null) { - nodeStore.purgeCache() - window._notes2.current.reloadTree.value = window._notes2.current.reloadTree.value + 1 - } + window._app.tree.reset() break } this.render() diff --git a/static/js/tree.mjs b/static/js/tree.mjs index d7a11ee..1da5dee 100644 --- a/static/js/tree.mjs +++ b/static/js/tree.mjs @@ -10,6 +10,7 @@ export class N2Tree extends CustomHTMLElement { +
` }// }}} @@ -39,12 +40,20 @@ export class N2Tree extends CustomHTMLElement { for (const node of this.treeTrunk) { const treenode = new N2TreeNode(this, node) this.treeNodeComponents[node.UUID] = treenode - this.appendChild(treenode.render()) + this.elTreenodes.appendChild(treenode.render()) } this.rendered = true return this }// }}} + reset() { + console.log('tree reset') + this.treeNodeComponents = {} + this.treeTrunk = [] + this.rendered = false + this.elTreenodes.replaceChildren() + this.populateFirstLevel() + } populateFirstLevel() {//{{{ nodeStore.get(ROOT_NODE) .then(node => node.fetchChildren())