From 559f6bc8f25bbb9ee5b81bcd4e39939f8a24b374 Mon Sep 17 00:00:00 2001 From: Ivan Date: Mon, 13 Apr 2026 18:29:33 -0500 Subject: [PATCH] feat(components): update AboutPage and ConversationViewer with async file handling; add max hops filter to NetworkVisualiser and improve SettingsPage styling --- .../frontend/components/about/AboutPage.vue | 24 +- .../messages/ConversationViewer.vue | 214 +++++++++++++++--- .../network-visualiser/NetworkVisualiser.vue | 62 +++++ .../components/settings/SettingsPage.vue | 2 +- 4 files changed, 260 insertions(+), 42 deletions(-) diff --git a/meshchatx/src/frontend/components/about/AboutPage.vue b/meshchatx/src/frontend/components/about/AboutPage.vue index 18144c7..10ce23f 100644 --- a/meshchatx/src/frontend/components/about/AboutPage.vue +++ b/meshchatx/src/frontend/components/about/AboutPage.vue @@ -1257,16 +1257,28 @@ export default { showTutorial() { GlobalEmitter.emit("show-tutorial"); }, - showReticulumConfigFile() { + async showReticulumConfigFile() { const reticulumConfigPath = this.appInfo.reticulum_config_path; - if (reticulumConfigPath) { - ElectronUtils.showPathInFolder(reticulumConfigPath); + if (!reticulumConfigPath) { + return; + } + const ok = await ElectronUtils.revealPathInFolderOrCopy(reticulumConfigPath, () => + ToastUtils.success(this.$t("common.copied")), + ); + if (!ok) { + DialogUtils.alert(reticulumConfigPath); } }, - showDatabaseFile() { + async showDatabaseFile() { const databasePath = this.appInfo.database_path; - if (databasePath) { - ElectronUtils.showPathInFolder(databasePath); + if (!databasePath) { + return; + } + const ok = await ElectronUtils.revealPathInFolderOrCopy(databasePath, () => + ToastUtils.success(this.$t("common.copied")), + ); + if (!ok) { + DialogUtils.alert(databasePath); } }, formatBytes: function (bytes) { diff --git a/meshchatx/src/frontend/components/messages/ConversationViewer.vue b/meshchatx/src/frontend/components/messages/ConversationViewer.vue index 64b02cc..0faf665 100644 --- a/meshchatx/src/frontend/components/messages/ConversationViewer.vue +++ b/meshchatx/src/frontend/components/messages/ConversationViewer.vue @@ -529,7 +529,7 @@ : isOutboundPathfindingBubble(entry.items[0]) ? 'bg-gray-200 dark:bg-zinc-700 text-gray-900 dark:text-zinc-100 border border-gray-300 dark:border-zinc-600 shadow-sm' : entry.items[0].is_outbound - ? 'shadow-sm' + ? outboundBubbleSurfaceClass(entry.items[0]) : 'bg-white dark:bg-zinc-900 text-gray-900 dark:text-zinc-100 border border-gray-200/60 dark:border-zinc-800/60 shadow-sm', ]" :style="bubbleStyles(entry.items[0])" @@ -548,12 +548,17 @@ {{ formatTimeAgo(entry.items[0].lxmf_message.created_at) }}
- - {{ $t("messages.opportunistic_deferred_label") }} - + + {{ $t("messages.opportunistic_deferred_label") }} +
+ +
+
+ + {{ hopFilterSlider === 0 ? $t("visualiser.all") : hopFilterSlider }} +
+ +
+
!i.status); }, + hopSliderMax() { + let m = 0; + for (const e of this.pathTable) { + if (e.hops != null && e.hops > m) m = e.hops; + } + return Math.min(256, Math.max(1, m)); + }, + hopFilterMax() { + if (this.hopFilterSlider === 0) return null; + return this.hopFilterSlider; + }, }, watch: { autoReload(val) { @@ -354,6 +391,18 @@ export default { // we don't want to trigger a full update from server, just re-run the filtering on existing data this.processVisualization(); }, + hopSliderMax() { + if (this.hopFilterSlider > this.hopSliderMax) { + this.hopFilterSlider = this.hopSliderMax; + } + }, + hopFilterSlider() { + if (this._hopFilterDebounce) clearTimeout(this._hopFilterDebounce); + this._hopFilterDebounce = setTimeout(() => { + this._hopFilterDebounce = null; + this.processVisualization(); + }, 80); + }, }, beforeUnmount() { if (this.abortController) { @@ -368,6 +417,10 @@ export default { this.stopOrbit(); this.stopBouncingBalls(); clearInterval(this.reloadInterval); + if (this._hopFilterDebounce) { + clearTimeout(this._hopFilterDebounce); + this._hopFilterDebounce = null; + } if (this.network) { this.network.destroy(); } @@ -1210,6 +1263,14 @@ export default { continue; } + if ( + this.hopFilterMax != null && + disc.hops != null && + disc.hops > this.hopFilterMax + ) { + continue; + } + const isConnected = this.discoveredActive.some((a) => { const aHost = a.target_host || a.remote || a.listen_ip; const aPort = a.target_port || a.listen_port; @@ -1284,6 +1345,7 @@ export default { for (const entry of chunk) { this.loadedNodesCount++; if (entry.hops == null) continue; + if (this.hopFilterMax != null && entry.hops > this.hopFilterMax) continue; const announce = this.announces[entry.hash]; if (!announce || !aspectsToShow.includes(announce.aspect)) continue; diff --git a/meshchatx/src/frontend/components/settings/SettingsPage.vue b/meshchatx/src/frontend/components/settings/SettingsPage.vue index fa1a748..f5b81be 100644 --- a/meshchatx/src/frontend/components/settings/SettingsPage.vue +++ b/meshchatx/src/frontend/components/settings/SettingsPage.vue @@ -98,7 +98,7 @@