Work on node history
This commit is contained in:
parent
71af26ca1d
commit
5f068ac036
5 changed files with 246 additions and 20 deletions
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
49
static/images/icon_back.svg
Normal file
49
static/images/icon_back.svg
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="23.999962"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 6.3499898 6.35"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.4.2 (ebf0e94, 2025-05-08)"
|
||||||
|
sodipodi:docname="icon_back.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:zoom="0.81067621"
|
||||||
|
inkscape:cx="11.718612"
|
||||||
|
inkscape:cy="12.335381"
|
||||||
|
inkscape:window-width="1916"
|
||||||
|
inkscape:window-height="1161"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-101.82501,-145.32499)">
|
||||||
|
<title
|
||||||
|
id="title1">arrow-left-circle</title>
|
||||||
|
<path
|
||||||
|
d="m 101.82501,148.49999 a 3.1749998,3.1749998 0 0 1 3.175,-3.175 3.1749998,3.1749998 0 0 1 3.17499,3.175 3.1749998,3.1749998 0 0 1 -3.17499,3.175 3.1749998,3.1749998 0 0 1 -3.175,-3.175 m 5.08,-0.3175 H 104.365 l 1.11125,-1.11125 -0.45084,-0.45085 -1.87961,1.8796 1.87961,1.8796 0.45084,-0.45085 -1.11125,-1.11125 h 2.54001 z"
|
||||||
|
id="path1"
|
||||||
|
style="stroke-width:0.3175" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -44,7 +44,8 @@ export class App {
|
||||||
document.getElementById('node-content')?.focus()
|
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()
|
window._sync = new Sync()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export class NodeStore {
|
||||||
req.onsuccess = (event) => {
|
req.onsuccess = (event) => {
|
||||||
this.db = event.target.result
|
this.db = event.target.result
|
||||||
this.sendQueue = new SimpleNodeStore(this.db, 'send_queue')
|
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.files = new SimpleNodeStore(this.db, 'files')
|
||||||
this.initializeRootNode()
|
this.initializeRootNode()
|
||||||
.then(() => resolve())
|
.then(() => resolve())
|
||||||
|
|
@ -437,24 +437,66 @@ class SimpleNodeStore {
|
||||||
}//}}}
|
}//}}}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StoreFile {
|
class NodeHistoryStore extends SimpleNodeStore {
|
||||||
static createFromFileObject(f) {
|
constructor(db, storeName) {//{{{
|
||||||
const obj = new StoreFile()
|
super(db, storeName)
|
||||||
obj.name = f.name
|
}//}}}
|
||||||
obj.size = f.size
|
count(uuid) {//{{{
|
||||||
obj.mime = f.type
|
if (uuid === undefined)
|
||||||
return obj
|
return super.count()
|
||||||
}
|
|
||||||
constructor() {
|
|
||||||
this.name = ''
|
|
||||||
this.size = 0
|
|
||||||
this.mime = ''
|
|
||||||
|
|
||||||
this.objectURL = null // URL.createObjectURL(blob)
|
const index = this.db
|
||||||
}
|
.transaction(['nodes', this.storeName], 'readonly')
|
||||||
data() {
|
.objectStore(this.storeName)
|
||||||
return {
|
.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,94 @@
|
||||||
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
|
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
|
||||||
|
|
||||||
export class N2PageHistory extends CustomHTMLElement {
|
export class N2PageHistory extends CustomHTMLElement {
|
||||||
|
static PAGESIZE = 10
|
||||||
|
|
||||||
static {
|
static {
|
||||||
this.tmpl = document.createElement('template')
|
this.tmpl = document.createElement('template')
|
||||||
this.tmpl.innerHTML = `
|
this.tmpl.innerHTML = `
|
||||||
<div>History</div>
|
<style>
|
||||||
|
n2-pagehistory {
|
||||||
|
margin-top: 32px;
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="layout">
|
||||||
|
<img data-el="back-image" src="/images/${_VERSION}/icon_back.svg" class="colorize">
|
||||||
|
<div data-el="back-text">Back to node</div>
|
||||||
|
|
||||||
|
<img src="/images/${_VERSION}/icon_history.svg" class="colorize">
|
||||||
|
<h1 data-el="node-name"></h1>
|
||||||
|
|
||||||
|
<div data-el="nodes"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="pagination">
|
||||||
|
<div data-el="prev"><</div>
|
||||||
|
<div data-el="page"></div>
|
||||||
|
<div data-el="next">></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
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 = `
|
||||||
|
<div>${N2PageHistory.PAGESIZE * (this.page - 1) + i}</div>
|
||||||
|
<div>${n.get('Updated').replace('T', ' ')}</div>
|
||||||
|
`
|
||||||
|
div.classList.add('history-node')
|
||||||
|
this.elNodes.append(div)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define('n2-pagehistory', N2PageHistory)
|
customElements.define('n2-pagehistory', N2PageHistory)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue