From 5f068ac0365250583b3363ebda7389b50233d555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Fri, 5 Jun 2026 18:00:10 +0200 Subject: [PATCH] Work on node history --- static/css/notes2.css | 55 +++++++++++++++++++++++++ static/images/icon_back.svg | 49 ++++++++++++++++++++++ static/js/app.mjs | 3 +- static/js/node_store.mjs | 78 ++++++++++++++++++++++++++--------- static/js/page_history.mjs | 81 ++++++++++++++++++++++++++++++++++++- 5 files changed, 246 insertions(+), 20 deletions(-) create mode 100644 static/images/icon_back.svg diff --git a/static/css/notes2.css b/static/css/notes2.css index d9b6170..a41afda 100644 --- a/static/css/notes2.css +++ b/static/css/notes2.css @@ -513,3 +513,58 @@ dialog.op { } } } + + + + + +n2-pagehistory { + .layout { + display: grid; + grid-template-columns: min-content 1fr; + grid-gap: 8px; + align-items: center; + + .el-back-image, + .el-back-text { + cursor: pointer; + } + + .el-node-name { + margin-left: 8px; + } + + .el-nodes { + grid-column: 1 / -1; + + display: grid; + grid-template-columns: min-content 1fr; + grid-gap: 4px 8px; + + .history-node { + display: contents; + } + } + + .pagination { + grid-column: 1 / -1; + margin-top: 16px; + + display: grid; + grid-template-columns: repeat(3, min-content); + grid-gap: 32px; + white-space: nowrap; + user-select: none; + + .el-prev { + font-weight: bold; + cursor: pointer; + } + + .el-next { + font-weight: bold; + cursor: pointer; + } + } + } +} diff --git a/static/images/icon_back.svg b/static/images/icon_back.svg new file mode 100644 index 0000000..504976b --- /dev/null +++ b/static/images/icon_back.svg @@ -0,0 +1,49 @@ + + + + + + + + arrow-left-circle + + + diff --git a/static/js/app.mjs b/static/js/app.mjs index efdddd1..11c3007 100644 --- a/static/js/app.mjs +++ b/static/js/app.mjs @@ -44,7 +44,8 @@ export class App { document.getElementById('node-content')?.focus() }) - _mbus.dispatch('SHOW_PAGE', { page: 'node' }) + // XXX - _mbus.dispatch('SHOW_PAGE', { page: 'node' }) + _mbus.dispatch('SHOW_PAGE', { page: 'history' }) window._sync = new Sync() diff --git a/static/js/node_store.mjs b/static/js/node_store.mjs index c72445c..d3b95de 100644 --- a/static/js/node_store.mjs +++ b/static/js/node_store.mjs @@ -74,7 +74,7 @@ export class NodeStore { req.onsuccess = (event) => { this.db = event.target.result this.sendQueue = new SimpleNodeStore(this.db, 'send_queue') - this.nodesHistory = new SimpleNodeStore(this.db, 'nodes_history') + this.nodesHistory = new NodeHistoryStore(this.db, 'nodes_history') this.files = new SimpleNodeStore(this.db, 'files') this.initializeRootNode() .then(() => resolve()) @@ -437,24 +437,66 @@ class SimpleNodeStore { }//}}} } -export class StoreFile { - static createFromFileObject(f) { - const obj = new StoreFile() - obj.name = f.name - obj.size = f.size - obj.mime = f.type - return obj - } - constructor() { - this.name = '' - this.size = 0 - this.mime = '' +class NodeHistoryStore extends SimpleNodeStore { + constructor(db, storeName) {//{{{ + super(db, storeName) + }//}}} + count(uuid) {//{{{ + if (uuid === undefined) + return super.count() - this.objectURL = null // URL.createObjectURL(blob) - } - data() { - return { - } + const index = this.db + .transaction(['nodes', this.storeName], 'readonly') + .objectStore(this.storeName) + .index('byUUID') + + return new Promise((resolve, reject) => { + const request = index.count(uuid) + request.onsuccess = (event) => resolve(event.target.result) + request.onerror = (event) => reject(event.target.error) + }) + }//}}} + retrievePage(uuid, perPage, page) { + return new Promise((resolve, _reject) => { + const cursor = this.db + .transaction(['nodes', this.storeName], 'readonly') + .objectStore(this.storeName) + .index('byUUID') + .openCursor(uuid) + + let retrieved = 0 + let first = true + const nodes = [] + + cursor.onsuccess = (event) => { + const cursor = event.target.result + if (!cursor) { + resolve(nodes) + return + } + + // openCursor returns the first value which is only useful + // if the first page is requested. + if (page == 1 || !first) { + retrieved++ + nodes.push(new Node(cursor.value)) + if (retrieved === perPage) { + resolve(nodes) + return + } + cursor.continue() + return + } + + // Jump to the start of the requested page. + // Minus one since the first record was already returned. + if (page > 1 && first) { + first = false + cursor.advance((perPage * (page - 1))) + return + } + } + }) } } diff --git a/static/js/page_history.mjs b/static/js/page_history.mjs index a931faa..434f47b 100644 --- a/static/js/page_history.mjs +++ b/static/js/page_history.mjs @@ -1,15 +1,94 @@ import { CustomHTMLElement } from './lib/custom_html_element.mjs' export class N2PageHistory extends CustomHTMLElement { + static PAGESIZE = 10 + static { this.tmpl = document.createElement('template') this.tmpl.innerHTML = ` -
History
+ + +
+ +
Back to node
+ + +

+ +
+ + + +
` } constructor() { super() + + // Connect back icon and text to give the user a way back to the node. + this.elBackImage.addEventListener('click', () => _mbus.dispatch('SHOW_PAGE', { page: 'node' })) + this.elBackText.addEventListener('click', () => _mbus.dispatch('SHOW_PAGE', { page: 'node' })) + this.elPrev.addEventListener('click', ()=>this.prevPage()) + this.elNext.addEventListener('click', ()=>this.nextPage()) + + _mbus.subscribe('NODE_UI_OPEN', async (event) => { + await this.useNode(event.detail.data) + this.render() + }) + } + + async useNode(node) { + this.node = node + this.page = 1 + + this.nodesTotal = await nodeStore.nodesHistory.count(this.node.UUID) + this.pages = Math.ceil(this.nodesTotal / N2PageHistory.PAGESIZE) + } + + + prevPage() { + if (this.page == 1) + return + this.page-- + this.render() + } + + nextPage() { + if (this.page >= this.pages) + return + this.page++ + this.render() + } + + async render() { + this.elNodeName.innerText = this.node.get('Name') + this.elPage.innerText = `${this.page} / ${this.pages}` + + this.elNodes.innerHTML = '' + let nodes = await nodeStore.nodesHistory.retrievePage(this.node.UUID, N2PageHistory.PAGESIZE, this.page) + let i = 0 + for (const n of nodes) { + i++ + const div = document.createElement('div') + div.innerHTML = ` +
${N2PageHistory.PAGESIZE * (this.page - 1) + i}
+
${n.get('Updated').replace('T', ' ')}
+ ` + div.classList.add('history-node') + this.elNodes.append(div) + } } } customElements.define('n2-pagehistory', N2PageHistory)