From d9c82868abd118462495c631ac8ca17fd0da64a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sat, 29 Nov 2025 16:45:56 +0100 Subject: [PATCH] Better visual sync --- main.go | 6 +- static/css/notes2.css | 121 +++++++++++++++++++++----------------- static/js/app.mjs | 4 ++ static/js/node.mjs | 6 +- static/js/sync.mjs | 97 +++++++++++++++--------------- views/pages/notes2.gotmpl | 2 - 6 files changed, 130 insertions(+), 106 deletions(-) diff --git a/main.go b/main.go index 3e5a05f..e608601 100644 --- a/main.go +++ b/main.go @@ -25,7 +25,7 @@ import ( const VERSION = "v1" const CONTEXT_USER = 1 -const SYNC_PAGINATION = 100 +const SYNC_PAGINATION = 500 var ( FlagGenerate bool @@ -269,9 +269,11 @@ func actionSyncFromServer(w http.ResponseWriter, r *http.Request) { // {{{ return } + /* Log.Debug("/sync/from_server", "num_nodes", len(nodes), "maxSeq", maxSeq) foo, _ := json.Marshal(nodes) os.WriteFile(fmt.Sprintf("/tmp/nodes-%d.json", offset), foo, 0644) + */ j, _ := json.Marshal(struct { OK bool @@ -288,7 +290,6 @@ func actionSyncFromServerCount(w http.ResponseWriter, r *http.Request) { // {{{ user := getUser(r) changedFrom, _ := strconv.Atoi(r.PathValue("sequence")) - Log.Debug("FOO", "UUID", user.ClientUUID, "changedFrom", changedFrom) count, err := NodesCount(user.UserID, uint64(changedFrom), user.ClientUUID) if err != nil { Log.Error("/sync/from_server/count", "error", err) @@ -341,7 +342,6 @@ func actionSyncToServer(w http.ResponseWriter, r *http.Request) { // {{{ return } - responseData(w, map[string]any{ "OK": true, }) diff --git a/static/css/notes2.css b/static/css/notes2.css index 1104c8b..adfa51d 100644 --- a/static/css/notes2.css +++ b/static/css/notes2.css @@ -19,14 +19,10 @@ html { "tree files" */ "tree blank" - ; + ; grid-template-columns: min-content 1fr; grid-template-rows: - 48px - 56px - 48px - min-content - 1fr; + 48px 56px 48px min-content 1fr; @media only screen and (max-width: 600px) { @@ -41,7 +37,7 @@ html { "files" */ "blank" - ; + ; grid-template-columns: 1fr; #tree { @@ -55,7 +51,8 @@ html { display: grid; padding: 16px 0px 16px 16px; color: #ddd; - z-index: 100; /* Over crumbs shadow */ + z-index: 100; + /* Over crumbs shadow */ border-left: 2px solid #333; &:focus { @@ -90,13 +87,13 @@ html { display: grid; grid-template-columns: 24px min-content; grid-template-rows: - min-content - 1fr; + min-content 1fr; margin-top: 12px; .expand-toggle { user-select: none; + img { width: 16px; height: 16px; @@ -111,6 +108,7 @@ html { &:hover { color: var(--color1); } + &.selected { color: var(--color1); font-weight: bold; @@ -135,7 +133,7 @@ html { padding: 16px 32px; background-color: #333; border-radius: 8px; - box-shadow: 5px 5px 10px -5px rgba(0,0,0,0.75); + box-shadow: 5px 5px 10px -5px rgba(0, 0, 0, 0.75); } #crumbs { @@ -158,6 +156,7 @@ html { &.node-modified { background-color: var(--color1); color: var(--color2); + .crumb:after { color: var(--color2); } @@ -184,6 +183,7 @@ html { .crumb:last-child { margin-right: 0; } + .crumb:last-child:after { content: ''; margin-left: 0px; @@ -194,55 +194,64 @@ html { } #sync-progress { + --radius: 8px; + grid-area: sync; display: grid; justify-items: center; - + align-items: center; + width: 100%; height: 56px; - position: relative; - progress { - width: 100%; - padding: 0 7px; - max-width: 900px; - height: 16px; - border-radius: 4px; + .container { + position: relative; + + progress { + width: 900px; + padding: 0 7px; + max-width: 900px; + height: 24px; + border-radius: 8px; + } + + .count { + position: absolute; + top: 5px; + width: 100%; + white-space: nowrap; + color: #888; + text-align: center; + font-size: 12pt; + font-weight: bold; + } + + progress[value]::-webkit-progress-bar { + background-color: #eee; + box-shadow: 0 2px var(--radius) rgba(0, 0, 0, 0.25) inset; + border-radius: var(--radius); + } + + progress[value]::-moz-progress-bar { + background-color: #eee; + box-shadow: 0 2px var(--radius) rgba(0, 0, 0, 0.25) inset; + border-radius: var(--radius); + } + + progress[value]::-webkit-progress-value { + background: rgb(186, 95, 89); + background: linear-gradient(180deg, rgba(186, 95, 89, 1) 0%, rgba(254, 95, 85, 1) 50%, rgba(186, 95, 89, 1) 100%); + border-radius: var(--radius); + } + + progress[value]::-moz-progress-value { + background: rgb(186, 95, 89); + background: linear-gradient(180deg, rgba(186, 95, 89, 1) 0%, rgba(254, 95, 85, 1) 50%, rgba(186, 95, 89, 1) 100%); + border-radius: var(--radius); + } } - progress[value]::-webkit-progress-bar { - background-color: #eee; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25) inset; - border-radius: 4px; - } - progress[value]::-moz-progress-bar { - background-color: #eee; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25) inset; - border-radius: 4px; - } - - progress[value]::-webkit-progress-value { - background: rgb(186,95,89); - background: linear-gradient(180deg, rgba(186,95,89,1) 0%, rgba(254,95,85,1) 50%, rgba(186,95,89,1) 100%); - border-radius: 4px; - } - - /* TODO: style the progress value for Firefox */ - progress[value]::-moz-progress-value { - background: rgb(186,95,89); - background: linear-gradient(180deg, rgba(186,95,89,1) 0%, rgba(254,95,85,1) 50%, rgba(186,95,89,1) 100%); - border-radius: 4px; - } - - .count { - width: min-content; - white-space: nowrap; - margin-top: 0px; - color: #888; - position: absolute; - top: 22px; - } &.hidden { visibility: hidden; @@ -270,6 +279,7 @@ html { grid-area: content; font-size: 1.0em; } + .grow-wrap::after { /* Note the weird space! Needed to preventy jumpy behavior */ content: attr(data-replicated-value) " "; @@ -285,14 +295,16 @@ html { /* Hidden from view, clicks, and screen readers */ visibility: hidden; } -.grow-wrap > textarea { + +.grow-wrap>textarea { /* You could leave this, but after a user resizes, then it ruins the auto sizing */ resize: none; /* Firefox shows scrollbar on growth, you can hide like this. */ overflow: hidden; } -.grow-wrap > textarea, + +.grow-wrap>textarea, .grow-wrap::after { /* Identical styling required!! */ padding: 0.5rem; @@ -301,6 +313,7 @@ html { /* Place on top of each other */ grid-area: 1 / 1 / 2 / 2; } + /* ============================================================= */ #node-content { @@ -329,7 +342,7 @@ dialog.op { &::backdrop { background: rgba(0, 0, 0, 0.5); } - + .header { font-weight: bold; margin-top: 16px; diff --git a/static/js/app.mjs b/static/js/app.mjs index 7882923..7926368 100644 --- a/static/js/app.mjs +++ b/static/js/app.mjs @@ -1,6 +1,7 @@ import { ROOT_NODE } from 'node_store' import { TreeNative } from 'tree' import { NodeUINative, Node } from 'node' +import { SyncProgress } from 'sync' export class App { constructor() {// {{{ @@ -34,6 +35,9 @@ export class App { document.getElementById('node-content')?.focus() }) + const syncProgress = document.getElementById('sync-progress') + new SyncProgress(syncProgress) + window._sync = new Sync() window._sync.run() }// }}} diff --git a/static/js/node.mjs b/static/js/node.mjs index dfd1052..3820941 100644 --- a/static/js/node.mjs +++ b/static/js/node.mjs @@ -54,7 +54,7 @@ export class NodeUI extends Component { ${crumbDivs} - <${SyncProgress} ref=${this.syncProgress} /> +
${node.get('Name')}
<${NodeContent} key=${node.UUID} node=${node} ref=${this.nodeContent} />
@@ -156,8 +156,12 @@ export class NodeUI extends Component { ` }//}}} 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 diff --git a/static/js/sync.mjs b/static/js/sync.mjs index b1097a9..edc93ea 100644 --- a/static/js/sync.mjs +++ b/static/js/sync.mjs @@ -1,31 +1,11 @@ import { API } from 'api' import { Node } from 'node' -import { h, Component } from 'preact' -import htm from 'htm' -const html = htm.bind(h) - -const SYNC_COUNT = 1 -const SYNC_HANDLED = 2 -const SYNC_DONE = 3 export class Sync { constructor() {//{{{ this.listeners = [] this.messagesReceived = [] }//}}} - addListener(fn, runMessageQueue) {//{{{ - // Some handlers won't be added until a time after sync messages have been added to the queue. - // This is an opportunity for the handler to receive the old messages in order. - if (runMessageQueue) - for (const msg of this.messagesReceived) - fn(msg) - this.listeners.push(fn) - }//}}} - pushMessage(msg) {//{{{ - this.messagesReceived.push(msg) - for (const fn of this.listeners) - fn(msg) - }//}}} async run() {//{{{ try { @@ -38,8 +18,8 @@ export class Sync { let nodeCount = await this.getNodeCount(oldMax) nodeCount += await nodeStore.sendQueue.count() - const msg = { op: SYNC_COUNT, count: nodeCount } - this.pushMessage(msg) + + _mbus.dispatch('SYNC_COUNT', { count: nodeCount }) await this.nodesFromServer(oldMax) .then(durationNodes => { @@ -49,7 +29,7 @@ export class Sync { await this.nodesToServer() } finally { - this.pushMessage({ op: SYNC_DONE }) + _mbus.dispatch('SYNC_DONE') } }//}}} async getNodeCount(oldMax) {//{{{ @@ -60,6 +40,7 @@ export class Sync { async nodesFromServer(oldMax) {//{{{ const syncStart = Date.now() let syncEnd + let handled = 0 try { let currMax = oldMax let offset = 0 @@ -86,9 +67,14 @@ export class Sync { for (const i in res.Nodes) { backendNode = new Node(res.Nodes[i], -1) await window._sync.handleNode(backendNode) + + handled++ + if (handled % 100 === 0) + _mbus.dispatch('SYNC_HANDLED', { handled }) } } while (res.Continue) + _mbus.dispatch('SYNC_HANDLED', { handled }) nodeStore.setAppState('latest_sync_node', currMax) } catch (e) { @@ -130,7 +116,7 @@ export class Sync { } catch (e) { console.error(e) } finally { - this.pushMessage({ op: SYNC_HANDLED, count: 1 }) + //_mbus.dispatch('SYNC_HANDLED', { count: 1 }) } }//}}} async nodesToServer() {//{{{ @@ -157,7 +143,7 @@ export class Sync { // Nodes are archived on server and can now be deleted from the send queue. const keys = nodesToSend.map(node => node.ClientSequence) await nodeStore.sendQueue.delete(keys) - this.pushMessage({ op: SYNC_HANDLED, count: nodesToSend.length }) + _mbus.dispatch('SYNC_UPLOADED', { count: nodesToSend.length }) } catch (e) { console.trace(e) @@ -168,11 +154,28 @@ export class Sync { }//}}} } -export class SyncProgress extends Component { - constructor() {//{{{ - super() +export class SyncProgress { + constructor(parentEl) {//{{{ 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 = ` + +
0 / 0
+ ` + + this.elProgress = div.querySelector('progress') + this.elCount = div.querySelector('.count') + return div + } reset() {//{{{ this.forceUpdateRequest = null this.state = { @@ -182,9 +185,6 @@ export class SyncProgress extends Component { } document.getElementById('sync-progress')?.classList.remove('hidden') }//}}} - componentDidMount() {//{{{ - window._sync.addListener(msg => this.progressHandler(msg), true) - }//}}} getSnapshotBeforeUpdate(_, prevState) {//{{{ if (!prevState.syncedDone && this.state.syncedDone) setTimeout(() => document.getElementById('sync-progress')?.classList.add('hidden'), 750) @@ -202,19 +202,22 @@ export class SyncProgress extends Component { ) } }//}}} - progressHandler(msg) {//{{{ - switch (msg.op) { - case SYNC_COUNT: - this.setState({ nodesToSync: msg.count }) + progressHandler(event) {//{{{ + const eventData = event.detail.data + switch (event.type) { + case 'SYNC_COUNT': + console.log(eventData.count) + this.state.nodesToSync = eventData.count break - case SYNC_HANDLED: - this.state.nodesSynced += msg.count + case 'SYNC_HANDLED': + console.log('sync handled') + this.state.nodesSynced = eventData.handled break - case SYNC_DONE: + case 'SYNC_DONE': // Hides the progress bar. - this.setState({ syncedDone: true }) + this.state.syncedDone = true // Don't update anything if nothing was synced. if (this.state.nodesSynced === 0) @@ -227,17 +230,19 @@ export class SyncProgress extends Component { } break } + this.render() }//}}} - render(_, { nodesToSync, nodesSynced }) {//{{{ + 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`
` + */ + - return html` -
- -
${nodesSynced} / ${nodesToSync}
-
- ` }//}}} } diff --git a/views/pages/notes2.gotmpl b/views/pages/notes2.gotmpl index 52d128a..1a82a90 100644 --- a/views/pages/notes2.gotmpl +++ b/views/pages/notes2.gotmpl @@ -3,10 +3,8 @@
-