@tellegen/svelte 0.1.2 → 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 +46 -0
- package/dist/Sparkline.svelte.d.ts +0 -1
- package/dist/TellegenMap.svelte +328 -200
- package/dist/TellegenMap.svelte.d.ts +0 -1
- package/dist/TellegenProvider.svelte +4 -0
- package/dist/TellegenProvider.svelte.d.ts +0 -1
- package/dist/TellegenShell.svelte +19 -13
- package/dist/TellegenShell.svelte.d.ts +0 -1
- package/dist/TellegenViewer.svelte.d.ts +0 -1
- package/dist/api.d.ts +8 -2
- package/dist/api.js +6 -2
- package/dist/colors.d.ts +7 -8
- package/dist/colors.js +13 -13
- package/dist/components/AppFooter.svelte.d.ts +0 -1
- package/dist/components/AppHeader.svelte +27 -13
- package/dist/components/AppHeader.svelte.d.ts +0 -1
- package/dist/components/BindingLines.svelte +3 -6
- package/dist/components/BindingLines.svelte.d.ts +0 -1
- package/dist/components/BusPicker.svelte +8 -2
- package/dist/components/BusPicker.svelte.d.ts +0 -1
- package/dist/components/ControlPanel.svelte +106 -343
- package/dist/components/ControlPanel.svelte.d.ts +0 -1
- package/dist/components/DemandSlider.svelte +26 -8
- package/dist/components/DemandSlider.svelte.d.ts +0 -1
- 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/DisplayControls.svelte.d.ts +0 -1
- package/dist/components/DropZone.svelte.d.ts +0 -1
- package/dist/components/FormulationSelector.svelte +8 -3
- package/dist/components/FormulationSelector.svelte.d.ts +0 -1
- package/dist/components/LocalCaseDetails.svelte +57 -44
- package/dist/components/LocalCaseDetails.svelte.d.ts +0 -1
- 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/MulticonductorDetails.svelte.d.ts +0 -1
- package/dist/components/NetworkStats.svelte +20 -18
- package/dist/components/NetworkStats.svelte.d.ts +0 -1
- 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/PlacementCue.svelte.d.ts +0 -1
- 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/RatingSlider.svelte.d.ts +0 -1
- package/dist/components/RestoreDefaultsButton.svelte.d.ts +0 -1
- 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/SensitivityReadout.svelte.d.ts +0 -1
- package/dist/components/SizeLegend.svelte.d.ts +0 -1
- package/dist/components/SolveCard.svelte +46 -49
- package/dist/components/SolveCard.svelte.d.ts +0 -1
- package/dist/components/TopMovers.svelte +8 -3
- package/dist/components/TopMovers.svelte.d.ts +2 -18
- package/dist/components.d.ts +2 -1
- package/dist/components.js +2 -0
- package/dist/context.svelte.d.ts +0 -1
- package/dist/controller.svelte.d.ts +74 -41
- package/dist/controller.svelte.js +1095 -284
- package/dist/diagram-labels.d.ts +9 -0
- package/dist/diagram-labels.js +40 -0
- package/dist/display.d.ts +3 -8
- package/dist/display.js +31 -27
- package/dist/drop-classify.d.ts +5 -42
- package/dist/drop-classify.js +5 -78
- package/dist/drop-limits.d.ts +27 -0
- package/dist/drop-limits.js +83 -0
- package/dist/format.d.ts +0 -1
- package/dist/format.js +5 -4
- package/dist/index.d.ts +11 -7
- package/dist/index.js +8 -3
- package/dist/map-bounds.d.ts +5 -0
- package/dist/map-bounds.js +41 -0
- package/dist/map.d.ts +1 -2
- package/dist/map.js +1 -1
- package/dist/multiconductor.d.ts +11 -3
- 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 -24
- package/dist/state.svelte.js +94 -20
- package/dist/synthetic-layout.d.ts +0 -1
- package/dist/synthetic-layout.js +17 -2
- package/dist/transformer-icon.d.ts +0 -1
- package/dist/types.d.ts +0 -1
- package/package.json +28 -12
- package/dist/Sparkline.svelte.d.ts.map +0 -1
- package/dist/TellegenMap.svelte.d.ts.map +0 -1
- package/dist/TellegenProvider.svelte.d.ts.map +0 -1
- package/dist/TellegenShell.svelte.d.ts.map +0 -1
- package/dist/TellegenViewer.svelte.d.ts.map +0 -1
- package/dist/api.d.ts.map +0 -1
- package/dist/colors.d.ts.map +0 -1
- package/dist/components/AppFooter.svelte.d.ts.map +0 -1
- package/dist/components/AppHeader.svelte.d.ts.map +0 -1
- package/dist/components/BindingLines.svelte.d.ts.map +0 -1
- package/dist/components/BusPicker.svelte.d.ts.map +0 -1
- package/dist/components/ControlPanel.svelte.d.ts.map +0 -1
- package/dist/components/DemandSlider.svelte.d.ts.map +0 -1
- package/dist/components/DisplayControls.svelte.d.ts.map +0 -1
- package/dist/components/DropZone.svelte.d.ts.map +0 -1
- package/dist/components/FormulationSelector.svelte.d.ts.map +0 -1
- package/dist/components/LocalCaseDetails.svelte.d.ts.map +0 -1
- package/dist/components/MulticonductorDetails.svelte.d.ts.map +0 -1
- package/dist/components/NetworkStats.svelte.d.ts.map +0 -1
- package/dist/components/PlacementCue.svelte.d.ts.map +0 -1
- package/dist/components/RatingSlider.svelte.d.ts.map +0 -1
- package/dist/components/RestoreDefaultsButton.svelte.d.ts.map +0 -1
- package/dist/components/SensitivityReadout.svelte.d.ts.map +0 -1
- package/dist/components/SizeLegend.svelte.d.ts.map +0 -1
- package/dist/components/SolveCard.svelte.d.ts.map +0 -1
- package/dist/components/TopMovers.svelte.d.ts.map +0 -1
- package/dist/components.d.ts.map +0 -1
- package/dist/context.svelte.d.ts.map +0 -1
- package/dist/controller.svelte.d.ts.map +0 -1
- package/dist/display.d.ts.map +0 -1
- package/dist/drop-classify.d.ts.map +0 -1
- package/dist/format.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/map.d.ts.map +0 -1
- package/dist/multiconductor.d.ts.map +0 -1
- package/dist/state.svelte.d.ts.map +0 -1
- package/dist/study-package.d.ts +0 -9
- package/dist/study-package.d.ts.map +0 -1
- package/dist/study-package.js +0 -17
- package/dist/synthetic-layout.d.ts.map +0 -1
- package/dist/transformer-icon.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { untrack } from 'svelte';
|
|
2
|
+
import { tick, untrack } from 'svelte';
|
|
3
3
|
import { getAppState, getController, getUiConfig } from '../context.svelte.js';
|
|
4
|
-
import
|
|
4
|
+
import PanelFrame from './PanelFrame.svelte';
|
|
5
|
+
import SavedNetworkDetails from './SavedNetworkDetails.svelte';
|
|
6
|
+
import ModelDetails from './ModelDetails.svelte';
|
|
7
|
+
import { getPanelLayout } from '../panels.svelte.js';
|
|
5
8
|
import AppFooter from './AppFooter.svelte';
|
|
6
9
|
import BindingLines from './BindingLines.svelte';
|
|
7
10
|
import BusPicker from './BusPicker.svelte';
|
|
@@ -11,6 +14,7 @@
|
|
|
11
14
|
import LocalCaseDetails from './LocalCaseDetails.svelte';
|
|
12
15
|
import MulticonductorDetails from './MulticonductorDetails.svelte';
|
|
13
16
|
import NetworkStats from './NetworkStats.svelte';
|
|
17
|
+
import PowerFlowDetails from './PowerFlowDetails.svelte';
|
|
14
18
|
import RatingSlider from './RatingSlider.svelte';
|
|
15
19
|
import SensitivityReadout from './SensitivityReadout.svelte';
|
|
16
20
|
import SizeLegend from './SizeLegend.svelte';
|
|
@@ -20,56 +24,9 @@
|
|
|
20
24
|
const ctrl = getController();
|
|
21
25
|
const config = getUiConfig();
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const SNAPS: Snap[] = ['peek', 'half', 'full'];
|
|
27
|
-
const SNAP_FRACTION: Record<Snap, number> = { peek: 0, half: 0.46, full: 0.92 };
|
|
28
|
-
/** Below this the viewport is a landscape phone and half has to give up room. */
|
|
29
|
-
const SHORT_VIEWPORT_PX = 560;
|
|
30
|
-
/** px/ms above which a release snaps in the drag direction instead of to the nearest. */
|
|
31
|
-
const FLICK_SPEED = 0.35;
|
|
32
|
-
/** Map left visible above a full sheet, on top of the header it also clears. */
|
|
33
|
-
const FULL_SNAP_MAP_BAND = 14;
|
|
34
|
-
|
|
35
|
-
let snap = $state<Snap>('half');
|
|
36
|
-
let headEl = $state.raw<HTMLElement | undefined>(undefined);
|
|
37
|
-
let bodyEl = $state.raw<HTMLElement | undefined>(undefined);
|
|
38
|
-
let headHeight = $state(52);
|
|
39
|
-
/** Height under an active drag; null when resting on a snap. */
|
|
40
|
-
let dragHeight = $state<number | null>(null);
|
|
41
|
-
|
|
42
|
-
const fractionFor = (s: Snap) =>
|
|
43
|
-
s === 'half' && app.viewportHeight < SHORT_VIEWPORT_PX ? 0.38 : SNAP_FRACTION[s];
|
|
44
|
-
|
|
45
|
-
const snapHeight = (s: Snap) =>
|
|
46
|
-
s === 'peek'
|
|
47
|
-
? headHeight
|
|
48
|
-
: Math.min(
|
|
49
|
-
Math.round(app.viewportHeight * fractionFor(s)),
|
|
50
|
-
app.viewportHeight - app.headerInset - FULL_SNAP_MAP_BAND
|
|
51
|
-
);
|
|
52
|
-
const sheetHeight = $derived(dragHeight ?? snapHeight(snap));
|
|
53
|
-
|
|
54
|
-
const caseLabel = $derived(
|
|
55
|
-
app.active
|
|
56
|
-
? splitName(app.active.name)[0]
|
|
57
|
-
: (app.activeLocal?.label ?? app.activeMulti?.label ?? '')
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
/** Single line shown on the grab bar while collapsed. */
|
|
61
|
-
const summary = $derived.by(() => {
|
|
62
|
-
if (app.error) return 'error — open for details';
|
|
63
|
-
if (app.selectedBus !== null) return `bus ${app.selectedBus} · ∂LMP/∂d`;
|
|
64
|
-
if (app.selectedBranch !== null) return `line ${app.selectedBranch} · ∂LMP/∂rating`;
|
|
65
|
-
const stats = ctrl.networkStats;
|
|
66
|
-
if (!stats) return app.parsingFile ? 'parsing…' : 'loading…';
|
|
67
|
-
return `${caseLabel} · ${stats.buses} buses · ${stats.branches} lines`;
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const expandLabel = $derived(
|
|
71
|
-
snap === 'full' ? 'collapse the control panel' : 'expand the control panel'
|
|
72
|
-
);
|
|
27
|
+
const layout = getPanelLayout();
|
|
28
|
+
let open = $state(true);
|
|
29
|
+
let bodyEl = $state.raw<HTMLElement>();
|
|
73
30
|
|
|
74
31
|
/** A selection whose readout has resolved (or is resolving). */
|
|
75
32
|
const busReadout = $derived(
|
|
@@ -81,141 +38,34 @@
|
|
|
81
38
|
/** Compact only: a resolved selection outranks the case stats it was read
|
|
82
39
|
* against. Gated on networkStats because that is what renders the readout. */
|
|
83
40
|
const selectionLeads = $derived(
|
|
84
|
-
app.compactLayout &&
|
|
41
|
+
app.compactLayout &&
|
|
42
|
+
!!ctrl.networkStats &&
|
|
43
|
+
(busReadout ||
|
|
44
|
+
branchReadout ||
|
|
45
|
+
(ctrl.activeFormulation === 'acpf' &&
|
|
46
|
+
(app.selectedBus !== null || app.selectedBranch !== null)))
|
|
85
47
|
);
|
|
86
48
|
|
|
87
49
|
$effect(() => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const observer = new ResizeObserver(measure);
|
|
98
|
-
observer.observe(el);
|
|
99
|
-
return () => observer.disconnect();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// A map selection renders the sensitivity readout and the demand control, both
|
|
103
|
-
// of which are below the fold at peek. The body also has to go back to the top:
|
|
104
|
-
// reaching the bus lookup scrolls it down, and the readout that replaces it as
|
|
105
|
-
// the first block would otherwise open out of view.
|
|
106
|
-
$effect(() => {
|
|
107
|
-
const selected = app.selectedBus ?? app.selectedBranch;
|
|
108
|
-
if (selected === null) return;
|
|
109
|
-
untrack(() => {
|
|
110
|
-
if (!app.compactLayout) return;
|
|
111
|
-
if (snap === 'peek') snap = 'half';
|
|
112
|
-
if (bodyEl) bodyEl.scrollTop = 0;
|
|
113
|
-
});
|
|
50
|
+
const selection = app.selectedBus ?? app.selectedBranch;
|
|
51
|
+
if (selection !== null)
|
|
52
|
+
untrack(() => {
|
|
53
|
+
open = true;
|
|
54
|
+
if (layout.compact) {
|
|
55
|
+
layout.drawer = 'network';
|
|
56
|
+
void tick().then(() => bodyEl?.closest('.panel-content')?.scrollTo({ top: 0 }));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
114
59
|
});
|
|
115
|
-
|
|
116
|
-
function cycle() {
|
|
117
|
-
snap = snap === 'peek' ? 'half' : snap === 'half' ? 'full' : 'peek';
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function nearestSnap(height: number): Snap {
|
|
121
|
-
return SNAPS.reduce((best, s) =>
|
|
122
|
-
Math.abs(snapHeight(s) - height) < Math.abs(snapHeight(best) - height) ? s : best
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
let dragStartY = 0;
|
|
127
|
-
let dragStartHeight = 0;
|
|
128
|
-
let dragMoved = false;
|
|
129
|
-
let lastY = 0;
|
|
130
|
-
let lastT = 0;
|
|
131
|
-
let speed = 0;
|
|
132
|
-
|
|
133
|
-
function onPointerDown(e: PointerEvent) {
|
|
134
|
-
if (!app.compactLayout || !e.isPrimary) return;
|
|
135
|
-
if (e.currentTarget instanceof HTMLElement) e.currentTarget.setPointerCapture(e.pointerId);
|
|
136
|
-
dragStartY = e.clientY;
|
|
137
|
-
dragStartHeight = sheetHeight;
|
|
138
|
-
dragHeight = dragStartHeight;
|
|
139
|
-
dragMoved = false;
|
|
140
|
-
speed = 0;
|
|
141
|
-
lastY = e.clientY;
|
|
142
|
-
lastT = e.timeStamp;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function onPointerMove(e: PointerEvent) {
|
|
146
|
-
if (dragHeight === null) return;
|
|
147
|
-
const dy = dragStartY - e.clientY;
|
|
148
|
-
if (Math.abs(dy) > 4) dragMoved = true;
|
|
149
|
-
const dt = e.timeStamp - lastT;
|
|
150
|
-
if (dt > 0) speed = (lastY - e.clientY) / dt;
|
|
151
|
-
lastY = e.clientY;
|
|
152
|
-
lastT = e.timeStamp;
|
|
153
|
-
dragHeight = Math.max(
|
|
154
|
-
snapHeight('peek'),
|
|
155
|
-
Math.min(snapHeight('full'), dragStartHeight + dy)
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function onPointerUp() {
|
|
160
|
-
if (dragHeight === null) return;
|
|
161
|
-
const height = dragHeight;
|
|
162
|
-
dragHeight = null;
|
|
163
|
-
if (!dragMoved) return; // a tap; the click handler cycles
|
|
164
|
-
const landed = nearestSnap(height);
|
|
165
|
-
if (Math.abs(speed) > FLICK_SPEED) {
|
|
166
|
-
const next = SNAPS.indexOf(landed) + (speed > 0 ? 1 : -1);
|
|
167
|
-
snap = SNAPS[Math.max(0, Math.min(SNAPS.length - 1, next))];
|
|
168
|
-
} else {
|
|
169
|
-
snap = landed;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function onHeadKeydown(e: KeyboardEvent) {
|
|
174
|
-
const step = e.key === 'ArrowUp' ? 1 : e.key === 'ArrowDown' ? -1 : 0;
|
|
175
|
-
if (step === 0) return;
|
|
176
|
-
e.preventDefault();
|
|
177
|
-
const next = SNAPS.indexOf(snap) + step;
|
|
178
|
-
snap = SNAPS[Math.max(0, Math.min(SNAPS.length - 1, next))];
|
|
179
|
-
}
|
|
180
60
|
</script>
|
|
181
61
|
|
|
182
|
-
<
|
|
183
|
-
class="panel"
|
|
184
|
-
class:sheet={app.compactLayout}
|
|
185
|
-
class:dragging={dragHeight !== null}
|
|
186
|
-
style:height={app.compactLayout ? `${sheetHeight}px` : null}
|
|
187
|
-
>
|
|
188
|
-
{#if app.compactLayout}
|
|
189
|
-
<button
|
|
190
|
-
type="button"
|
|
191
|
-
class="sheet-head"
|
|
192
|
-
bind:this={headEl}
|
|
193
|
-
aria-expanded={snap !== 'peek'}
|
|
194
|
-
aria-controls="control-panel-body"
|
|
195
|
-
aria-label={expandLabel}
|
|
196
|
-
onpointerdown={onPointerDown}
|
|
197
|
-
onpointermove={onPointerMove}
|
|
198
|
-
onpointerup={onPointerUp}
|
|
199
|
-
onpointercancel={onPointerUp}
|
|
200
|
-
onkeydown={onHeadKeydown}
|
|
201
|
-
onclick={() => {
|
|
202
|
-
// click fires after pointerup; a keyboard activation leaves dragMoved
|
|
203
|
-
// unset, so clear it here rather than in the pointer handlers
|
|
204
|
-
const moved = dragMoved;
|
|
205
|
-
dragMoved = false;
|
|
206
|
-
if (!moved) cycle();
|
|
207
|
-
}}
|
|
208
|
-
>
|
|
209
|
-
<span class="grabber" aria-hidden="true"></span>
|
|
210
|
-
{#if snap === 'peek'}
|
|
211
|
-
<span class="sheet-summary mono">{summary}</span>
|
|
212
|
-
{/if}
|
|
213
|
-
</button>
|
|
214
|
-
{/if}
|
|
215
|
-
|
|
62
|
+
<PanelFrame id="network" title="Network" side="left" order={10} width={320} bind:open>
|
|
216
63
|
<div class="panel-body" id="control-panel-body" bind:this={bodyEl}>
|
|
217
64
|
{#snippet selectionBlock()}
|
|
218
|
-
{#if
|
|
65
|
+
{#if ctrl.activeFormulation === 'acpf'}
|
|
66
|
+
<DisplayControls />
|
|
67
|
+
<PowerFlowDetails />
|
|
68
|
+
{:else if busReadout}
|
|
219
69
|
<SensitivityReadout />
|
|
220
70
|
|
|
221
71
|
<DemandSlider />
|
|
@@ -236,108 +86,90 @@
|
|
|
236
86
|
{/if}
|
|
237
87
|
{/snippet}
|
|
238
88
|
|
|
239
|
-
{#if app.
|
|
240
|
-
<
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
{/if}
|
|
246
|
-
{#if app.parsingFile}
|
|
247
|
-
<p class="dim mono blink">parsing…</p>
|
|
248
|
-
{/if}
|
|
89
|
+
{#if app.studyView}
|
|
90
|
+
<SavedNetworkDetails />
|
|
91
|
+
{:else}
|
|
92
|
+
{#if app.parsingFile}
|
|
93
|
+
<p class="dim mono blink">parsing…</p>
|
|
94
|
+
{/if}
|
|
249
95
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
{@render selectionBlock()}
|
|
96
|
+
<!-- Selected equipment leads on narrow screens. -->
|
|
97
|
+
{#if selectionLeads}
|
|
98
|
+
{@render selectionBlock()}
|
|
254
99
|
|
|
255
|
-
|
|
256
|
-
|
|
100
|
+
<hr />
|
|
101
|
+
{/if}
|
|
257
102
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
{#if !ctrl.networkStats}
|
|
265
|
-
{#if !app.error && !app.activeLocal && !app.activeMulti}
|
|
266
|
-
{#if ctrl.casesLoaded && app.cases.length === 0}
|
|
267
|
-
<p class="dim mono">
|
|
268
|
-
{config.loadDefaultCases ? 'no default cases loaded' : 'drop a case file to begin'}
|
|
269
|
-
</p>
|
|
270
|
-
{#if config.loadDefaultCases}
|
|
271
|
-
<button class="reset mono" onclick={ctrl.restoreDefaultCases}
|
|
272
|
-
>restore default cases</button
|
|
103
|
+
{#if app.activeLocal}
|
|
104
|
+
{#if app.activeLocal.diagram && app.activeLocal.view?.buses.length}
|
|
105
|
+
<div class="view-mode" aria-label="Coordinate display">
|
|
106
|
+
<button
|
|
107
|
+
aria-pressed={app.activeLocal.displayMode === 'geographic'}
|
|
108
|
+
onclick={() => ctrl.setCaseDisplayMode(app.activeLocal!, 'geographic')}>Map</button
|
|
273
109
|
>
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
110
|
+
<button
|
|
111
|
+
aria-pressed={app.activeLocal.displayMode === 'diagram'}
|
|
112
|
+
onclick={() => ctrl.setCaseDisplayMode(app.activeLocal!, 'diagram')}>Diagram</button
|
|
113
|
+
>
|
|
114
|
+
</div>
|
|
279
115
|
{/if}
|
|
116
|
+
<LocalCaseDetails />
|
|
280
117
|
{/if}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
<NetworkStats />
|
|
118
|
+
{#if app.activeMulti}
|
|
119
|
+
<MulticonductorDetails />
|
|
284
120
|
{/if}
|
|
121
|
+
{#if !ctrl.networkStats}
|
|
122
|
+
{#if !app.activeLocal && !app.activeMulti}
|
|
123
|
+
{#if ctrl.casesLoaded && app.cases.length === 0}
|
|
124
|
+
<p class="dim mono">
|
|
125
|
+
{config.loadDefaultCases ? 'no default cases loaded' : 'drop a case file to begin'}
|
|
126
|
+
</p>
|
|
127
|
+
{#if config.loadDefaultCases}
|
|
128
|
+
<button class="reset mono" onclick={ctrl.restoreDefaultCases}
|
|
129
|
+
>restore default cases</button
|
|
130
|
+
>
|
|
131
|
+
{/if}
|
|
132
|
+
{:else if ctrl.loadingBackendCase}
|
|
133
|
+
<p class="dim mono blink">loading selected case…</p>
|
|
134
|
+
{:else}
|
|
135
|
+
<p class="dim mono blink">loading cases…</p>
|
|
136
|
+
{/if}
|
|
137
|
+
{/if}
|
|
138
|
+
{:else}
|
|
139
|
+
{#if !app.activeLocal}
|
|
140
|
+
<NetworkStats />
|
|
141
|
+
{/if}
|
|
285
142
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
143
|
+
{#if ctrl.activeSolvable}
|
|
144
|
+
<FormulationSelector />
|
|
145
|
+
{/if}
|
|
289
146
|
|
|
290
|
-
|
|
147
|
+
<ModelDetails details={ctrl.activeSolvable?.network?.model_details} />
|
|
291
148
|
<BusPicker inline />
|
|
292
|
-
{/if}
|
|
293
149
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
150
|
+
{#if !app.placingLocalId && ctrl.activeFormulation !== 'acpf'}
|
|
151
|
+
<BindingLines />
|
|
152
|
+
{/if}
|
|
297
153
|
|
|
298
|
-
|
|
299
|
-
|
|
154
|
+
{#if !selectionLeads}
|
|
155
|
+
<hr />
|
|
300
156
|
|
|
301
|
-
|
|
302
|
-
|
|
157
|
+
{@render selectionBlock()}
|
|
158
|
+
{/if}
|
|
303
159
|
|
|
304
|
-
|
|
160
|
+
<hr />
|
|
305
161
|
|
|
306
|
-
|
|
162
|
+
<SizeLegend />
|
|
163
|
+
{/if}
|
|
307
164
|
{/if}
|
|
308
|
-
|
|
309
165
|
{#if app.compactLayout && config.showFooter}
|
|
310
166
|
<AppFooter inline />
|
|
311
167
|
{/if}
|
|
312
168
|
</div>
|
|
313
|
-
</
|
|
169
|
+
</PanelFrame>
|
|
314
170
|
|
|
315
171
|
<style>
|
|
316
|
-
.panel {
|
|
317
|
-
position: absolute;
|
|
318
|
-
top: 76px;
|
|
319
|
-
left: 20px;
|
|
320
|
-
z-index: 10;
|
|
321
|
-
display: flex;
|
|
322
|
-
flex-direction: column;
|
|
323
|
-
width: 312px;
|
|
324
|
-
max-height: calc(100% - 122px);
|
|
325
|
-
overflow: hidden;
|
|
326
|
-
background: var(--panel);
|
|
327
|
-
border: 1px solid var(--line);
|
|
328
|
-
border-radius: 3px;
|
|
329
|
-
backdrop-filter: blur(6px);
|
|
330
|
-
box-shadow: 0 4px 24px rgba(32, 36, 43, 0.08);
|
|
331
|
-
animation: rise 0.5s 0.12s ease-out both;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
172
|
.panel-body {
|
|
335
|
-
flex: 1 1 auto;
|
|
336
|
-
min-height: 0;
|
|
337
|
-
overflow-y: auto;
|
|
338
|
-
/* A flick that runs past the end of the panel must not scroll the page or
|
|
339
|
-
pan the map underneath. */
|
|
340
|
-
overscroll-behavior: contain;
|
|
341
173
|
padding: 16px 18px;
|
|
342
174
|
}
|
|
343
175
|
|
|
@@ -347,95 +179,26 @@
|
|
|
347
179
|
margin: 12px 0;
|
|
348
180
|
}
|
|
349
181
|
|
|
350
|
-
.
|
|
351
|
-
color: var(--red);
|
|
352
|
-
font-size: 12px;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
.error-actions {
|
|
356
|
-
display: flex;
|
|
357
|
-
gap: 6px;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/* The grab bar: a 44px strip that drags the sheet and cycles it on tap. */
|
|
361
|
-
.sheet-head {
|
|
362
|
-
flex: 0 0 auto;
|
|
182
|
+
.view-mode {
|
|
363
183
|
display: flex;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
justify-content: center;
|
|
367
|
-
gap: 7px;
|
|
368
|
-
width: 100%;
|
|
369
|
-
min-height: 44px;
|
|
370
|
-
padding: 9px 16px 11px;
|
|
371
|
-
background: none;
|
|
372
|
-
border: 0;
|
|
373
|
-
border-bottom: 1px solid transparent;
|
|
374
|
-
color: inherit;
|
|
375
|
-
font: inherit;
|
|
376
|
-
cursor: grab;
|
|
377
|
-
/* the browser must not claim the vertical gesture */
|
|
378
|
-
touch-action: none;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
.sheet-head:active {
|
|
382
|
-
cursor: grabbing;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
.grabber {
|
|
386
|
-
width: 40px;
|
|
387
|
-
height: 4px;
|
|
388
|
-
border-radius: 2px;
|
|
389
|
-
background: var(--line);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
.sheet-head:hover .grabber,
|
|
393
|
-
.sheet-head:focus-visible .grabber {
|
|
394
|
-
background: var(--accent);
|
|
184
|
+
gap: 4px;
|
|
185
|
+
margin-bottom: 12px;
|
|
395
186
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
font
|
|
401
|
-
color: var(--
|
|
402
|
-
text-overflow: ellipsis;
|
|
403
|
-
white-space: nowrap;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
.sheet {
|
|
407
|
-
top: auto;
|
|
408
|
-
right: 0;
|
|
409
|
-
bottom: 0;
|
|
410
|
-
left: 0;
|
|
411
|
-
width: auto;
|
|
412
|
-
max-height: none;
|
|
187
|
+
.view-mode button {
|
|
188
|
+
border: 1px solid var(--line);
|
|
189
|
+
border-radius: 4px;
|
|
190
|
+
padding: 5px 10px;
|
|
191
|
+
font: 12px/1.4 var(--font-display);
|
|
192
|
+
color: var(--ink);
|
|
413
193
|
background: var(--paper);
|
|
414
|
-
|
|
415
|
-
border-radius: 12px 12px 0 0;
|
|
416
|
-
box-shadow: 0 -6px 28px rgba(32, 36, 43, 0.14);
|
|
417
|
-
transition: height var(--dur-med) var(--ease-out);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
.sheet .panel-body {
|
|
421
|
-
padding: 0 16px 14px;
|
|
422
|
-
/* clear the home indicator on a phone with no bezel */
|
|
423
|
-
padding-bottom: max(14px, env(safe-area-inset-bottom));
|
|
424
|
-
-webkit-overflow-scrolling: touch;
|
|
194
|
+
cursor: pointer;
|
|
425
195
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
196
|
+
.view-mode button[aria-pressed='true'] {
|
|
197
|
+
border-color: var(--accent);
|
|
198
|
+
background: var(--accent-soft);
|
|
429
199
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
transition: none;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
@media (prefers-reduced-motion: reduce) {
|
|
437
|
-
.panel {
|
|
438
|
-
animation: none;
|
|
439
|
-
}
|
|
200
|
+
.view-mode button:focus-visible {
|
|
201
|
+
outline: 2px solid var(--focus-ring);
|
|
202
|
+
outline-offset: 2px;
|
|
440
203
|
}
|
|
441
204
|
</style>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const app = getAppState();
|
|
6
6
|
const ctrl = getController();
|
|
7
|
+
const disabled = $derived(ctrl.sliderDisabled);
|
|
7
8
|
|
|
8
9
|
// Fill from the committed demand to the live thumb. The local number line stays
|
|
9
10
|
// stable after release; only the committed tick moves.
|
|
@@ -23,8 +24,9 @@
|
|
|
23
24
|
const fillHi = $derived(valFrac < neutralFrac ? neutralPos : valPos);
|
|
24
25
|
const sliderTip =
|
|
25
26
|
'The black tick marks the last committed demand. Drag the knob to preview a new demand; release to solve and move the tick to that point.';
|
|
27
|
+
const disabledTip = 'this analysis bus is display-only; demand is not adjustable';
|
|
26
28
|
const scoreTip =
|
|
27
|
-
'Gradient
|
|
29
|
+
'Gradient estimates the declared objective change before the solve finishes. It uses the selected bus LMP times the demand step, plus local curvature when the preview engine is unavailable. Exact is the resolved OPF objective change.';
|
|
28
30
|
</script>
|
|
29
31
|
|
|
30
32
|
{#if ctrl.activeSolvable}
|
|
@@ -41,6 +43,7 @@
|
|
|
41
43
|
class:active={app.demandRangeMode === 'local'}
|
|
42
44
|
aria-pressed={app.demandRangeMode === 'local'}
|
|
43
45
|
aria-label="nearby demand range"
|
|
46
|
+
{disabled}
|
|
44
47
|
title="range near the selected demand setting"
|
|
45
48
|
onclick={() => ctrl.setDemandRangeMode('local')}>nearby</button
|
|
46
49
|
>
|
|
@@ -49,22 +52,30 @@
|
|
|
49
52
|
class:active={app.demandRangeMode === 'full'}
|
|
50
53
|
aria-pressed={app.demandRangeMode === 'full'}
|
|
51
54
|
aria-label="full demand range"
|
|
55
|
+
{disabled}
|
|
52
56
|
title="range from zero load to the local physical limit"
|
|
53
57
|
onclick={() => ctrl.setDemandRangeMode('full')}>full range</button
|
|
54
58
|
>
|
|
55
59
|
</div>
|
|
56
|
-
<span class="mono dim">
|
|
60
|
+
<span class="mono dim">
|
|
61
|
+
{#if disabled}
|
|
62
|
+
not adjustable
|
|
63
|
+
{:else}
|
|
64
|
+
{fmt.format(ctrl.sliderMin)} to {fmt.format(ctrl.sliderMax)} MW
|
|
65
|
+
{/if}
|
|
66
|
+
</span>
|
|
57
67
|
</div>
|
|
58
68
|
<div
|
|
59
69
|
class="slider-track"
|
|
60
70
|
style="--fill-lo:{fillLo}; --fill-hi:{fillHi}; --neutral-pos:{neutralPos}"
|
|
61
|
-
title={sliderTip}
|
|
71
|
+
title={disabled ? disabledTip : sliderTip}
|
|
62
72
|
>
|
|
63
73
|
<input
|
|
64
74
|
type="range"
|
|
65
75
|
min={ctrl.sliderMin}
|
|
66
76
|
max={ctrl.sliderMax}
|
|
67
77
|
step="any"
|
|
78
|
+
{disabled}
|
|
68
79
|
bind:value={ctrl.sliderCurrent, ctrl.setSliderPreview}
|
|
69
80
|
aria-label="demand delta at selected bus"
|
|
70
81
|
onpointerdown={() => ctrl.setSliderPreview(ctrl.sliderValue)}
|
|
@@ -77,7 +88,7 @@
|
|
|
77
88
|
<div class="demand-feedback" class:idle={!ctrl.previewing && !ctrl.isPerturbed(c)}>
|
|
78
89
|
<p class="pred mono dim" aria-hidden={!(ctrl.predictedDeltaObj !== null && ctrl.previewing)}>
|
|
79
90
|
{#if ctrl.predictedDeltaObj !== null && ctrl.previewing}
|
|
80
|
-
predicted Δ
|
|
91
|
+
predicted Δobjective {signed(ctrl.predictedDeltaObj)}
|
|
81
92
|
{:else}
|
|
82
93
|
|
|
83
94
|
{/if}
|
|
@@ -85,10 +96,8 @@
|
|
|
85
96
|
<p class="score mono" aria-hidden={!(ctrl.gradientScore && ctrl.isPerturbed(c))}>
|
|
86
97
|
{#if ctrl.gradientScore && ctrl.isPerturbed(c)}
|
|
87
98
|
<span title={scoreTip}>
|
|
88
|
-
gradient {signed(ctrl.gradientScore.pred)}
|
|
89
|
-
|
|
90
|
-
)}
|
|
91
|
-
$/h
|
|
99
|
+
gradient {signed(ctrl.gradientScore.pred)}, exact {signed(ctrl.gradientScore.exact)} objective
|
|
100
|
+
units
|
|
92
101
|
</span>
|
|
93
102
|
{:else}
|
|
94
103
|
|
|
@@ -173,6 +182,15 @@
|
|
|
173
182
|
margin: 0;
|
|
174
183
|
}
|
|
175
184
|
|
|
185
|
+
input[type='range']:disabled {
|
|
186
|
+
opacity: 0.45;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.range-mode button:disabled {
|
|
190
|
+
cursor: default;
|
|
191
|
+
opacity: 0.45;
|
|
192
|
+
}
|
|
193
|
+
|
|
176
194
|
input[type='range']::-webkit-slider-thumb {
|
|
177
195
|
-webkit-appearance: none;
|
|
178
196
|
appearance: none;
|