mirror of
https://github.com/Quad4-Software/MeshChatX.git
synced 2026-08-18 09:49:09 -04:00
feat: update network visualiser with WebGL rendering options and user preferences
This commit is contained in:
parent
9566e49d1e
commit
cee526b3a8
24 changed files with 308 additions and 38 deletions
40
.github/workflows/ci.yml
vendored
40
.github/workflows/ci.yml
vendored
|
|
@ -9,6 +9,7 @@
|
|||
# actions/setup-python@v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
# actions/upload-artifact@v5.0.0 330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
# actions/download-artifact@v5.0.0 634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
# actions/setup-go@v7.0.0 b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
|
||||
|
||||
name: CI
|
||||
|
||||
|
|
@ -36,6 +37,7 @@ env:
|
|||
NODE_VERSION: "24"
|
||||
UV_VERSION: "0.11.15"
|
||||
PNPM_VERSION: "11.1.2"
|
||||
GO_VERSION: "1.24.x"
|
||||
RNS_REQUIRED_SIGNER: e46112d44649266d71fe2193e00a4710
|
||||
|
||||
jobs:
|
||||
|
|
@ -102,6 +104,44 @@ jobs:
|
|||
- name: Verify workspace clean
|
||||
uses: ./.github/actions/verify-workspace-clean
|
||||
|
||||
visualiser-wasm:
|
||||
name: Visualiser WASM (Go)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
- name: Setup Task
|
||||
run: sh scripts/ci/setup-task.sh 3.49.1
|
||||
|
||||
- name: Run visualiser-wasm tests
|
||||
run: task test:visualiser-wasm
|
||||
|
||||
- name: Build visualiser-wasm artifacts
|
||||
run: task build:visualiser-wasm
|
||||
|
||||
- name: Verify integrity.json stamped
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f meshchatx/src/frontend/public/vendor/visualiser-wasm/integrity.json
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
data = json.loads(Path("meshchatx/src/frontend/public/vendor/visualiser-wasm/integrity.json").read_text())
|
||||
assert data.get("version") == "1.2.0", data
|
||||
assert str(data.get("wasm", "")).startswith("sha384-"), data
|
||||
assert str(data.get("wasmExec", "")).startswith("sha384-"), data
|
||||
print("visualiser-wasm integrity OK", data["version"])
|
||||
PY
|
||||
|
||||
backend-tests:
|
||||
name: Backend tests (Python 3.14) - Shard ${{ matrix.shard }} / 4
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
6
.github/workflows/frontend-build.yml
vendored
6
.github/workflows/frontend-build.yml
vendored
|
|
@ -8,6 +8,7 @@
|
|||
# actions/setup-python@v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
# actions/setup-node@v6.1.0 395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
# actions/upload-artifact@v5.0.0 330a01c490aca151604b8cf639adc76d48f6c5d4
|
||||
# actions/setup-go@v7.0.0 b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
|
||||
|
||||
name: Frontend build
|
||||
|
||||
|
|
@ -90,6 +91,11 @@ jobs:
|
|||
node-version: ${{ inputs.node_version }}
|
||||
pnpm-version: ${{ inputs.pnpm_version }}
|
||||
|
||||
- name: Set up Go (visualiser-wasm)
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e
|
||||
with:
|
||||
go-version: "1.24.x"
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file.
|
|||
- RSM signing and verification for meshchatx.rsm (CI and pre-commit resign)
|
||||
- Notification sound settings
|
||||
- LXMFy 1.6.5 vendor refresh, wasmtime, mutation test tasks
|
||||
- Network visualiser WebGL + WASM renderer (vis-network fallback) and Settings renderer preference
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
BIN
meshchatx.rsm
BIN
meshchatx.rsm
Binary file not shown.
|
|
@ -95,6 +95,7 @@ import {
|
|||
persistVisualiserLiveLayout,
|
||||
VISUALISER_DISPLAY_PREFS_CHANGED,
|
||||
} from "../../js/settings/settingsVisualiserPrefs.js";
|
||||
import ToastUtils from "../../js/ToastUtils";
|
||||
|
||||
const HOP_MAX_FILTER_STORAGE_KEY = "meshchatx.visualiser.maxHops";
|
||||
|
||||
|
|
@ -186,6 +187,7 @@ export default {
|
|||
isUpdating: false,
|
||||
isLoading: false,
|
||||
enablePhysics: displayPrefs.enablePhysics,
|
||||
preferredRenderer: displayPrefs.renderer || "auto",
|
||||
showDisabledInterfaces: displayPrefs.showDisabledInterfaces,
|
||||
showDiscoveredInterfaces: displayPrefs.showDiscoveredInterfaces,
|
||||
loadingStatus: "Initializing...",
|
||||
|
|
@ -366,9 +368,14 @@ export default {
|
|||
this.isShowingControls = false;
|
||||
}
|
||||
|
||||
this._visualiserPrefsHandler = () => {
|
||||
this._visualiserPrefsHandler = async () => {
|
||||
const prevRenderer = this.preferredRenderer;
|
||||
this.loadVisualiserDisplayPrefs();
|
||||
this.applyBatterySaverVisualiserPrefs();
|
||||
if (this.preferredRenderer !== prevRenderer) {
|
||||
await this.reinitRenderer();
|
||||
return;
|
||||
}
|
||||
if (this.hasRenderer) {
|
||||
this.processVisualization();
|
||||
}
|
||||
|
|
@ -805,6 +812,54 @@ export default {
|
|||
this.showDiscoveredInterfaces = p.showDiscoveredInterfaces;
|
||||
this.enablePhysics = p.enablePhysics;
|
||||
this.autoReload = p.autoReload;
|
||||
this.preferredRenderer = p.renderer || "auto";
|
||||
},
|
||||
destroyActiveRenderer() {
|
||||
this.hoverTooltip = null;
|
||||
if (this.webglEngine) {
|
||||
this.webglEngine.destroy();
|
||||
this.webglEngine = null;
|
||||
}
|
||||
if (this.network) {
|
||||
this.network.destroy();
|
||||
this.network = null;
|
||||
}
|
||||
try {
|
||||
this.nodes.clear();
|
||||
this.edges.clear();
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
this.rendererMode = "vis";
|
||||
},
|
||||
async reinitRenderer() {
|
||||
this.destroyActiveRenderer();
|
||||
await this.init({ skipWarm: true });
|
||||
},
|
||||
async tryStartWebGL() {
|
||||
const canvas = this.$refs.webglCanvas;
|
||||
if (!canvas || !canUseVisualiserWebGL()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
this.webglEngine = createVisualiserWebGLEngine(canvas, {
|
||||
getLiveLayout: () => this.enablePhysics === true,
|
||||
isDark: () => document.documentElement.classList.contains("dark"),
|
||||
onNodeActivate: (id, meta) => this.onWebGLNodeActivate(id, meta),
|
||||
onHover: (id, meta, x, y) => this.onWebGLHover(id, meta, x, y),
|
||||
});
|
||||
this.rendererMode = "webgl";
|
||||
this.engineMode = "webgl";
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.warn("WebGL visualiser failed:", e);
|
||||
if (this.webglEngine) {
|
||||
this.webglEngine.destroy();
|
||||
this.webglEngine = null;
|
||||
}
|
||||
this.rendererMode = "vis";
|
||||
return false;
|
||||
}
|
||||
},
|
||||
refreshPhysicsEnabled() {
|
||||
if (this.webglEngine) {
|
||||
|
|
@ -892,30 +947,26 @@ export default {
|
|||
posById[id] = { x: v.x, y: v.y };
|
||||
return v;
|
||||
},
|
||||
async init() {
|
||||
await warmVisualiserWasm();
|
||||
const canvas = this.$refs.webglCanvas;
|
||||
if (canvas && canUseVisualiserWebGL(canvas)) {
|
||||
try {
|
||||
this.webglEngine = createVisualiserWebGLEngine(canvas, {
|
||||
getLiveLayout: () => this.enablePhysics === true,
|
||||
isDark: () => document.documentElement.classList.contains("dark"),
|
||||
onNodeActivate: (id, meta) => this.onWebGLNodeActivate(id, meta),
|
||||
onHover: (id, meta, x, y) => this.onWebGLHover(id, meta, x, y),
|
||||
});
|
||||
this.rendererMode = "webgl";
|
||||
this.engineMode = "webgl";
|
||||
await this.manualUpdate();
|
||||
this.restartAutoReloadInterval();
|
||||
return;
|
||||
} catch (e) {
|
||||
console.warn("WebGL visualiser failed, falling back to vis-network:", e);
|
||||
if (this.webglEngine) {
|
||||
this.webglEngine.destroy();
|
||||
this.webglEngine = null;
|
||||
}
|
||||
this.rendererMode = "vis";
|
||||
}
|
||||
async init(opts = {}) {
|
||||
if (!opts.skipWarm) {
|
||||
await warmVisualiserWasm();
|
||||
}
|
||||
const preferred = this.preferredRenderer || loadVisualiserDisplayPrefs().renderer || "auto";
|
||||
|
||||
if (preferred === "vis") {
|
||||
await this.initVisNetwork();
|
||||
return;
|
||||
}
|
||||
|
||||
const started = await this.tryStartWebGL();
|
||||
if (started) {
|
||||
await this.manualUpdate();
|
||||
this.restartAutoReloadInterval();
|
||||
return;
|
||||
}
|
||||
|
||||
if (preferred === "webgl") {
|
||||
ToastUtils.warning(this.$t("visualiser.renderer_webgl_unavailable"));
|
||||
}
|
||||
await this.initVisNetwork();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1896,6 +1896,24 @@
|
|||
</div>
|
||||
</header>
|
||||
<div class="settings-section__body space-y-4">
|
||||
<div class="space-y-2">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
{{ $t("visualiser.renderer_title") }}
|
||||
</div>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">
|
||||
{{ $t("visualiser.renderer_desc") }}
|
||||
</p>
|
||||
<select
|
||||
id="settings-visualiser-renderer"
|
||||
v-model="visualiserRenderer"
|
||||
class="input-field"
|
||||
@change="onVisualiserRendererChange"
|
||||
>
|
||||
<option value="auto">{{ $t("visualiser.renderer_option_auto") }}</option>
|
||||
<option value="webgl">{{ $t("visualiser.renderer_option_webgl") }}</option>
|
||||
<option value="vis">{{ $t("visualiser.renderer_option_vis") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<label class="setting-toggle">
|
||||
<Toggle
|
||||
id="settings-visualiser-offline"
|
||||
|
|
@ -3581,6 +3599,7 @@ import {
|
|||
loadVisualiserDisplayPrefs,
|
||||
persistVisualiserShowDisabled,
|
||||
persistVisualiserShowDiscovered,
|
||||
persistVisualiserRenderer,
|
||||
} from "../../js/settings/settingsVisualiserPrefs";
|
||||
import { loadBatterySaverPrefs, saveBatterySaverPrefs } from "../../js/settings/batterySaverPrefs.js";
|
||||
import {
|
||||
|
|
@ -3752,6 +3771,7 @@ export default {
|
|||
gifImportReplaceDuplicates: false,
|
||||
visualiserShowDisabledInterfaces: false,
|
||||
visualiserShowDiscoveredInterfaces: false,
|
||||
visualiserRenderer: "auto",
|
||||
batterySaver: loadBatterySaverPrefs(),
|
||||
batteryInterfaceRows: [],
|
||||
batteryBitrateBusy: false,
|
||||
|
|
@ -4307,6 +4327,7 @@ export default {
|
|||
const p = loadVisualiserDisplayPrefs();
|
||||
this.visualiserShowDisabledInterfaces = p.showDisabledInterfaces;
|
||||
this.visualiserShowDiscoveredInterfaces = p.showDiscoveredInterfaces;
|
||||
this.visualiserRenderer = p.renderer || "auto";
|
||||
},
|
||||
onVisualiserShowDisabledChange(val) {
|
||||
this.visualiserShowDisabledInterfaces = val;
|
||||
|
|
@ -4316,6 +4337,9 @@ export default {
|
|||
this.visualiserShowDiscoveredInterfaces = val;
|
||||
persistVisualiserShowDiscovered(val);
|
||||
},
|
||||
onVisualiserRendererChange() {
|
||||
persistVisualiserRenderer(this.visualiserRenderer);
|
||||
},
|
||||
async getTrustedTelemetryPeers() {
|
||||
try {
|
||||
const response = await window.api.get("/api/v1/telemetry/trusted-peers");
|
||||
|
|
|
|||
|
|
@ -6,9 +6,14 @@ const KEY_DISABLED = "meshchatx.visualiser.showDisabledInterfaces";
|
|||
const KEY_DISCOVERED = "meshchatx.visualiser.showDiscoveredInterfaces";
|
||||
const KEY_LIVE_LAYOUT = "meshchatx.visualiser.enablePhysics";
|
||||
const KEY_AUTO_RELOAD = "meshchatx.visualiser.autoReload";
|
||||
const KEY_RENDERER = "meshchatx.visualiser.renderer";
|
||||
|
||||
export const VISUALISER_DISPLAY_PREFS_CHANGED = "visualiser-display-prefs-changed";
|
||||
|
||||
/** @typedef {"auto" | "webgl" | "vis"} VisualiserRendererPref */
|
||||
|
||||
export const VISUALISER_RENDERER_OPTIONS = ["auto", "webgl", "vis"];
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {boolean} defaultValue
|
||||
|
|
@ -42,12 +47,38 @@ function writeBool(key, val) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown} raw
|
||||
* @returns {VisualiserRendererPref}
|
||||
*/
|
||||
export function normalizeVisualiserRenderer(raw) {
|
||||
if (raw === "webgl" || raw === "vis" || raw === "auto") {
|
||||
return raw;
|
||||
}
|
||||
return "auto";
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {VisualiserRendererPref}
|
||||
*/
|
||||
function readRenderer() {
|
||||
try {
|
||||
if (typeof localStorage === "undefined") {
|
||||
return "auto";
|
||||
}
|
||||
return normalizeVisualiserRenderer(localStorage.getItem(KEY_RENDERER));
|
||||
} catch {
|
||||
return "auto";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {{
|
||||
* showDisabledInterfaces: boolean,
|
||||
* showDiscoveredInterfaces: boolean,
|
||||
* enablePhysics: boolean,
|
||||
* autoReload: boolean,
|
||||
* renderer: VisualiserRendererPref,
|
||||
* }}
|
||||
*/
|
||||
export function loadVisualiserDisplayPrefs() {
|
||||
|
|
@ -57,6 +88,7 @@ export function loadVisualiserDisplayPrefs() {
|
|||
// Live Layout defaults on when never set.
|
||||
enablePhysics: readBool(KEY_LIVE_LAYOUT, true),
|
||||
autoReload: readBool(KEY_AUTO_RELOAD, false),
|
||||
renderer: readRenderer(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -97,3 +129,21 @@ export function persistVisualiserAutoReload(val, opts = {}) {
|
|||
GlobalEmitter.emit(VISUALISER_DISPLAY_PREFS_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown} val
|
||||
* @param {{ emit?: boolean }} [opts]
|
||||
*/
|
||||
export function persistVisualiserRenderer(val, opts = {}) {
|
||||
const next = normalizeVisualiserRenderer(val);
|
||||
try {
|
||||
if (typeof localStorage !== "undefined") {
|
||||
localStorage.setItem(KEY_RENDERER, next);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
if (opts.emit !== false) {
|
||||
GlobalEmitter.emit(VISUALISER_DISPLAY_PREFS_CHANGED);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3367,7 +3367,13 @@
|
|||
"search_nodes_placeholder": "Knoten suchen ({count})...",
|
||||
"clear_search": "Suche leeren",
|
||||
"engine_webgl": "WebGL",
|
||||
"engine_webgl_hint": "Graph, Layout und Zeichenpuffer laufen in Go-WebAssembly. Pixelzeichnung mit WebGL2"
|
||||
"engine_webgl_hint": "Graph, Layout und Zeichenpuffer laufen in Go-WebAssembly. Pixelzeichnung mit WebGL2",
|
||||
"renderer_title": "Renderer",
|
||||
"renderer_desc": "Auto bevorzugt WebGL mit WASM und fällt sonst auf vis-network zurück. Erzwingen Sie einen Pfad zum Debuggen oder für ältere GPUs.",
|
||||
"renderer_option_auto": "Auto (WebGL wenn verfügbar)",
|
||||
"renderer_option_webgl": "WebGL + WASM",
|
||||
"renderer_option_vis": "vis-network (Canvas)",
|
||||
"renderer_webgl_unavailable": "WebGL-Renderer nicht verfügbar. Nutze vis-network-Fallback."
|
||||
},
|
||||
"banishment": {
|
||||
"title": "Verbannt",
|
||||
|
|
|
|||
|
|
@ -1895,6 +1895,12 @@
|
|||
"visualiser": {
|
||||
"title": "Network Visualiser",
|
||||
"description": "Graph of discovered nodes and links.",
|
||||
"renderer_title": "Renderer",
|
||||
"renderer_desc": "Auto prefers WebGL with WASM when available, then falls back to vis-network. Force a path for debugging or older GPUs.",
|
||||
"renderer_option_auto": "Auto (WebGL when available)",
|
||||
"renderer_option_webgl": "WebGL + WASM",
|
||||
"renderer_option_vis": "vis-network (canvas)",
|
||||
"renderer_webgl_unavailable": "WebGL renderer unavailable. Using vis-network fallback.",
|
||||
"reticulum_mesh": "Reticulum Mesh",
|
||||
"network_visualizer": "Network Visualizer",
|
||||
"node_info": "Node Info",
|
||||
|
|
|
|||
|
|
@ -1963,7 +1963,13 @@
|
|||
"search_nodes_placeholder": "Buscar nodos ({count})...",
|
||||
"clear_search": "Borrar busqueda",
|
||||
"engine_webgl": "WebGL",
|
||||
"engine_webgl_hint": "Grafo, layout y buffers de dibujo en Go WebAssembly. Pixels con WebGL2"
|
||||
"engine_webgl_hint": "Grafo, layout y buffers de dibujo en Go WebAssembly. Pixels con WebGL2",
|
||||
"renderer_title": "Renderizador",
|
||||
"renderer_desc": "Auto prefiere WebGL con WASM cuando está disponible y luego usa vis-network. Fuerza una ruta para depurar o GPUs antiguas.",
|
||||
"renderer_option_auto": "Auto (WebGL si está disponible)",
|
||||
"renderer_option_webgl": "WebGL + WASM",
|
||||
"renderer_option_vis": "vis-network (canvas)",
|
||||
"renderer_webgl_unavailable": "Renderizador WebGL no disponible. Usando reserva vis-network."
|
||||
},
|
||||
"banishment": {
|
||||
"title": "Desterrados",
|
||||
|
|
|
|||
|
|
@ -1963,7 +1963,13 @@
|
|||
"search_nodes_placeholder": "Hae solmuja ({count})...",
|
||||
"clear_search": "Tyhjennä haku",
|
||||
"engine_webgl": "WebGL",
|
||||
"engine_webgl_hint": "Kaavio, asettelu ja piirtopuskurit Go WebAssemblyssa. Pikselit WebGL2:lla"
|
||||
"engine_webgl_hint": "Kaavio, asettelu ja piirtopuskurit Go WebAssemblyssa. Pikselit WebGL2:lla",
|
||||
"renderer_title": "Renderöijä",
|
||||
"renderer_desc": "Auto suosii WebGL+WASM:ia kun saatavilla ja palaa sitten vis-networkiin. Pakota polku debuggaukseen tai vanhoille GPU:ille.",
|
||||
"renderer_option_auto": "Auto (WebGL jos saatavilla)",
|
||||
"renderer_option_webgl": "WebGL + WASM",
|
||||
"renderer_option_vis": "vis-network (canvas)",
|
||||
"renderer_webgl_unavailable": "WebGL-renderöijä ei ole saatavilla. Käytetään vis-network-varausta."
|
||||
},
|
||||
"banishment": {
|
||||
"title": "Karkotus",
|
||||
|
|
|
|||
|
|
@ -1963,7 +1963,13 @@
|
|||
"search_nodes_placeholder": "Rechercher des noeuds ({count})...",
|
||||
"clear_search": "Effacer la recherche",
|
||||
"engine_webgl": "WebGL",
|
||||
"engine_webgl_hint": "Graphe, layout et buffers de dessin en Go WebAssembly. Pixels via WebGL2"
|
||||
"engine_webgl_hint": "Graphe, layout et buffers de dessin en Go WebAssembly. Pixels via WebGL2",
|
||||
"renderer_title": "Moteur de rendu",
|
||||
"renderer_desc": "Auto préfère WebGL avec WASM puis revient à vis-network. Forcez un chemin pour le débogage ou les anciens GPU.",
|
||||
"renderer_option_auto": "Auto (WebGL si disponible)",
|
||||
"renderer_option_webgl": "WebGL + WASM",
|
||||
"renderer_option_vis": "vis-network (canvas)",
|
||||
"renderer_webgl_unavailable": "Rendu WebGL indisponible. Repli sur vis-network."
|
||||
},
|
||||
"banishment": {
|
||||
"title": "Interdit",
|
||||
|
|
|
|||
|
|
@ -2015,7 +2015,13 @@
|
|||
"search_nodes_placeholder": "Cerca nodi ({count})...",
|
||||
"clear_search": "Cancella ricerca",
|
||||
"engine_webgl": "WebGL",
|
||||
"engine_webgl_hint": "Grafo, layout e buffer di disegno in Go WebAssembly. Pixel con WebGL2"
|
||||
"engine_webgl_hint": "Grafo, layout e buffer di disegno in Go WebAssembly. Pixel con WebGL2",
|
||||
"renderer_title": "Renderer",
|
||||
"renderer_desc": "Auto preferisce WebGL con WASM quando disponibile, poi passa a vis-network. Forza un percorso per debug o GPU datate.",
|
||||
"renderer_option_auto": "Auto (WebGL se disponibile)",
|
||||
"renderer_option_webgl": "WebGL + WASM",
|
||||
"renderer_option_vis": "vis-network (canvas)",
|
||||
"renderer_webgl_unavailable": "Renderer WebGL non disponibile. Uso fallback vis-network."
|
||||
},
|
||||
"banishment": {
|
||||
"title": "Esiliati",
|
||||
|
|
|
|||
|
|
@ -1963,7 +1963,13 @@
|
|||
"search_nodes_placeholder": "Zoek knooppunten ({count})...",
|
||||
"clear_search": "Zoekopdracht wissen",
|
||||
"engine_webgl": "WebGL",
|
||||
"engine_webgl_hint": "Grafiek, layout en tekenbuffers in Go WebAssembly. Pixels met WebGL2"
|
||||
"engine_webgl_hint": "Grafiek, layout en tekenbuffers in Go WebAssembly. Pixels met WebGL2",
|
||||
"renderer_title": "Renderer",
|
||||
"renderer_desc": "Auto prefereert WebGL met WASM indien beschikbaar, anders vis-network. Forceer een pad voor debug of oudere GPU's.",
|
||||
"renderer_option_auto": "Auto (WebGL indien beschikbaar)",
|
||||
"renderer_option_webgl": "WebGL + WASM",
|
||||
"renderer_option_vis": "vis-network (canvas)",
|
||||
"renderer_webgl_unavailable": "WebGL-renderer niet beschikbaar. Gebruik vis-network-fallback."
|
||||
},
|
||||
"banishment": {
|
||||
"title": "Verbannen",
|
||||
|
|
|
|||
|
|
@ -3367,7 +3367,13 @@
|
|||
"search_nodes_placeholder": "Поиск узлов ({count})...",
|
||||
"clear_search": "Очистить поиск",
|
||||
"engine_webgl": "WebGL",
|
||||
"engine_webgl_hint": "Граф, раскладка и буферы отрисовки в Go WebAssembly. Пиксели через WebGL2"
|
||||
"engine_webgl_hint": "Граф, раскладка и буферы отрисовки в Go WebAssembly. Пиксели через WebGL2",
|
||||
"renderer_title": "Рендерер",
|
||||
"renderer_desc": "Auto предпочитает WebGL с WASM при наличии, иначе vis-network. Принудительный выбор для отладки или старых GPU.",
|
||||
"renderer_option_auto": "Auto (WebGL если доступен)",
|
||||
"renderer_option_webgl": "WebGL + WASM",
|
||||
"renderer_option_vis": "vis-network (canvas)",
|
||||
"renderer_webgl_unavailable": "WebGL-рендерер недоступен. Используется запасной vis-network."
|
||||
},
|
||||
"banishment": {
|
||||
"title": "Забаненные",
|
||||
|
|
|
|||
|
|
@ -1963,7 +1963,13 @@
|
|||
"search_nodes_placeholder": "搜索节点 ({count})...",
|
||||
"clear_search": "清除搜索",
|
||||
"engine_webgl": "WebGL",
|
||||
"engine_webgl_hint": "图构建、布局与绘制缓冲在 Go WebAssembly 中运行。像素由 WebGL2 绘制"
|
||||
"engine_webgl_hint": "图构建、布局与绘制缓冲在 Go WebAssembly 中运行。像素由 WebGL2 绘制",
|
||||
"renderer_title": "渲染器",
|
||||
"renderer_desc": "自动在可用时优先使用 WebGL+WASM,否则回退到 vis-network。可强制选择以便调试或兼容旧 GPU。",
|
||||
"renderer_option_auto": "自动(可用时使用 WebGL)",
|
||||
"renderer_option_webgl": "WebGL + WASM",
|
||||
"renderer_option_vis": "vis-network(canvas)",
|
||||
"renderer_webgl_unavailable": "WebGL 渲染器不可用。已回退到 vis-network。"
|
||||
},
|
||||
"banishment": {
|
||||
"title": "放逐",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"version": "1.0.0",
|
||||
"wasm": "sha384-ERsG9mb3L8kjxDEvn36T/mdj3Cz4zPA7U/hNo6Nppt9sjE0VAuNT3XjHsFDx9P/b",
|
||||
"version": "1.2.0",
|
||||
"wasm": "sha384-kx5+sGvflCkoO65dt5IcMJVB/8y85IAwT6zUbAKjha1XK/8p3tMp+QFwX5hd2DYN",
|
||||
"wasmExec": "sha384-PWCs+V4BDf9yY1yjkD/p+9xNEs4iEbuvq+HezAOJiY3XL5GI6VyJXMsvnjiwNbce",
|
||||
"wasmExecSource": "/usr/lib/go/lib/wasm/wasm_exec.js"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const GO_MOD_DIR = path.join(REPO_ROOT, "visualiser-wasm");
|
|||
const OUT_DIR = path.join(REPO_ROOT, "meshchatx", "src", "frontend", "public", "vendor", "visualiser-wasm");
|
||||
const WASM_NAME = "visualiser.wasm";
|
||||
const EXEC_NAME = "wasm_exec.js";
|
||||
const VERSION = "1.0.0";
|
||||
const VERSION = "1.2.0";
|
||||
|
||||
function computeSri(buf) {
|
||||
return `sha384-${crypto.createHash("sha384").update(buf).digest("base64")}`;
|
||||
|
|
|
|||
|
|
@ -908,6 +908,10 @@
|
|||
"method": "POST",
|
||||
"path": "/api/v1/reticulum/interfaces/add"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/reticulum/interfaces/bitrates"
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/api/v1/reticulum/interfaces/delete"
|
||||
|
|
|
|||
|
|
@ -60,12 +60,14 @@ test.describe("Network visualiser", () => {
|
|||
await prepareE2eSession(request);
|
||||
});
|
||||
|
||||
test("shows mesh header, graph container, and node search", async ({ page }) => {
|
||||
test("shows mesh header, graph hosts, and node search", async ({ page }) => {
|
||||
await page.goto("/#/network-visualiser");
|
||||
await expect(page).toHaveURL(/#\/network-visualiser/);
|
||||
await expect(page.getByText("Reticulum Mesh", { exact: true })).toBeVisible({ timeout: 30000 });
|
||||
await expect(page.locator("#network")).toBeAttached();
|
||||
await expect(page.locator("#network-webgl")).toBeAttached();
|
||||
await expect(page.getByPlaceholder(/Search nodes \(\d+\)/)).toBeVisible({ timeout: 30000 });
|
||||
await expect(page.getByText(/WebGL|WASM|JS fallback|Checking/)).toBeVisible({ timeout: 30000 });
|
||||
});
|
||||
|
||||
test("collapses and expands control panel via header", async ({ page }) => {
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ describe("NetworkVisualiser.vue", () => {
|
|||
"visualiser.nodes": "Nodes",
|
||||
"visualiser.links": "Links",
|
||||
"visualiser.engine_wasm": "WASM",
|
||||
"visualiser.engine_webgl": "WebGL",
|
||||
"visualiser.engine_fallback": "JS fallback",
|
||||
"visualiser.engine_checking": "Checking",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -542,6 +542,14 @@ describe("SettingsPage — visualiser display prefs (localStorage + emitter)", (
|
|||
expect(GlobalEmitter.emit).toHaveBeenCalledWith("visualiser-display-prefs-changed");
|
||||
});
|
||||
|
||||
it("onVisualiserRendererChange persists renderer and emits", async () => {
|
||||
const w = await mountSettingsPage(api);
|
||||
w.vm.visualiserRenderer = "webgl";
|
||||
w.vm.onVisualiserRendererChange();
|
||||
expect(localStorage.getItem("meshchatx.visualiser.renderer")).toBe("webgl");
|
||||
expect(GlobalEmitter.emit).toHaveBeenCalledWith("visualiser-display-prefs-changed");
|
||||
});
|
||||
|
||||
it("onDetailedOutboundSendStatusChange updates GlobalState and localStorage", async () => {
|
||||
localStorage.removeItem("meshchatx_detailed_outbound_send_status");
|
||||
const w = await mountSettingsPage(api);
|
||||
|
|
|
|||
|
|
@ -260,4 +260,21 @@ describe("behavior contracts: network visualiser performance", () => {
|
|||
expect(perf).toContain("dedupeIconQueueEntries");
|
||||
expect(perf).toContain("pickAdaptiveFetchConcurrency");
|
||||
});
|
||||
|
||||
it("prefers WebGL with WASM scene and keeps vis-network fallback", () => {
|
||||
const src = readSource("meshchatx/src/frontend/components/network-visualiser/NetworkVisualiser.vue");
|
||||
expect(src).toContain("tryStartWebGL");
|
||||
expect(src).toContain("initVisNetwork");
|
||||
expect(src).toContain("preferredRenderer");
|
||||
expect(src).toContain('name: "nomadnetwork"');
|
||||
expect(src).toContain("openAnnounceDestination");
|
||||
const engine = readSource("meshchatx/src/frontend/js/networkVisualiserWebGLEngine.js");
|
||||
expect(engine).toContain("meshchatxVisualiserSceneSet");
|
||||
expect(engine).toContain("createVisualiserWebGLEngine");
|
||||
const prefs = readSource("meshchatx/src/frontend/js/settings/settingsVisualiserPrefs.js");
|
||||
expect(prefs).toContain("persistVisualiserRenderer");
|
||||
expect(prefs).toContain('"auto"');
|
||||
expect(prefs).toContain('"webgl"');
|
||||
expect(prefs).toContain('"vis"');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import {
|
|||
persistVisualiserAutoReload,
|
||||
persistVisualiserShowDisabled,
|
||||
persistVisualiserShowDiscovered,
|
||||
persistVisualiserRenderer,
|
||||
normalizeVisualiserRenderer,
|
||||
} from "@/js/settings/settingsVisualiserPrefs.js";
|
||||
|
||||
describe("settingsVisualiserPrefs", () => {
|
||||
|
|
@ -12,12 +14,13 @@ describe("settingsVisualiserPrefs", () => {
|
|||
localStorage.clear();
|
||||
});
|
||||
|
||||
it("defaults live layout on and auto-reload off", () => {
|
||||
it("defaults live layout on, auto-reload off, renderer auto", () => {
|
||||
expect(loadVisualiserDisplayPrefs()).toEqual({
|
||||
showDisabledInterfaces: false,
|
||||
showDiscoveredInterfaces: false,
|
||||
enablePhysics: true,
|
||||
autoReload: false,
|
||||
renderer: "auto",
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -31,6 +34,7 @@ describe("settingsVisualiserPrefs", () => {
|
|||
showDiscoveredInterfaces: true,
|
||||
enablePhysics: false,
|
||||
autoReload: true,
|
||||
renderer: "auto",
|
||||
});
|
||||
persistVisualiserLiveLayout(true);
|
||||
expect(loadVisualiserDisplayPrefs().enablePhysics).toBe(true);
|
||||
|
|
@ -42,4 +46,12 @@ describe("settingsVisualiserPrefs", () => {
|
|||
persistVisualiserAutoReload(true, { emit: false });
|
||||
expect(loadVisualiserDisplayPrefs().autoReload).toBe(true);
|
||||
});
|
||||
|
||||
it("normalizes and persists renderer preference", () => {
|
||||
expect(normalizeVisualiserRenderer("nope")).toBe("auto");
|
||||
persistVisualiserRenderer("webgl");
|
||||
expect(loadVisualiserDisplayPrefs().renderer).toBe("webgl");
|
||||
persistVisualiserRenderer("vis", { emit: false });
|
||||
expect(loadVisualiserDisplayPrefs().renderer).toBe("vis");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue