Work on sync element, now a custom HTML element
This commit is contained in:
parent
99063d34be
commit
9fc4a14ce3
10 changed files with 190 additions and 1001 deletions
|
|
@ -1,8 +1,7 @@
|
|||
import { ROOT_NODE } from 'node_store'
|
||||
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
|
||||
import { N2Tree } from 'tree'
|
||||
import { NodeUINative, Node } from 'node'
|
||||
import { SyncProgress } from 'sync'
|
||||
import { Node } from 'node'
|
||||
|
||||
export class App {
|
||||
constructor() {// {{{
|
||||
|
|
@ -10,7 +9,7 @@ export class App {
|
|||
this.treeNative = new N2Tree()
|
||||
this.crumbs = new N2Crumbs()
|
||||
this.crumbsElement = document.getElementById('crumbs')
|
||||
this.nodeUI = new NodeUINative(document.getElementById('note'))
|
||||
this.nodeUI = document.getElementById('note')
|
||||
|
||||
_mbus.subscribe('TREE_TRUNK_FETCHED', async () => {
|
||||
document.getElementById('tree').append(this.treeNative.render())
|
||||
|
|
@ -36,11 +35,12 @@ export class App {
|
|||
document.getElementById('node-content')?.focus()
|
||||
})
|
||||
|
||||
const syncProgress = document.getElementById('sync-progress')
|
||||
new SyncProgress(syncProgress)
|
||||
|
||||
window._sync = new Sync()
|
||||
window._sync.run()
|
||||
|
||||
// I think it is uncomfortable having the sync running as soon as the page load.
|
||||
// I haven't gotten the time to look at the page before stuff jumps around.
|
||||
// There a slight delay to initiate sync seems reasonable.
|
||||
setTimeout(() => window._sync.run(), 1000)
|
||||
}// }}}
|
||||
|
||||
keyHandler(event) {//{{{
|
||||
|
|
@ -55,9 +55,9 @@ export class App {
|
|||
switch (event.key.toUpperCase()) {
|
||||
case 'T':
|
||||
if (document.activeElement.id === 'tree-nodes')
|
||||
document.getElementById('node-content').focus()
|
||||
this.nodeUI.takeFocus()
|
||||
else
|
||||
document.getElementById('tree-nodes').focus()
|
||||
this.nodeUI.takeFocus()
|
||||
break
|
||||
|
||||
case 'F':
|
||||
|
|
|
|||
|
|
@ -1,345 +1,20 @@
|
|||
import { h, Component, createRef } from 'preact'
|
||||
import htm from 'htm'
|
||||
import { signal } from 'preact/signals'
|
||||
import { ROOT_NODE } from 'node_store'
|
||||
import { SyncProgress } from 'sync'
|
||||
const html = htm.bind(h)
|
||||
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
|
||||
|
||||
export class NodeUI extends Component {
|
||||
constructor(props) {//{{{
|
||||
super(props)
|
||||
this.menu = signal(false)
|
||||
this.node = signal(null)
|
||||
this.nodeContent = createRef()
|
||||
this.nodeProperties = createRef()
|
||||
this.nodeModified = signal(false)
|
||||
this.keys = signal([])
|
||||
this.page = signal('node')
|
||||
this.crumbs = []
|
||||
this.syncProgress = createRef()
|
||||
window.addEventListener('popstate', evt => {
|
||||
if (evt.state?.hasOwnProperty('nodeUUID'))
|
||||
_notes2.current.goToNode(evt.state.nodeUUID, true)
|
||||
else
|
||||
_notes2.current.goToNode('00000000-0000-0000-0000-000000000000', true)
|
||||
})
|
||||
|
||||
window.addEventListener('keydown', evt => this.keyHandler(evt))
|
||||
}//}}}
|
||||
render() {//{{{
|
||||
if (this.node.value === null)
|
||||
return
|
||||
|
||||
const node = this.node.value
|
||||
document.title = node.get('Name')
|
||||
|
||||
const nodeModified = this.nodeModified.value ? 'node-modified' : ''
|
||||
|
||||
|
||||
const crumbDivs = [
|
||||
html`<div class="crumb" onclick=${() => _notes2.current.goToNode(ROOT_NODE)}>Start</div>`
|
||||
]
|
||||
for (let i = this.crumbs.length - 1; i >= 0; i--) {
|
||||
const crumbNode = this.crumbs[i]
|
||||
crumbDivs.push(html`<div class="crumb" onclick=${() => _notes2.current.goToNode(crumbNode.UUID)}>${crumbNode.get('Name')}</div>`)
|
||||
}
|
||||
if (node.UUID !== ROOT_NODE)
|
||||
crumbDivs.push(
|
||||
html`<div class="crumb" onclick=${() => _notes2.current.goToNode(node.UUID)}>${node.get('Name')}</div>`
|
||||
)
|
||||
|
||||
return html`
|
||||
<div id="crumbs" onclick=${() => this.saveNode()}>
|
||||
<div class="crumbs ${nodeModified}">
|
||||
${crumbDivs}
|
||||
</div>
|
||||
</div>
|
||||
<div id="sync-progress"></div>
|
||||
<div id="name">${node.get('Name')}</div>
|
||||
<${NodeContent} key=${node.UUID} node=${node} ref=${this.nodeContent} />
|
||||
<div id="blank"></div>
|
||||
export class N2NodeUI extends CustomHTMLElement {
|
||||
static {// {{{
|
||||
this.tmpl = document.createElement('template')
|
||||
this.tmpl.innerHTML = `
|
||||
<div data-el="name"></div>
|
||||
<textarea data-el="node-content" required rows=1></textarea>
|
||||
`
|
||||
}// }}}
|
||||
|
||||
|
||||
|
||||
|
||||
return
|
||||
|
||||
let crumbs = [
|
||||
html`<div class="crumb" onclick=${() => this.goToNode(0)}>Start</div>`
|
||||
]
|
||||
|
||||
crumbs = crumbs.concat(node.Crumbs.slice(0).map(node =>
|
||||
html`<div class="crumb" onclick=${() => this.goToNode(node.ID)}>${node.Name}</div>`
|
||||
).reverse())
|
||||
|
||||
|
||||
// Page to display
|
||||
let page = ''
|
||||
switch (this.page.value) {
|
||||
case 'node':
|
||||
if (node.ID === 0) {
|
||||
page = html`
|
||||
<div style="cursor: pointer; color: #000; text-align: center;" onclick=${() => { this.page.value = 'schedule-events' }}>Schedule events</div>
|
||||
${children.length > 0 ? html`<div class="child-nodes">${children}</div><div id="notes-version">Notes version ${window._VERSION}</div>` : html``}
|
||||
`
|
||||
} else {
|
||||
let padlock = ''
|
||||
if (node.CryptoKeyID > 0)
|
||||
padlock = html`<img src="/images/${window._VERSION}/padlock-black.svg" style="height: 24px;" />`
|
||||
|
||||
page = html`
|
||||
${children.length > 0 ? html`<div class="child-nodes">${children}</div>` : html``}
|
||||
<div class="node-name">
|
||||
${node.Name} ${padlock}
|
||||
</div>
|
||||
<${NodeContent} key=${node.ID} node=${node} ref=${this.nodeContent} />
|
||||
<${NodeEvents} events=${node.ScheduleEvents.value} />
|
||||
<${Checklist} ui=${this} groups=${node.ChecklistGroups} />
|
||||
<${NodeFiles} node=${this.node.value} />
|
||||
`
|
||||
}
|
||||
break
|
||||
|
||||
case 'upload':
|
||||
page = html`<${UploadUI} nodeui=${this} />`
|
||||
break
|
||||
|
||||
case 'node-properties':
|
||||
page = html`<${NodeProperties} ref=${this.nodeProperties} nodeui=${this} />`
|
||||
break
|
||||
|
||||
case 'keys':
|
||||
page = html`<${Keys} nodeui=${this} />`
|
||||
break
|
||||
|
||||
case 'profile-settings':
|
||||
page = html`<${ProfileSettings} nodeui=${this} />`
|
||||
break
|
||||
|
||||
case 'search':
|
||||
page = html`<${Search} nodeui=${this} />`
|
||||
break
|
||||
|
||||
case 'schedule-events':
|
||||
page = html`<${ScheduleEventList} nodeui=${this} />`
|
||||
break
|
||||
}
|
||||
|
||||
const menu = () => (this.menu.value ? html`<${Menu} nodeui=${this} />` : null)
|
||||
const checklist = () =>
|
||||
html`
|
||||
<div class="checklist" onclick=${evt => { evt.stopPropagation(); this.toggleChecklist() }}>
|
||||
<img src="/images/${window._VERSION}/${this.showChecklist() ? 'checklist-on.svg' : 'checklist-off.svg'}" />
|
||||
</div>`
|
||||
|
||||
return html`
|
||||
<${menu} />
|
||||
<!--header class="${modified}" onclick=${() => this.saveNode()}>
|
||||
<div class="tree"><img src="/images/${window._VERSION}/tree.svg" onclick=${() => document.getElementById('app').classList.toggle('toggle-tree')} /></div>
|
||||
<div class="name">Notes</div>
|
||||
<div class="markdown" onclick=${evt => { evt.stopPropagation(); this.toggleMarkdown() }}><img src="/images/${window._VERSION}/${node.RenderMarkdown.value ? 'markdown.svg' : 'markdown-hollow.svg'}" /></div>
|
||||
<${checklist} />
|
||||
<div class="search" onclick=${evt => { evt.stopPropagation(); this.showPage('search') }}><img src="/images/${window._VERSION}/search.svg" /></div>
|
||||
<div class="add" onclick=${evt => this.createNode(evt)}><img src="/images/${window._VERSION}/add.svg" /></div>
|
||||
<div class="keys" onclick=${evt => { evt.stopPropagation(); this.showPage('keys') }}><img src="/images/${window._VERSION}/padlock.svg" /></div>
|
||||
<div class="menu" onclick=${evt => this.showMenu(evt)}>☰</div>
|
||||
</header-->
|
||||
|
||||
<div style="display: grid; justify-items: center;">
|
||||
<div id="crumbs">
|
||||
<div class="crumbs">${crumbs}</crumbs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${page}
|
||||
`
|
||||
}//}}}
|
||||
async componentDidMount() {//{{{
|
||||
console.log('hum')
|
||||
_notes2.current.goToNode(this.props.startNode.UUID, true)
|
||||
_notes2.current.tree.expandToTrunk(this.props.startNode)
|
||||
|
||||
const syncProgressEl = document.getElementById('#sync-progress')
|
||||
console.log(syncProgressEl)
|
||||
}//}}}
|
||||
setNode(node) {//{{{
|
||||
this.nodeModified.value = false
|
||||
this.node.value = node
|
||||
}//}}}
|
||||
setCrumbs(nodes) {//{{{
|
||||
this.crumbs = nodes
|
||||
}//}}}
|
||||
async saveNode() {//{{{
|
||||
if (!this.nodeModified.value)
|
||||
return
|
||||
|
||||
/* The node history is a local store for node history.
|
||||
* This could be provisioned from the server or cleared if
|
||||
* deemed unnecessary.
|
||||
*
|
||||
* The send queue is what will be sent back to the server
|
||||
* to have a recorded history of the notes.
|
||||
*
|
||||
* A setting to be implemented in the future could be to
|
||||
* not save the history locally at all. */
|
||||
const node = this.node.value
|
||||
|
||||
// The node is still in its old state and will present
|
||||
// the unmodified content to the node store.
|
||||
const history = nodeStore.nodesHistory.add(node)
|
||||
|
||||
// Prepares the node object for saving.
|
||||
// Sets Updated value to current date and time.
|
||||
await node.save()
|
||||
|
||||
// Updated node is added to the send queue to be stored on server.
|
||||
const sendQueue = nodeStore.sendQueue.add(this.node.value)
|
||||
|
||||
// Updated node is saved to the primary node store.
|
||||
const nodeStoreAdding = nodeStore.add([node])
|
||||
|
||||
await Promise.all([history, sendQueue, nodeStoreAdding])
|
||||
this.nodeModified.value = false
|
||||
}//}}}
|
||||
|
||||
keyHandler(evt) {//{{{
|
||||
let handled = true
|
||||
|
||||
// All keybindings is Alt+Shift, since the popular browsers at the time (2023) allows to override thees.
|
||||
// Ctrl+S is the exception to using Alt+Shift, since it is overridable and in such widespread use for saving.
|
||||
// Thus, the exception is acceptable to consequent use of alt+shift.
|
||||
if (!(evt.shiftKey && evt.altKey) && !(evt.key.toUpperCase() === 'S' && evt.ctrlKey))
|
||||
return
|
||||
|
||||
switch (evt.key.toUpperCase()) {
|
||||
case 'T':
|
||||
if (document.activeElement.id === 'tree')
|
||||
document.getElementById('node-content').focus()
|
||||
else
|
||||
document.getElementById('tree').focus()
|
||||
break
|
||||
|
||||
case 'F':
|
||||
_mbus.dispatch('op-search')
|
||||
break
|
||||
/*
|
||||
case 'C':
|
||||
this.showPage('node')
|
||||
break
|
||||
|
||||
case 'E':
|
||||
this.showPage('keys')
|
||||
break
|
||||
|
||||
case 'M':
|
||||
this.toggleMarkdown()
|
||||
break
|
||||
|
||||
case 'N':
|
||||
this.createNode()
|
||||
break
|
||||
|
||||
case 'P':
|
||||
this.showPage('node-properties')
|
||||
break
|
||||
|
||||
*/
|
||||
case 'S':
|
||||
if (this.page.value === 'node')
|
||||
this.saveNode()
|
||||
else if (this.page.value === 'node-properties')
|
||||
this.nodeProperties.current.save()
|
||||
break
|
||||
/*
|
||||
|
||||
case 'U':
|
||||
this.showPage('upload')
|
||||
break
|
||||
|
||||
case 'F':
|
||||
this.showPage('search')
|
||||
break
|
||||
*/
|
||||
|
||||
default:
|
||||
handled = false
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
}
|
||||
}//}}}
|
||||
}
|
||||
|
||||
class NodeContent extends Component {
|
||||
constructor(props) {//{{{
|
||||
super(props)
|
||||
this.contentDiv = createRef()
|
||||
this.state = {
|
||||
modified: false,
|
||||
}
|
||||
}//}}}
|
||||
render({ node }) {//{{{
|
||||
let content = ''
|
||||
try {
|
||||
content = node.content()
|
||||
} catch (err) {
|
||||
return html`
|
||||
<div id="node-content" class="node-content encrypted">${err.message}</div>
|
||||
`
|
||||
}
|
||||
|
||||
/*
|
||||
let element
|
||||
if (node.RenderMarkdown.value)
|
||||
element = html`<${MarkdownContent} key='markdown-content' content=${content} />`
|
||||
else
|
||||
*/
|
||||
const element = html`
|
||||
<div class="grow-wrap">
|
||||
<textarea id="node-content" class="node-content" ref=${this.contentDiv} oninput=${evt => this.contentChanged(evt)} required rows=1>${content}</textarea>
|
||||
</div>
|
||||
`
|
||||
|
||||
return element
|
||||
}//}}}
|
||||
componentDidMount() {//{{{
|
||||
this.resize()
|
||||
window.addEventListener('resize', () => this.resize())
|
||||
|
||||
const contentResizeObserver = new ResizeObserver(entries => {
|
||||
for (const idx in entries) {
|
||||
const w = entries[idx].contentRect.width
|
||||
document.querySelector('#crumbs .crumbs').style.width = `${w}px`
|
||||
}
|
||||
});
|
||||
|
||||
const nodeContent = document.getElementById('node-content')
|
||||
contentResizeObserver.observe(nodeContent);
|
||||
|
||||
}//}}}
|
||||
componentDidUpdate() {//{{{
|
||||
this.resize()
|
||||
}//}}}
|
||||
contentChanged(evt) {//{{{
|
||||
_notes2.current.nodeUI.current.nodeModified.value = true
|
||||
this.props.node.setContent(evt.target.value)
|
||||
this.resize()
|
||||
}//}}}
|
||||
resize() {//{{{
|
||||
const textarea = document.getElementById('node-content')
|
||||
if (textarea)
|
||||
textarea.parentNode.dataset.replicatedValue = textarea.value
|
||||
}//}}}
|
||||
}
|
||||
|
||||
export class NodeUINative {
|
||||
constructor(parentElement) {// {{{
|
||||
constructor() {// {{{
|
||||
super()
|
||||
this.node = null
|
||||
this.parent = parentElement
|
||||
this.parent.replaceChildren(this.createElements())
|
||||
|
||||
this.style.display = 'contents'
|
||||
|
||||
_mbus.subscribe('NODE_UI_OPEN', event => {
|
||||
this.node = event.detail.data
|
||||
|
|
@ -353,42 +28,25 @@ export class NodeUINative {
|
|||
_mbus.subscribe('NODE_UNMODIFIED', () => {
|
||||
document.querySelector('#crumbs .crumbs')?.classList.remove('node-modified')
|
||||
})
|
||||
}// }}}
|
||||
createElements() {// {{{
|
||||
const tmpl = document.createElement('template')
|
||||
tmpl.innerHTML = `
|
||||
<div id="name"></div>
|
||||
<div class="grow-wrap" style="display: none">
|
||||
<textarea id="node-content" class="node-content" required rows=1></textarea>
|
||||
</div>
|
||||
`
|
||||
|
||||
tmpl.content.querySelector('#node-content').addEventListener('input', event => this.contentChanged(event))
|
||||
|
||||
return tmpl.content
|
||||
this.elNodeContent.addEventListener('input', event => this.contentChanged(event))
|
||||
}// }}}
|
||||
render() {// {{{
|
||||
this.parent.querySelector('.grow-wrap').style.display = (this.node === null ? 'none' : 'grid')
|
||||
this.parent.querySelector('#name').innerText = this.node?.get('Name') ?? ''
|
||||
this.parent.querySelector('#node-content').value = this.node?.get('Content') ?? ''
|
||||
|
||||
this.resize()
|
||||
return this.parent
|
||||
this.elName.innerText = this.node?.get('Name') ?? ''
|
||||
this.elNodeContent.value = this.node?.get('Content') ?? ''
|
||||
}// }}}
|
||||
takeFocus() {// {{{
|
||||
this.elNodeContent.focus()
|
||||
}// }}}
|
||||
|
||||
resize() {//{{{
|
||||
const textarea = this.parent.querySelector('#node-content')
|
||||
textarea.parentNode.dataset.replicatedValue = textarea.value
|
||||
}//}}}
|
||||
contentChanged(event) {//{{{
|
||||
this.node.setContent(event.target.value)
|
||||
this.resize()
|
||||
}//}}}
|
||||
isModified() {// {{{
|
||||
return this.node?.isModified()
|
||||
}// }}}
|
||||
|
||||
}
|
||||
customElements.define('n2-nodeui', N2NodeUI)
|
||||
|
||||
export class Node {
|
||||
static sort(a, b) {//{{{
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export class NodeStore {
|
|||
this.sendQueue = null
|
||||
this.nodesHistory = null
|
||||
}//}}}
|
||||
async initializeDB() {//{{{
|
||||
initializeDB() {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = indexedDB.open('notes', 7)
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ export class NodeStore {
|
|||
}
|
||||
})
|
||||
}//}}}
|
||||
async initializeRootNode() {//{{{
|
||||
initializeRootNode() {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
// The root node is a magical node which displays as the first node if none is specified.
|
||||
// If not already existing, it will be created.
|
||||
|
|
@ -120,7 +120,7 @@ export class NodeStore {
|
|||
return n
|
||||
}//}}}
|
||||
|
||||
async getAppState(key) {//{{{
|
||||
getAppState(key) {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
const trx = this.db.transaction('app_state', 'readonly')
|
||||
const appState = trx.objectStore('app_state')
|
||||
|
|
@ -135,7 +135,7 @@ export class NodeStore {
|
|||
getRequest.onerror = (event) => reject(event.target.error)
|
||||
})
|
||||
}//}}}
|
||||
async setAppState(key, value) {//{{{
|
||||
setAppState(key, value) {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const t = this.db.transaction('app_state', 'readwrite')
|
||||
|
|
@ -159,32 +159,7 @@ export class NodeStore {
|
|||
})
|
||||
}//}}}
|
||||
|
||||
/* TODO - Remove?
|
||||
async storeNode(node) {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
const t = this.db.transaction('nodes', 'readwrite')
|
||||
const nodeStore = t.objectStore('nodes')
|
||||
t.onerror = (event) => {
|
||||
console.log('transaction error', event.target.error)
|
||||
reject(event.target.error)
|
||||
}
|
||||
t.oncomplete = () => {
|
||||
resolve()
|
||||
}
|
||||
|
||||
const nodeReq = nodeStore.put(node.data)
|
||||
nodeReq.onsuccess = () => {
|
||||
console.debug(`Storing ${node.UUID} (${node.get('Name')})`)
|
||||
}
|
||||
queueReq.onerror = (event) => {
|
||||
console.log(`Error storing ${node.UUID}`, event.target.error)
|
||||
reject(event.target.error)
|
||||
}
|
||||
})
|
||||
}//}}}
|
||||
*/
|
||||
|
||||
async upsertNodeRecords(records) {//{{{
|
||||
upsertNodeRecords(records) {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
const t = this.db.transaction('nodes', 'readwrite')
|
||||
const nodeStore = t.objectStore('nodes')
|
||||
|
|
@ -222,7 +197,7 @@ export class NodeStore {
|
|||
}
|
||||
})
|
||||
}//}}}
|
||||
async getTreeNodes(parent, newLevel) {//{{{
|
||||
getTreeNodes(parent, newLevel) {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
// Parent of toplevel nodes is ROOT_NODE in indexedDB.
|
||||
// Only the root node has '' as parent.
|
||||
|
|
@ -274,13 +249,13 @@ export class NodeStore {
|
|||
})
|
||||
}//}}}
|
||||
|
||||
async add(records) {//{{{
|
||||
add(records) {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const t = this.db.transaction('nodes', 'readwrite')
|
||||
const nodeStore = t.objectStore('nodes')
|
||||
t.onerror = (event) => {
|
||||
console.log('transaction error', event.target.error)
|
||||
console.error('transaction error', event.target.error)
|
||||
reject(event.target.error)
|
||||
}
|
||||
|
||||
|
|
@ -291,12 +266,9 @@ export class NodeStore {
|
|||
const addReq = nodeStore.put(record.data)
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
addReq.onsuccess = () => {
|
||||
console.debug('OK!', record.ID, record.Name)
|
||||
resolve()
|
||||
}
|
||||
addReq.onsuccess = () => resolve()
|
||||
addReq.onerror = (event) => {
|
||||
console.log('Error!', event.target.error, record.ID)
|
||||
console.error('Error!', event.target.error, record.ID)
|
||||
reject(event.target.error)
|
||||
}
|
||||
})
|
||||
|
|
@ -309,7 +281,7 @@ export class NodeStore {
|
|||
}
|
||||
})
|
||||
}//}}}
|
||||
async get(uuid) {//{{{
|
||||
get(uuid) {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
const trx = this.db.transaction('nodes', 'readonly')
|
||||
const nodeStore = trx.objectStore('nodes')
|
||||
|
|
@ -325,7 +297,7 @@ export class NodeStore {
|
|||
}
|
||||
})
|
||||
}//}}}
|
||||
async getNodeAncestry(node, accumulated) {//{{{
|
||||
getNodeAncestry(node, accumulated) {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
if (accumulated === undefined)
|
||||
accumulated = []
|
||||
|
|
@ -357,7 +329,7 @@ export class NodeStore {
|
|||
|
||||
}//}}}
|
||||
|
||||
async nodeCount() {//{{{
|
||||
nodeCount() {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
const t = this.db.transaction('nodes', 'readwrite')
|
||||
const nodeStore = t.objectStore('nodes')
|
||||
|
|
@ -374,7 +346,7 @@ class SimpleNodeStore {
|
|||
this.db = db
|
||||
this.storeName = storeName
|
||||
}//}}}
|
||||
async add(node) {//{{{
|
||||
add(node) {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
const t = this.db.transaction(['nodes', this.storeName], 'readwrite')
|
||||
const store = t.objectStore(this.storeName)
|
||||
|
|
@ -394,7 +366,7 @@ class SimpleNodeStore {
|
|||
}
|
||||
})
|
||||
}//}}}
|
||||
async retrieve(limit) {//{{{
|
||||
retrieve(limit) {//{{{
|
||||
return new Promise((resolve, reject) => {
|
||||
const cursorReq = this.db
|
||||
.transaction(['nodes', this.storeName], 'readonly')
|
||||
|
|
@ -422,7 +394,7 @@ class SimpleNodeStore {
|
|||
}
|
||||
})
|
||||
}//}}}
|
||||
async delete(keys) {//{{{
|
||||
delete(keys) {//{{{
|
||||
const store = this.db
|
||||
.transaction(['nodes', this.storeName], 'readwrite')
|
||||
.objectStore(this.storeName)
|
||||
|
|
@ -439,7 +411,7 @@ class SimpleNodeStore {
|
|||
}
|
||||
return Promise.all(promises)
|
||||
}//}}}
|
||||
async count() {//{{{
|
||||
count() {//{{{
|
||||
const store = this.db
|
||||
.transaction(['nodes', this.storeName], 'readonly')
|
||||
.objectStore(this.storeName)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { API } from 'api'
|
||||
import { Node } from 'node'
|
||||
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
|
||||
|
||||
export class Sync {
|
||||
constructor() {//{{{
|
||||
|
|
@ -154,70 +155,43 @@ export class Sync {
|
|||
}//}}}
|
||||
}
|
||||
|
||||
export class SyncProgress {
|
||||
constructor(parentEl) {//{{{
|
||||
export class N2SyncProgress extends CustomHTMLElement {
|
||||
static {
|
||||
this.tmpl = document.createElement('template')
|
||||
this.tmpl.innerHTML = `
|
||||
<progress data-el="progress" min=0 max=137 value=0></progress>
|
||||
<div data-el="count" class="count">0 / 0</div>
|
||||
`
|
||||
}
|
||||
constructor() {//{{{
|
||||
super()
|
||||
|
||||
this.reset()
|
||||
_mbus.subscribe('SYNC_COUNT', event => this.progressHandler(event))
|
||||
_mbus.subscribe('SYNC_HANDLED', event => this.progressHandler(event))
|
||||
_mbus.subscribe('SYNC_DONE', event => this.progressHandler(event))
|
||||
|
||||
this.el = this.createElements()
|
||||
parentEl.replaceChildren(this.el)
|
||||
}//}}}
|
||||
createElements() {
|
||||
const div = document.createElement('div')
|
||||
div.classList.add('container')
|
||||
div.innerHTML = `
|
||||
<progress min=0 max=137 value=0></progress>
|
||||
<div class="count">0 / 0</div>
|
||||
`
|
||||
|
||||
this.elProgress = div.querySelector('progress')
|
||||
this.elCount = div.querySelector('.count')
|
||||
return div
|
||||
}
|
||||
reset() {//{{{
|
||||
this.forceUpdateRequest = null
|
||||
this.state = {
|
||||
nodesToSync: 0,
|
||||
nodesSynced: 0,
|
||||
syncedDone: false,
|
||||
}
|
||||
document.getElementById('sync-progress')?.classList.remove('hidden')
|
||||
}//}}}
|
||||
getSnapshotBeforeUpdate(_, prevState) {//{{{
|
||||
if (!prevState.syncedDone && this.state.syncedDone)
|
||||
setTimeout(() => document.getElementById('sync-progress')?.classList.add('hidden'), 750)
|
||||
}//}}}
|
||||
componentDidUpdate() {//{{{
|
||||
if (!this.state.syncedDone) {
|
||||
if (this.forceUpdateRequest !== null)
|
||||
clearTimeout(this.forceUpdateRequest)
|
||||
this.forceUpdateRequest = setTimeout(
|
||||
() => {
|
||||
this.forceUpdateRequest = null
|
||||
this.forceUpdate()
|
||||
},
|
||||
50
|
||||
)
|
||||
}
|
||||
}//}}}
|
||||
progressHandler(event) {//{{{
|
||||
const eventData = event.detail.data
|
||||
switch (event.type) {
|
||||
case 'SYNC_COUNT':
|
||||
console.log(eventData.count)
|
||||
this.state.nodesToSync = eventData.count
|
||||
this.setSyncState(true)
|
||||
break
|
||||
|
||||
case 'SYNC_HANDLED':
|
||||
console.log('sync handled')
|
||||
this.state.nodesSynced = eventData.handled
|
||||
break
|
||||
|
||||
case 'SYNC_DONE':
|
||||
// Hides the progress bar.
|
||||
this.state.syncedDone = true
|
||||
this.setSyncState(false)
|
||||
|
||||
// Don't update anything if nothing was synced.
|
||||
if (this.state.nodesSynced === 0)
|
||||
|
|
@ -233,17 +207,17 @@ export class SyncProgress {
|
|||
this.render()
|
||||
}//}}}
|
||||
render() {//{{{
|
||||
console.log('render', this.state.nodesToSync)
|
||||
this.elProgress.max = this.state.nodesToSync
|
||||
this.elProgress.value = this.state.nodesSynced
|
||||
this.elCount.innerText = `${this.state.nodesSynced} / ${this.state.nodesToSync}`
|
||||
/*
|
||||
if (nodesToSync === 0)
|
||||
return html`<div id="sync-progress"></div>`
|
||||
*/
|
||||
|
||||
|
||||
}//}}}
|
||||
setSyncState(state) {// {{{
|
||||
if (state)
|
||||
this.classList.add('show')
|
||||
else
|
||||
setTimeout(() => this.classList.remove('show'), 1500)
|
||||
}// }}}
|
||||
}
|
||||
customElements.define('n2-syncprogress', N2SyncProgress)
|
||||
|
||||
// vim: foldmethod=marker
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue