From 9ae73670eff8de2635a1e4c80d2679223777fb27 Mon Sep 17 00:00:00 2001 From: Alan Garny Date: Sun, 18 Jan 2026 13:11:06 +1300 Subject: [PATCH 1/4] New version. --- package.json | 2 +- src/renderer/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fa162038..6873dd04 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "url": "git+https://github.com/opencor/webapp.git" }, "type": "module", - "version": "0.20260117.0", + "version": "0.20260119.0", "scripts": { "archive:web": "bun src/renderer/scripts/archive.web.js", "build": "electron-vite build", diff --git a/src/renderer/package.json b/src/renderer/package.json index 969358ae..d2934bbc 100644 --- a/src/renderer/package.json +++ b/src/renderer/package.json @@ -39,7 +39,7 @@ }, "./style.css": "./dist/opencor.css" }, - "version": "0.20260117.0", + "version": "0.20260119.0", "scripts": { "build": "vite build", "build:lib": "vite build --config vite.lib.config.ts && cp index.d.ts dist/index.d.ts", From b887c745a77ebd346854c970cef29136775739a1 Mon Sep 17 00:00:00 2001 From: Alan Garny Date: Sun, 18 Jan 2026 23:04:27 +1300 Subject: [PATCH 2/4] Simulation Experiment view: handle the live and additional runs together. This removes some duplicated code. --- .../views/SimulationExperimentView.vue | 149 +++++++----------- 1 file changed, 55 insertions(+), 94 deletions(-) diff --git a/src/renderer/src/components/views/SimulationExperimentView.vue b/src/renderer/src/components/views/SimulationExperimentView.vue index 8a8c3f89..c954adf2 100644 --- a/src/renderer/src/components/views/SimulationExperimentView.vue +++ b/src/renderer/src/components/views/SimulationExperimentView.vue @@ -114,64 +114,30 @@ />
-
-
-
- Live run -
-
- -
- -
-
-
-
-
- Run #{{ index + 1 }} + {{ run.isLiveRun ? 'Live run' : `Run #${index}` }}
-
+
There are no (additional) runs.
Click "Add run" to add one.
@@ -250,6 +217,7 @@ interface ISimulationRun { data: IGraphPanelData[]; color: string; tooltip: string; + isLiveRun: boolean; } const props = defineProps<{ @@ -411,14 +379,20 @@ const interactiveShowInput = vue.ref( interactiveModeAvailable.value ? props.uiJson.input.map((input: locApi.IUiJsonInput) => input.visible ?? 'true') : [] ); const interactiveIdToInfo: Record = {}; -const interactiveRuns = vue.ref([]); -const interactiveLiveRunColor = vue.ref(DefaultGraphPanelWidgetColor); -const interactiveLiveRunVisible = vue.ref(true); -const interactiveLiveRunColorPopover = vue.ref(); +const interactiveRuns = vue.ref([ + { + inputParameters: {}, + visible: true, + data: [], + color: DefaultGraphPanelWidgetColor, + tooltip: '', + isLiveRun: true + } +]); const interactiveRunColorPopoverIndex = vue.ref(-1); -const interactiveRunPopoverRefs = vue.ref>({}); +const interactiveRunColorPopoverRefs = vue.ref>({}); const interactiveCompData = vue.computed(() => { - // Combine the live data with the data from the visible runs. + // Combine the live data with the data from the additional runs. const res: IGraphPanelData[] = []; @@ -427,36 +401,31 @@ const interactiveCompData = vue.computed(() => { interactiveDataIndex < (interactiveData.value.length || 0); ++interactiveDataIndex ) { - const traces: IGraphPanelPlotTrace[] = interactiveLiveRunVisible.value - ? (interactiveData.value[interactiveDataIndex]?.traces ?? []).map((trace, traceIndex) => { - return { - ...trace, - name: trace.name + (interactiveRuns.value.length ? ` [Live]` : ''), - color: - GraphPanelWidgetPalette[ - (GraphPanelWidgetPalette.indexOf(interactiveLiveRunColor.value) + traceIndex) % - GraphPanelWidgetPalette.length - ] ?? DefaultGraphPanelWidgetColor, - zorder: 1 - }; - }) - : []; + const traces: IGraphPanelPlotTrace[] = []; interactiveRuns.value.forEach((interactiveRun: ISimulationRun, runIndex: number) => { - if (interactiveRun.visible) { - const runTraces = (interactiveRun.data[interactiveDataIndex]?.traces ?? []).map((trace, traceIndex) => { - return { - ...trace, - name: trace.name + (interactiveRuns.value.length ? ` [#${runIndex + 1}]` : ''), - color: - GraphPanelWidgetPalette[ - (GraphPanelWidgetPalette.indexOf(interactiveRun.color) + traceIndex) % GraphPanelWidgetPalette.length - ] ?? DefaultGraphPanelWidgetColor - }; - }); - - traces.push(...runTraces); + if (!interactiveRun.visible) { + return; } + + const data = interactiveRun.isLiveRun + ? interactiveData.value[interactiveDataIndex] + : interactiveRun.data[interactiveDataIndex]; + const runTraces = (data?.traces ?? []).map((trace, traceIndex) => { + return { + ...trace, + name: + trace.name + + (interactiveRuns.value.length === 1 ? '' : interactiveRun.isLiveRun ? ' [Live]' : ` [#${runIndex}]`), + color: + GraphPanelWidgetPalette[ + (GraphPanelWidgetPalette.indexOf(interactiveRun.color) + traceIndex) % GraphPanelWidgetPalette.length + ] ?? DefaultGraphPanelWidgetColor, + zorder: interactiveRun.isLiveRun ? 1 : undefined + }; + }); + + traces.push(...runTraces); }); res.push({ @@ -693,9 +662,8 @@ function onAddRun(): void { // Determine the colour (of the first trace) by using the next unused colour in the palette unless all the colours // have already been used. - const usedColors = new Set([interactiveLiveRunColor.value, ...interactiveRuns.value.map((run) => run.color)]); - const interactiveRun = interactiveRuns.value.length ? interactiveRuns.value[interactiveRuns.value.length - 1] : null; - const lastColor = interactiveRun ? interactiveRun.color : interactiveLiveRunColor.value; + const usedColors = new Set(interactiveRuns.value.map((run) => run.color)); + const lastColor = interactiveRuns.value[interactiveRuns.value.length - 1]?.color ?? DefaultGraphPanelWidgetColor; const lastColorIndex = GraphPanelWidgetPalette.indexOf(lastColor); let color: string = DefaultGraphPanelWidgetColor; @@ -716,7 +684,8 @@ function onAddRun(): void { visible: true, data: interactiveData.value, color, - tooltip + tooltip, + isLiveRun: false }); } @@ -727,9 +696,9 @@ function onRemoveRun(index: number): void { } function onRemoveAllRuns(): void { - // Remove all the runs. + // Remove all the runs except the live run. - interactiveRuns.value = []; + interactiveRuns.value.splice(1); } function onToggleRun(index: number): void { @@ -742,12 +711,6 @@ function onToggleRun(index: number): void { } } -function onLiveRunColorChange(color: string) { - interactiveLiveRunColor.value = color; - - interactiveLiveRunColorPopover.value.hide(); -} - function onRunColorChange(index: number, color: string) { const interactiveRun = interactiveRuns.value[index]; @@ -767,13 +730,13 @@ function onToggleRunColorPopover(index: number, event: MouseEvent) { vue.nextTick(() => { // Note: we do this in a next tick to ensure that the reference is available. - interactiveRunPopoverRefs.value[index]?.toggle(event); + interactiveRunColorPopoverRefs.value[index]?.toggle(event); }); } } function closeRunColorPopover(index: number) { - interactiveRunPopoverRefs.value[index]?.hide(); + interactiveRunColorPopoverRefs.value[index]?.hide(); interactiveRunColorPopoverIndex.value = -1; } @@ -845,8 +808,6 @@ vue.onMounted(() => { () => windowIsFocused.value, (isFocused) => { if (!isFocused) { - interactiveLiveRunColorPopover.value?.hide(); - if (interactiveRunColorPopoverIndex.value !== -1) { closeRunColorPopover(interactiveRunColorPopoverIndex.value); } From 2960bd31e477cfb5b1210cee719250de78dea395 Mon Sep 17 00:00:00 2001 From: Alan Garny Date: Sun, 18 Jan 2026 23:16:23 +1300 Subject: [PATCH 3/4] Simulation Experiment view: various improvements when it comes to the multiple run support. - Highlight the selected run colour with a thick border instead of a tick. - Keep the colour popover open so that we can test different colours in one go. - Fixed the width of the show/hide and remove buttons. --- .../views/SimulationExperimentView.vue | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/renderer/src/components/views/SimulationExperimentView.vue b/src/renderer/src/components/views/SimulationExperimentView.vue index c954adf2..35733c49 100644 --- a/src/renderer/src/components/views/SimulationExperimentView.vue +++ b/src/renderer/src/components/views/SimulationExperimentView.vue @@ -98,28 +98,28 @@
-
-
-
+
-
-
@@ -716,29 +716,20 @@ function onRunColorChange(index: number, color: string) { if (interactiveRun) { interactiveRun.color = color; - - closeRunColorPopover(index); } } function onToggleRunColorPopover(index: number, event: MouseEvent) { - if (interactiveRunColorPopoverIndex.value === index) { - closeRunColorPopover(index); - } else { - interactiveRunColorPopoverIndex.value = index; - - vue.nextTick(() => { - // Note: we do this in a next tick to ensure that the reference is available. + // Note: interactiveRunColorPopoverIndex is only used to ensure that the active popover gets properly hidden when the + // window loses focus as a result of switching tabs (see onMounted below). - interactiveRunColorPopoverRefs.value[index]?.toggle(event); - }); - } -} + interactiveRunColorPopoverIndex.value = index; -function closeRunColorPopover(index: number) { - interactiveRunColorPopoverRefs.value[index]?.hide(); + vue.nextTick(() => { + // Note: we do this in a next tick to ensure that the reference is available. - interactiveRunColorPopoverIndex.value = -1; + interactiveRunColorPopoverRefs.value[index]?.toggle(event); + }); } // "Initialise" our standard and/or interactive modes. @@ -802,14 +793,16 @@ vue.onMounted(() => { mutationObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['style'] }); - // Close popovers when the window loses focus. + // Make sure that our active popover gets hidden when the window loses focus. vue.watch( () => windowIsFocused.value, (isFocused) => { if (!isFocused) { if (interactiveRunColorPopoverIndex.value !== -1) { - closeRunColorPopover(interactiveRunColorPopoverIndex.value); + interactiveRunColorPopoverRefs.value[interactiveRunColorPopoverIndex.value]?.hide(); + + interactiveRunColorPopoverIndex.value = -1; } } } @@ -892,4 +885,8 @@ if (common.isDesktop()) { .run { border-color: var(--p-content-border-color); } + +.selected-color { + outline: 3px solid var(--p-text-color); +} From 8b6bff5bdc942812b8aee5df8c56d2d261f584e7 Mon Sep 17 00:00:00 2001 From: Alan Garny Date: Mon, 19 Jan 2026 13:58:01 +1300 Subject: [PATCH 4/4] Contents component: make sure that a file tab doesn't have any URL encoding. --- .../src/components/ContentsComponent.vue | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/components/ContentsComponent.vue b/src/renderer/src/components/ContentsComponent.vue index 8ee8f36e..ec94c3af 100644 --- a/src/renderer/src/components/ContentsComponent.vue +++ b/src/renderer/src/components/ContentsComponent.vue @@ -39,10 +39,7 @@
{{ - fileTab.file - .path() - .split(/(\\|\/)/g) - .pop() + fileName(fileTab.file.path()) }}
@@ -136,6 +133,18 @@ vue.watch(activeFile, (newActiveFile: string) => { electronApi?.fileSelected(newActiveFile); }); +function fileName(filePath: string): string { + const res = filePath.split(/(\\|\/)/g).pop() || ''; + + try { + return decodeURIComponent(res); + } catch (error: unknown) { + console.error('Failed to decode the file path:', res, error); + + return res; + } +} + function openFile(file: locApi.File): void { const filePath = file.path(); const prevActiveFile = activeFile.value;