@tellegen/svelte 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -5
- package/dist/TellegenMap.svelte +325 -163
- package/dist/TellegenProvider.svelte +4 -0
- package/dist/TellegenShell.svelte +19 -13
- package/dist/api.d.ts +8 -1
- package/dist/api.js +6 -2
- package/dist/colors.d.ts +7 -7
- package/dist/colors.js +13 -13
- package/dist/components/AppHeader.svelte +23 -11
- package/dist/components/BindingLines.svelte +3 -6
- package/dist/components/BusPicker.svelte +8 -2
- package/dist/components/ControlPanel.svelte +106 -343
- package/dist/components/DemandSlider.svelte +21 -23
- package/dist/components/DiagramCanvas.svelte +486 -0
- package/dist/components/DiagramCanvas.svelte.d.ts +13 -0
- package/dist/components/DisplayControls.svelte +4 -2
- package/dist/components/FormulationSelector.svelte +8 -3
- package/dist/components/LocalCaseDetails.svelte +55 -45
- package/dist/components/McResults.svelte +365 -0
- package/dist/components/McResults.svelte.d.ts +11 -0
- package/dist/components/ModelDetails.svelte +230 -0
- package/dist/components/ModelDetails.svelte.d.ts +7 -0
- package/dist/components/MulticonductorDetails.svelte +227 -78
- package/dist/components/NetworkStats.svelte +20 -18
- package/dist/components/NotificationCenter.svelte +243 -0
- package/dist/components/NotificationCenter.svelte.d.ts +3 -0
- package/dist/components/PanelCard.svelte +319 -0
- package/dist/components/PanelCard.svelte.d.ts +9 -0
- package/dist/components/PanelFrame.svelte +46 -0
- package/dist/components/PanelFrame.svelte.d.ts +16 -0
- package/dist/components/PanelHost.svelte +212 -0
- package/dist/components/PanelHost.svelte.d.ts +3 -0
- package/dist/components/PowerFlowDetails.svelte +99 -0
- package/dist/components/PowerFlowDetails.svelte.d.ts +3 -0
- package/dist/components/RatingSlider.svelte +5 -9
- package/dist/components/SavedNetworkDetails.svelte +214 -0
- package/dist/components/SavedNetworkDetails.svelte.d.ts +3 -0
- package/dist/components/SensitivityReadout.svelte +24 -16
- package/dist/components/SolveCard.svelte +46 -49
- package/dist/components/TopMovers.svelte +8 -3
- package/dist/components/TopMovers.svelte.d.ts +2 -17
- package/dist/components.d.ts +2 -0
- package/dist/components.js +2 -0
- package/dist/controller.svelte.d.ts +54 -33
- package/dist/controller.svelte.js +927 -233
- package/dist/diagram-labels.d.ts +9 -0
- package/dist/diagram-labels.js +40 -0
- package/dist/display.d.ts +3 -7
- package/dist/display.js +31 -27
- package/dist/format.js +5 -4
- package/dist/index.d.ts +10 -4
- package/dist/index.js +7 -2
- package/dist/map.d.ts +1 -1
- package/dist/map.js +1 -1
- package/dist/multiconductor.d.ts +11 -2
- package/dist/multiconductor.js +107 -14
- package/dist/notices.svelte.d.ts +29 -0
- package/dist/notices.svelte.js +82 -0
- package/dist/panels.svelte.d.ts +65 -0
- package/dist/panels.svelte.js +215 -0
- package/dist/state.svelte.d.ts +90 -23
- package/dist/state.svelte.js +94 -20
- package/dist/synthetic-layout.js +1 -0
- package/package.json +22 -9
- package/dist/study-package.d.ts +0 -8
- package/dist/study-package.js +0 -17
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
} from './context.svelte.js';
|
|
10
10
|
import { createController } from './controller.svelte.js';
|
|
11
11
|
import { createAppState } from './state.svelte.js';
|
|
12
|
+
import { PanelLayout, setPanelLayout } from './panels.svelte.js';
|
|
13
|
+
import { NoticeCenter, setNoticeCenter } from './notices.svelte.js';
|
|
12
14
|
|
|
13
15
|
interface Props extends TellegenUiOptions {
|
|
14
16
|
children?: Snippet;
|
|
@@ -30,6 +32,8 @@
|
|
|
30
32
|
const app = createAppState();
|
|
31
33
|
const ctrl = createController(app, { apiBase: config.apiBase });
|
|
32
34
|
|
|
35
|
+
setPanelLayout(new PanelLayout());
|
|
36
|
+
setNoticeCenter(new NoticeCenter());
|
|
33
37
|
setUiConfig(config);
|
|
34
38
|
setAppState(app);
|
|
35
39
|
setController(ctrl);
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
3
|
import AppFooter from './components/AppFooter.svelte';
|
|
4
|
+
import NotificationCenter from './components/NotificationCenter.svelte';
|
|
4
5
|
import AppHeader from './components/AppHeader.svelte';
|
|
5
|
-
import BusPicker from './components/BusPicker.svelte';
|
|
6
6
|
import ControlPanel from './components/ControlPanel.svelte';
|
|
7
7
|
import DropZone from './components/DropZone.svelte';
|
|
8
8
|
import PlacementCue from './components/PlacementCue.svelte';
|
|
9
9
|
import RestoreDefaultsButton from './components/RestoreDefaultsButton.svelte';
|
|
10
10
|
import SolveCard from './components/SolveCard.svelte';
|
|
11
|
+
import PanelHost from './components/PanelHost.svelte';
|
|
12
|
+
import { PANEL_COMPACT_QUERY } from './panels.svelte.js';
|
|
11
13
|
import { getAppState, getController, getUiConfig } from './context.svelte.js';
|
|
12
14
|
import TellegenMap from './TellegenMap.svelte';
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
// Gates the drag-and-drop affordance only. The upload button in the header
|
|
17
|
+
// is always rendered: a file picker works on every device, and the plain
|
|
18
|
+
// `hover`/`pointer` queries resolve false on a touchscreen laptop that also
|
|
19
|
+
// has a mouse, which hid the whole file flow there (#110). `any-*` match
|
|
20
|
+
// when at least one attached input can hover and point precisely.
|
|
21
|
+
const FILE_DROP_QUERY = '(any-hover: hover) and (any-pointer: fine)';
|
|
16
22
|
|
|
17
23
|
const app = getAppState();
|
|
18
24
|
const ctrl = getController();
|
|
@@ -21,6 +27,7 @@
|
|
|
21
27
|
let dragDepth = 0;
|
|
22
28
|
|
|
23
29
|
$effect(() => {
|
|
30
|
+
if (app.studyView) return;
|
|
24
31
|
let hasDisplayMode = false;
|
|
25
32
|
for (const option of ctrl.displayOptions) {
|
|
26
33
|
if (option.mode === app.displayMode) {
|
|
@@ -29,7 +36,7 @@
|
|
|
29
36
|
}
|
|
30
37
|
}
|
|
31
38
|
if (ctrl.displayOptions.length > 0 && !hasDisplayMode) {
|
|
32
|
-
app.displayMode = '
|
|
39
|
+
app.displayMode = 'price';
|
|
33
40
|
}
|
|
34
41
|
});
|
|
35
42
|
|
|
@@ -52,7 +59,7 @@
|
|
|
52
59
|
syncFileDropUi();
|
|
53
60
|
query.addEventListener('change', syncFileDropUi);
|
|
54
61
|
|
|
55
|
-
const compact = window.matchMedia(
|
|
62
|
+
const compact = window.matchMedia(PANEL_COMPACT_QUERY);
|
|
56
63
|
const syncCompact = () => {
|
|
57
64
|
app.compactLayout = compact.matches;
|
|
58
65
|
if (!compact.matches) app.sheetInset = 0;
|
|
@@ -60,8 +67,7 @@
|
|
|
60
67
|
syncCompact();
|
|
61
68
|
compact.addEventListener('change', syncCompact);
|
|
62
69
|
|
|
63
|
-
//
|
|
64
|
-
// it lifts the basemap chrome, so both read one measurement taken here.
|
|
70
|
+
// Shared viewport measurements keep panel and map framing dimensions aligned.
|
|
65
71
|
const syncViewport = () => (app.viewportHeight = window.innerHeight);
|
|
66
72
|
syncViewport();
|
|
67
73
|
window.addEventListener('resize', syncViewport);
|
|
@@ -129,15 +135,15 @@
|
|
|
129
135
|
/>
|
|
130
136
|
|
|
131
137
|
<AppHeader />
|
|
138
|
+
<PanelHost />
|
|
139
|
+
<NotificationCenter />
|
|
132
140
|
<ControlPanel />
|
|
133
141
|
<SolveCard />
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
<
|
|
142
|
+
{#if !app.studyView}
|
|
143
|
+
<DropZone />
|
|
144
|
+
<PlacementCue />
|
|
145
|
+
<RestoreDefaultsButton />
|
|
137
146
|
{/if}
|
|
138
|
-
<DropZone />
|
|
139
|
-
<PlacementCue />
|
|
140
|
-
<RestoreDefaultsButton />
|
|
141
147
|
{#if config.showFooter && !app.compactLayout}
|
|
142
148
|
<AppFooter />
|
|
143
149
|
{/if}
|
package/dist/api.d.ts
CHANGED
|
@@ -9,8 +9,15 @@ export interface TellegenApiClientOptions {
|
|
|
9
9
|
export interface TellegenApiClient {
|
|
10
10
|
getCases(): Promise<CaseSummary[]>;
|
|
11
11
|
getNetwork(caseId: string): Promise<Network>;
|
|
12
|
-
|
|
12
|
+
getCaseModuleJson(caseId: string, signal?: AbortSignal): Promise<string>;
|
|
13
13
|
getSolution(caseId: string): Promise<Solution>;
|
|
14
|
+
getSavedCase?(caseId: string): Promise<{
|
|
15
|
+
base_input: string;
|
|
16
|
+
input: string;
|
|
17
|
+
solution: string;
|
|
18
|
+
view: import('@tellegen/engine').StudyView;
|
|
19
|
+
display_solution: Solution;
|
|
20
|
+
}>;
|
|
14
21
|
getSensitivity(caseId: string, bus: number, deltas?: DemandDeltas, signal?: AbortSignal): Promise<SensitivityColumn>;
|
|
15
22
|
getBranchSensitivity(caseId: string, branch: number, deltas?: DemandDeltas, signal?: AbortSignal): Promise<SensitivityColumn>;
|
|
16
23
|
/** Whether the server's compute endpoints (solve stream, sensitivities) are
|
package/dist/api.js
CHANGED
|
@@ -77,11 +77,12 @@ export function createApiClient(options = {}) {
|
|
|
77
77
|
return {
|
|
78
78
|
getCases: () => getJson(fetchImpl, apiPath(apiBase, '/cases')),
|
|
79
79
|
getNetwork: (caseId) => getJson(fetchImpl, apiPath(apiBase, `/cases/${caseId}/network`)),
|
|
80
|
-
async
|
|
80
|
+
async getCaseModuleJson(caseId, signal) {
|
|
81
81
|
const url = apiPath(apiBase, `/cases/${caseId}/case`);
|
|
82
82
|
const res = await checkedFetch(fetchImpl, url, signal);
|
|
83
83
|
return res.text();
|
|
84
84
|
},
|
|
85
|
+
getSavedCase: (caseId) => getJson(fetchImpl, apiPath(apiBase, `/cases/${caseId}/snapshot`)),
|
|
85
86
|
getSolution: (caseId) => getJson(fetchImpl, apiPath(apiBase, `/cases/${caseId}/solution`)),
|
|
86
87
|
getSensitivity(caseId, bus, deltas = {}, signal) {
|
|
87
88
|
const d = encodeDeltas(deltas);
|
|
@@ -111,7 +112,10 @@ export function createApiClient(options = {}) {
|
|
|
111
112
|
finished = true;
|
|
112
113
|
es.close();
|
|
113
114
|
};
|
|
114
|
-
es.addEventListener('solution', (e) =>
|
|
115
|
+
es.addEventListener('solution', (e) => {
|
|
116
|
+
const solution = JSON.parse(e.data);
|
|
117
|
+
handlers.onsolution?.(solution);
|
|
118
|
+
});
|
|
115
119
|
es.addEventListener('sensitivity', (e) => handlers.onsensitivity?.(JSON.parse(e.data)));
|
|
116
120
|
es.addEventListener('fail', (e) => {
|
|
117
121
|
finish();
|
package/dist/colors.d.ts
CHANGED
|
@@ -8,20 +8,20 @@ export interface SensitivityDomain {
|
|
|
8
8
|
scale: number;
|
|
9
9
|
flat: boolean;
|
|
10
10
|
}
|
|
11
|
-
export declare const
|
|
12
|
-
export declare const
|
|
13
|
-
/** Trimmed
|
|
14
|
-
* minimum span.
|
|
11
|
+
export declare const priceColor: (t: number) => RGBA;
|
|
12
|
+
export declare const priceGradient: string;
|
|
13
|
+
/** Trimmed nodal value color domain: 5th to 95th percentile, with a one-unit
|
|
14
|
+
* minimum span. Nodal values are heavy tailed when one binding line pins a
|
|
15
15
|
* handful of buses far from the pack) so min-max scaling compresses the pack
|
|
16
16
|
* into a single hue, and a congestion-free case is one price plus solver
|
|
17
17
|
* noise that an exact scale would stretch into fake structure. Values beyond
|
|
18
18
|
* the domain clamp to the ramp ends. */
|
|
19
|
-
export declare function
|
|
19
|
+
export declare function priceDomain(values: number[]): {
|
|
20
20
|
lo: number;
|
|
21
21
|
hi: number;
|
|
22
22
|
};
|
|
23
23
|
/** Center a scalar display variable (bus voltage angle, |V|) on a symmetric domain
|
|
24
|
-
* with a per-mode minimum span;
|
|
24
|
+
* with a per-mode minimum span; nodal values keep their own robust quantile domain. Shared by
|
|
25
25
|
* the control panel readout and the map color scale. */
|
|
26
26
|
export declare function scalarDomain(mode: DisplayMode, values: number[]): {
|
|
27
27
|
lo: number;
|
|
@@ -33,7 +33,7 @@ export declare const sensGradient: string;
|
|
|
33
33
|
export declare const sensNeutral: RGBA;
|
|
34
34
|
export declare const busNeutral: RGBA;
|
|
35
35
|
export declare function sensFlatColor(domain: Pick<SensitivityDomain, 'mean'>): RGBA;
|
|
36
|
-
/** The fixed normalization for a slider drag: predicted
|
|
36
|
+
/** The fixed normalization for a slider drag: predicted nodal value change divides by
|
|
37
37
|
* scale × maxAbsStep, so color intensity is linear in the step and saturates at
|
|
38
38
|
* full deflection for the column's robust-max buses. A per-frame domain over
|
|
39
39
|
* column × step would cancel the step exactly (sensitivityDomain is degree-1
|
package/dist/colors.js
CHANGED
|
@@ -19,25 +19,25 @@ function cssGradient(stops) {
|
|
|
19
19
|
const parts = stops.map((s) => `rgb(${s[0]} ${s[1]} ${s[2]})`);
|
|
20
20
|
return `linear-gradient(90deg, ${parts.join(', ')})`;
|
|
21
21
|
}
|
|
22
|
-
/** Sequential
|
|
22
|
+
/** Sequential nodal value ramp: pale straw darkening through
|
|
23
23
|
* amber and rust to near-black maroon. Lightness falls monotonically, so the
|
|
24
24
|
* ordering survives every kind of color vision deficiency. */
|
|
25
|
-
const
|
|
25
|
+
const PRICE_STOPS = [
|
|
26
26
|
[246, 227, 180, 235],
|
|
27
27
|
[236, 178, 66, 235],
|
|
28
28
|
[212, 116, 34, 240],
|
|
29
29
|
[160, 56, 21, 245],
|
|
30
30
|
[84, 18, 7, 250]
|
|
31
31
|
];
|
|
32
|
-
export const
|
|
33
|
-
export const
|
|
34
|
-
/** Trimmed
|
|
35
|
-
* minimum span.
|
|
32
|
+
export const priceColor = (t) => ramp(PRICE_STOPS, t);
|
|
33
|
+
export const priceGradient = cssGradient(PRICE_STOPS);
|
|
34
|
+
/** Trimmed nodal value color domain: 5th to 95th percentile, with a one-unit
|
|
35
|
+
* minimum span. Nodal values are heavy tailed when one binding line pins a
|
|
36
36
|
* handful of buses far from the pack) so min-max scaling compresses the pack
|
|
37
37
|
* into a single hue, and a congestion-free case is one price plus solver
|
|
38
38
|
* noise that an exact scale would stretch into fake structure. Values beyond
|
|
39
39
|
* the domain clamp to the ramp ends. */
|
|
40
|
-
export function
|
|
40
|
+
export function priceDomain(values) {
|
|
41
41
|
if (values.length === 0)
|
|
42
42
|
return { lo: 0, hi: 1 };
|
|
43
43
|
const sorted = [...values].sort((a, b) => a - b);
|
|
@@ -49,11 +49,11 @@ export function lmpDomain(values) {
|
|
|
49
49
|
return { lo: mid - span / 2, hi: mid + span / 2 };
|
|
50
50
|
}
|
|
51
51
|
/** Center a scalar display variable (bus voltage angle, |V|) on a symmetric domain
|
|
52
|
-
* with a per-mode minimum span;
|
|
52
|
+
* with a per-mode minimum span; nodal values keep their own robust quantile domain. Shared by
|
|
53
53
|
* the control panel readout and the map color scale. */
|
|
54
54
|
export function scalarDomain(mode, values) {
|
|
55
|
-
if (mode === '
|
|
56
|
-
return
|
|
55
|
+
if (mode === 'price')
|
|
56
|
+
return priceDomain(values);
|
|
57
57
|
if (values.length === 0)
|
|
58
58
|
return { lo: 0, hi: 1 };
|
|
59
59
|
const { min: rawLo, max: rawHi } = extent(values);
|
|
@@ -62,9 +62,9 @@ export function scalarDomain(mode, values) {
|
|
|
62
62
|
const mid = (rawLo + rawHi) / 2;
|
|
63
63
|
return { lo: mid - span / 2, hi: mid + span / 2 };
|
|
64
64
|
}
|
|
65
|
-
/** Diverging sensitivity ramp: green (
|
|
65
|
+
/** Diverging sensitivity ramp: green (nodal value falls per MW of demand) through
|
|
66
66
|
* a paper-toned neutral to purple (price rises). The green/purple pair stays
|
|
67
|
-
* legible under CVD and shares no hue with the warm
|
|
67
|
+
* legible under CVD and shares no hue with the warm nodal value ramp, so the two
|
|
68
68
|
* modes cannot be confused. */
|
|
69
69
|
const SENS_STOPS = [
|
|
70
70
|
[27, 120, 55, 240],
|
|
@@ -84,7 +84,7 @@ export function sensFlatColor(domain) {
|
|
|
84
84
|
return sensNeutral;
|
|
85
85
|
return sensColor(domain.mean > 0 ? FLAT_SENSITIVITY_TINT : -FLAT_SENSITIVITY_TINT);
|
|
86
86
|
}
|
|
87
|
-
/** The fixed normalization for a slider drag: predicted
|
|
87
|
+
/** The fixed normalization for a slider drag: predicted nodal value change divides by
|
|
88
88
|
* scale × maxAbsStep, so color intensity is linear in the step and saturates at
|
|
89
89
|
* full deflection for the column's robust-max buses. A per-frame domain over
|
|
90
90
|
* column × step would cancel the step exactly (sensitivityDomain is degree-1
|
|
@@ -37,11 +37,16 @@
|
|
|
37
37
|
{#each app.cases as c (c.id)}
|
|
38
38
|
{@const [cname, cregion] = splitName(c.name)}
|
|
39
39
|
<div class="case-chip" class:active={app.activeCaseId === c.id}>
|
|
40
|
-
<button
|
|
40
|
+
<button
|
|
41
|
+
class="case-activate"
|
|
42
|
+
disabled={!!c.unavailableReason}
|
|
43
|
+
title={c.unavailableReason ?? undefined}
|
|
44
|
+
onclick={() => ctrl.activateCase(c.id)}
|
|
45
|
+
>
|
|
41
46
|
<span class="cname"
|
|
42
47
|
>{cname}{#if c.perturbed}<i class="mark" title="demand perturbed"></i>{/if}</span
|
|
43
48
|
>
|
|
44
|
-
<span class="cregion mono">{cregion}</span>
|
|
49
|
+
<span class="cregion mono">{c.unavailableReason ? 'Unavailable' : cregion}</span>
|
|
45
50
|
</button>
|
|
46
51
|
<button
|
|
47
52
|
class="case-remove mono"
|
|
@@ -79,16 +84,18 @@
|
|
|
79
84
|
>
|
|
80
85
|
</div>
|
|
81
86
|
{/each}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
<button
|
|
88
|
+
class="ghost filedrop-ui"
|
|
89
|
+
title="parsed in your browser; the file never uploads"
|
|
90
|
+
onclick={() => fileInput?.click()}
|
|
91
|
+
>
|
|
92
|
+
<span class="cname"
|
|
93
|
+
><span class="arrow">⇣</span>{ctrl.showFileDropUi
|
|
94
|
+
? 'drop a case file'
|
|
95
|
+
: 'open a case file'}</span
|
|
87
96
|
>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
</button>
|
|
91
|
-
{/if}
|
|
97
|
+
<span class="cregion mono">case + geo files</span>
|
|
98
|
+
</button>
|
|
92
99
|
</nav>
|
|
93
100
|
<span class="kicker mono">
|
|
94
101
|
<a href={config.orgHref} target="_blank" rel="noreferrer">{config.orgLabel}</a>
|
|
@@ -203,6 +210,11 @@
|
|
|
203
210
|
border: 0;
|
|
204
211
|
}
|
|
205
212
|
|
|
213
|
+
.case-activate:disabled {
|
|
214
|
+
color: var(--text-secondary);
|
|
215
|
+
cursor: not-allowed;
|
|
216
|
+
}
|
|
217
|
+
|
|
206
218
|
.case-remove {
|
|
207
219
|
position: absolute;
|
|
208
220
|
top: 1px;
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<div class="binding-lines">
|
|
33
33
|
<p
|
|
34
34
|
class="mono dim head"
|
|
35
|
-
title="lines loaded to their thermal rating; select one to see
|
|
35
|
+
title="lines loaded to their thermal rating; select one to see LMP sensitivity to the rating"
|
|
36
36
|
>
|
|
37
37
|
binding lines
|
|
38
38
|
</p>
|
|
@@ -45,9 +45,7 @@
|
|
|
45
45
|
aria-pressed={app.selectedBranch === branch.id}
|
|
46
46
|
onclick={() => select(branch.id)}
|
|
47
47
|
>
|
|
48
|
-
line {branch.from} – {branch.to}
|
|
49
|
-
loading * 100
|
|
50
|
-
)}%
|
|
48
|
+
line {branch.from} – {branch.to}, {Math.round(loading * 100)}%
|
|
51
49
|
</button>
|
|
52
50
|
</li>
|
|
53
51
|
{/each}
|
|
@@ -93,8 +91,7 @@
|
|
|
93
91
|
li button.selected {
|
|
94
92
|
color: var(--text-accent);
|
|
95
93
|
background:
|
|
96
|
-
linear-gradient(90deg, rgba(177, 104, 27, 0.16), rgba(177, 104, 27, 0.05)),
|
|
97
|
-
var(--accent-soft);
|
|
94
|
+
linear-gradient(90deg, rgba(177, 104, 27, 0.16), rgba(177, 104, 27, 0.05)), var(--accent-soft);
|
|
98
95
|
box-shadow: inset 3px 0 0 var(--accent);
|
|
99
96
|
font-weight: 600;
|
|
100
97
|
}
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
/** Render in the parent's flow instead of as a card floating over the map. */
|
|
9
9
|
let { inline = false }: { inline?: boolean } = $props();
|
|
10
10
|
|
|
11
|
-
const buses = $derived(
|
|
11
|
+
const buses = $derived(
|
|
12
|
+
app.studyView?.network.buses ?? app.active?.network?.buses ?? app.activeLocal?.view?.buses ?? []
|
|
13
|
+
);
|
|
12
14
|
|
|
13
15
|
let query = $state('');
|
|
14
16
|
let open = $state(false);
|
|
@@ -25,7 +27,11 @@
|
|
|
25
27
|
function commit(id: number) {
|
|
26
28
|
const caseId = app.activeCaseId;
|
|
27
29
|
const localId = app.activeLocalId;
|
|
28
|
-
if (
|
|
30
|
+
if (app.studyView) {
|
|
31
|
+
app.selectedBranch = null;
|
|
32
|
+
app.selectedBus = id;
|
|
33
|
+
void app.requestFrame({ caseId: app.studyView.caseId, busId: id });
|
|
34
|
+
} else if (caseId && app.active) ctrl.selectBus(caseId, id);
|
|
29
35
|
else if (localId && app.activeLocal) ctrl.selectLocalBus(localId, id);
|
|
30
36
|
query = '';
|
|
31
37
|
open = false;
|