diff --git a/meshchatx/src/frontend/components/CommandPalette.vue b/meshchatx/src/frontend/components/CommandPalette.vue index a7ca6de..d085eb5 100644 --- a/meshchatx/src/frontend/components/CommandPalette.vue +++ b/meshchatx/src/frontend/components/CommandPalette.vue @@ -257,6 +257,14 @@ export default { type: "navigation", route: { name: "documentation" }, }, + { + id: "nav-repository-server", + title: "nav_repository_server", + description: "nav_repository_server_desc", + icon: "package-variant", + type: "navigation", + route: { name: "repository-server" }, + }, { id: "nav-micron-editor", title: "nav_micron_editor", diff --git a/meshchatx/src/frontend/components/messages/ConversationMessageEntry.vue b/meshchatx/src/frontend/components/messages/ConversationMessageEntry.vue index b4c854f..a598429 100644 --- a/meshchatx/src/frontend/components/messages/ConversationMessageEntry.vue +++ b/meshchatx/src/frontend/components/messages/ConversationMessageEntry.vue @@ -500,6 +500,7 @@ v-if=" chatItem.lxmf_message.content && !cv.getParsedItems(chatItem)?.isOnlyPaperMessage && + !cv.getParsedItems(chatItem)?.isOnlyMapLink && !cv.shouldHideAutoImageCaption(chatItem) " class="leading-relaxed break-words [word-break:break-word] min-w-0 markdown-content" @@ -635,6 +636,39 @@ Ingest Message + +
+
+ + {{ + cv.getParsedItems(chatItem).mapLink.kind === "ping" + ? $t("messages.map_link_ping_title") + : $t("messages.map_link_share_title") + }} +
+
+ {{ cv.getParsedItems(chatItem).mapLink.parsed.lat.toFixed(5) }}, + {{ cv.getParsedItems(chatItem).mapLink.parsed.lon.toFixed(5) }} + (z{{ cv.getParsedItems(chatItem).mapLink.parsed.zoom }}) +
+ + +
diff --git a/meshchatx/src/frontend/components/messages/ConversationViewer.vue b/meshchatx/src/frontend/components/messages/ConversationViewer.vue index 77e80ff..096cd71 100644 --- a/meshchatx/src/frontend/components/messages/ConversationViewer.vue +++ b/meshchatx/src/frontend/components/messages/ConversationViewer.vue @@ -45,143 +45,14 @@ @close="close" /> - -
-
-
-
- -

Telemetry History

-
- -
-
-
- No telemetry history found for this peer. -
-
-
- - {{ item.is_outbound ? "Sent" : "Received" }} - - {{ - formatTimeAgo(item.lxmf_message.created_at) - }} -
- - -
- -
- - -
- - - Battery: {{ item.lxmf_message.fields.telemetry.battery.charge_percent }}% - - - - SNR: {{ item.lxmf_message.fields.telemetry.physical_link.snr }}dB - -
- - -
- - Location Request -
-
-
-
-
-
- Battery Level (%) - {{ telemetryBatteryHistory[0].y }}% → - {{ telemetryBatteryHistory[telemetryBatteryHistory.length - 1].y }}% -
- - - - -
- -
- - -
-
-
-
+
item.is_outbound && ["failed", "cancelled"].includes(item.lxmf_message?.state) ); }, - telemetryBatteryHistory() { - return this.selectedPeerTelemetryItems - .filter((item) => item.lxmf_message.fields?.telemetry?.battery) - .map((item) => ({ - x: item.lxmf_message.timestamp, - y: item.lxmf_message.fields.telemetry.battery.charge_percent, - })) - .sort((a, b) => a.x - b.x); - }, - batterySparklinePath() { - const history = this.telemetryBatteryHistory; - if (history.length < 2) return ""; - - const minX = history[0].x; - const maxX = history[history.length - 1].x; - const rangeX = maxX - minX || 1; - - return history - .map((p, i) => { - const x = ((p.x - minX) / rangeX) * 100; - const y = 100 - p.y; // SVG y is top-down - return `${i === 0 ? "M" : "L"} ${x} ${y}`; - }) - .join(" "); + telemetryModalGradientSuffix() { + const h = this.selectedPeer?.destination_hash || "default"; + return h.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 24) || "default"; }, }, watch: { @@ -2292,6 +2145,9 @@ export default { if (this.getParsedItems(chatItem)?.isOnlyPaperMessage) { return false; } + if (this.getParsedItems(chatItem)?.isOnlyMapLink) { + return false; + } if (this.shouldHideAutoImageCaption(chatItem)) { return false; } @@ -2652,6 +2508,7 @@ export default { const items = { contact: null, paperMessage: null, + mapLink: null, }; // Parse contact: Contact: ivan [LXMF: ...] [LXST: ...] @@ -2694,6 +2551,23 @@ export default { } } + const mapUri = findMapUriInContent(content); + if (mapUri && !items.paperMessage) { + const parsed = parseMeshchatMapUri(mapUri); + if (parsed) { + const kind = mapLinkKindFromMessage(content, parsed); + let t = content.trim().replace(mapUri, "").trim(); + t = t + .replace(/^MeshChatX\s+map\s+ping:\s*/i, "") + .replace(/^MeshChatX\s+map:\s*/i, "") + .trim(); + items.mapLink = { uri: mapUri, parsed, kind }; + if (t === "") { + items.isOnlyMapLink = true; + } + } + } + return items; }, async addContact(name, hash, lxmf_address = null, lxst_address = null) { @@ -4486,6 +4360,38 @@ export default { }, }); }, + openMapShareFromParsed(parsed) { + if (!parsed) { + return; + } + const query = { + lat: String(parsed.lat), + lon: String(parsed.lon), + zoom: String(parsed.zoom), + }; + if (parsed.layers) { + query.layers = parsed.layers; + } + if (parsed.label) { + query.label = parsed.label; + } + this.$router.push({ + name: "map", + query, + }); + }, + async copyMapShareUri(uri) { + if (!uri) { + return; + } + try { + await navigator.clipboard.writeText(uri); + ToastUtils.success(this.$t("messages.map_link_copied")); + } catch (e) { + console.error(e); + ToastUtils.error(this.$t("messages.failed_copy_address")); + } + }, isTelemetryOnly(msg) { return isTelemetryOnlyMessage(msg); }, diff --git a/meshchatx/src/frontend/components/tools/RepositoryServerPage.vue b/meshchatx/src/frontend/components/tools/RepositoryServerPage.vue new file mode 100644 index 0000000..4749c7a --- /dev/null +++ b/meshchatx/src/frontend/components/tools/RepositoryServerPage.vue @@ -0,0 +1,491 @@ + + + + + diff --git a/meshchatx/src/frontend/components/tools/ToolsPage.vue b/meshchatx/src/frontend/components/tools/ToolsPage.vue index 97d1235..b7ab6b0 100644 --- a/meshchatx/src/frontend/components/tools/ToolsPage.vue +++ b/meshchatx/src/frontend/components/tools/ToolsPage.vue @@ -195,8 +195,8 @@ export default { route: { name: "propagation-nodes" }, icon: "mailbox", iconBg: "tool-card__icon bg-cyan-50 text-cyan-500 dark:bg-cyan-900/30 dark:text-cyan-200", - title: "Propagation Nodes", - description: "Manage preferred and local propagation nodes with live stats and path checks.", + titleKey: "tools.propagation_nodes.title", + descriptionKey: "tools.propagation_nodes.description", }, { name: "forwarder", @@ -222,6 +222,14 @@ export default { titleKey: "docs.title", descriptionKey: "docs.subtitle", }, + { + name: "repository-server", + route: { name: "repository-server" }, + icon: "package-variant", + iconBg: "tool-card__icon bg-sky-50 text-sky-600 dark:bg-sky-900/30 dark:text-sky-200", + titleKey: "tools.repository_server.title", + descriptionKey: "tools.repository_server.description", + }, { name: "micron-editor", route: { name: "micron-editor" },