@tellegen/svelte 0.2.0 → 0.4.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.
Files changed (88) hide show
  1. package/README.md +269 -5
  2. package/dist/Sparkline.svelte +2 -2
  3. package/dist/TellegenMap.svelte +518 -202
  4. package/dist/TellegenMap.svelte.d.ts +45 -0
  5. package/dist/TellegenProvider.svelte +40 -4
  6. package/dist/TellegenShell.svelte +24 -46
  7. package/dist/TellegenViewer.svelte +12 -2
  8. package/dist/api.d.ts +18 -3
  9. package/dist/api.js +8 -2
  10. package/dist/basemap.d.ts +192 -0
  11. package/dist/basemap.js +259 -0
  12. package/dist/colors.d.ts +25 -7
  13. package/dist/colors.js +39 -13
  14. package/dist/components/AppFooter.svelte +3 -2
  15. package/dist/components/AppHeader.svelte +44 -19
  16. package/dist/components/BindingLines.svelte +4 -7
  17. package/dist/components/BusPicker.svelte +11 -5
  18. package/dist/components/ControlPanel.svelte +106 -343
  19. package/dist/components/DemandSlider.svelte +36 -29
  20. package/dist/components/DiagramCanvas.svelte +486 -0
  21. package/dist/components/DiagramCanvas.svelte.d.ts +13 -0
  22. package/dist/components/DisplayControls.svelte +8 -2
  23. package/dist/components/DropZone.svelte +1 -1
  24. package/dist/components/FormulationSelector.svelte +9 -4
  25. package/dist/components/LocalCaseDetails.svelte +61 -48
  26. package/dist/components/McResults.svelte +428 -0
  27. package/dist/components/McResults.svelte.d.ts +17 -0
  28. package/dist/components/ModelDetails.svelte +230 -0
  29. package/dist/components/ModelDetails.svelte.d.ts +7 -0
  30. package/dist/components/MulticonductorDetails.svelte +232 -83
  31. package/dist/components/NetworkStats.svelte +20 -18
  32. package/dist/components/NotificationCenter.svelte +245 -0
  33. package/dist/components/NotificationCenter.svelte.d.ts +3 -0
  34. package/dist/components/PanelCard.svelte +384 -0
  35. package/dist/components/PanelCard.svelte.d.ts +9 -0
  36. package/dist/components/PanelFrame.svelte +46 -0
  37. package/dist/components/PanelFrame.svelte.d.ts +16 -0
  38. package/dist/components/PanelHost.svelte +237 -0
  39. package/dist/components/PanelHost.svelte.d.ts +6 -0
  40. package/dist/components/PlacementCue.svelte +1 -1
  41. package/dist/components/PowerFlowDetails.svelte +99 -0
  42. package/dist/components/PowerFlowDetails.svelte.d.ts +3 -0
  43. package/dist/components/RatingSlider.svelte +19 -14
  44. package/dist/components/RestoreDefaultsButton.svelte +1 -1
  45. package/dist/components/SavedNetworkDetails.svelte +214 -0
  46. package/dist/components/SavedNetworkDetails.svelte.d.ts +3 -0
  47. package/dist/components/SensitivityReadout.svelte +26 -18
  48. package/dist/components/ShellEnvironment.svelte +51 -0
  49. package/dist/components/ShellEnvironment.svelte.d.ts +18 -0
  50. package/dist/components/SizeLegend.svelte +2 -2
  51. package/dist/components/SolveCard.svelte +48 -51
  52. package/dist/components/TopMovers.svelte +9 -4
  53. package/dist/components/TopMovers.svelte.d.ts +2 -17
  54. package/dist/components.d.ts +5 -0
  55. package/dist/components.js +5 -0
  56. package/dist/context.svelte.d.ts +8 -1
  57. package/dist/context.svelte.js +8 -3
  58. package/dist/controller.svelte.d.ts +117 -36
  59. package/dist/controller.svelte.js +1133 -253
  60. package/dist/diagram-labels.d.ts +9 -0
  61. package/dist/diagram-labels.js +40 -0
  62. package/dist/display.d.ts +3 -7
  63. package/dist/display.js +31 -27
  64. package/dist/format.d.ts +9 -0
  65. package/dist/format.js +17 -4
  66. package/dist/index.d.ts +15 -5
  67. package/dist/index.js +9 -2
  68. package/dist/map.d.ts +4 -2
  69. package/dist/map.js +2 -1
  70. package/dist/maplibre-worker.d.ts +5 -0
  71. package/dist/maplibre-worker.dev.d.ts +11 -0
  72. package/dist/maplibre-worker.dev.js +15 -0
  73. package/dist/maplibre-worker.js +8 -0
  74. package/dist/multiconductor.d.ts +11 -2
  75. package/dist/multiconductor.js +107 -14
  76. package/dist/notices.svelte.d.ts +29 -0
  77. package/dist/notices.svelte.js +86 -0
  78. package/dist/panels.svelte.d.ts +71 -0
  79. package/dist/panels.svelte.js +245 -0
  80. package/dist/slider.d.ts +12 -0
  81. package/dist/slider.js +31 -0
  82. package/dist/state.svelte.d.ts +107 -23
  83. package/dist/state.svelte.js +106 -20
  84. package/dist/styles.css +59 -6
  85. package/dist/synthetic-layout.js +1 -0
  86. package/package.json +29 -11
  87. package/dist/study-package.d.ts +0 -8
  88. package/dist/study-package.js +0 -17
@@ -0,0 +1,237 @@
1
+ <script lang="ts">
2
+ import { onMount, untrack } from 'svelte';
3
+ import { getAppState } from '../context.svelte.js';
4
+ import { getPanelLayout } from '../panels.svelte.js';
5
+ import { getNoticeCenter } from '../notices.svelte.js';
6
+ import PanelCard from './PanelCard.svelte';
7
+
8
+ // The dock insets come from `--tellegen-dock-left/right/bottom` on an
9
+ // ancestor (defaults 20px, 20px, 100px), so a custom shell with its own
10
+ // rail or status bar can move the docks without restyling them.
11
+ let { showToolbar = true }: { showToolbar?: boolean } = $props();
12
+
13
+ const app = getAppState();
14
+ const layout = getPanelLayout();
15
+ const notices = getNoticeCenter();
16
+ const bottomReserve = $derived(notices.entries.length ? 120 : 88);
17
+ const left = $derived(layout.ordered('left').filter((p) => p.open));
18
+ const right = $derived(layout.ordered('right').filter((p) => p.open));
19
+ const floating = $derived(
20
+ layout.ordered().filter((p) => p.open && layout.position(p) === 'floating')
21
+ );
22
+ const drawer = $derived(layout.panels.find((p) => p.id === layout.drawer));
23
+ const toolbarTop = $derived(app.headerInset + 6);
24
+ const panelTop = $derived(toolbarTop + 42);
25
+ const drawerHeight = $derived(
26
+ Math.max(
27
+ 1,
28
+ Math.min(
29
+ layout.viewportHeight * 0.6,
30
+ (layout.viewportHeight - panelTop - bottomReserve - 8) * 0.75
31
+ )
32
+ )
33
+ );
34
+ function resize() {
35
+ layout.resizeViewport(window.innerWidth, window.innerHeight, panelTop);
36
+ }
37
+ onMount(() => {
38
+ resize();
39
+ try {
40
+ layout.initialize(window.localStorage);
41
+ } catch {
42
+ layout.initialize();
43
+ }
44
+ });
45
+ $effect(() => {
46
+ const top = panelTop;
47
+ const bottom = bottomReserve;
48
+ untrack(() => {
49
+ layout.bottom = bottom;
50
+ layout.resizeViewport(window.innerWidth, window.innerHeight, top);
51
+ });
52
+ });
53
+ $effect(() => {
54
+ app.sheetInset = layout.compact && drawer ? drawerHeight + bottomReserve : 0;
55
+ });
56
+ </script>
57
+
58
+ <svelte:window
59
+ onresize={resize}
60
+ onpointermove={(event) => layout.moveGesture(event.clientX, event.clientY)}
61
+ onpointerup={() => layout.finishGesture()}
62
+ onpointercancel={() => layout.finishGesture()}
63
+ />
64
+ {#if showToolbar}
65
+ <div class="panel-toolbar" style:top="{toolbarTop}px" aria-label="Panels">
66
+ <div class="panel-buttons">
67
+ {#each layout.ordered().filter((p) => p.side === 'left') as panel (panel.id)}
68
+ <button
69
+ aria-label={panel.title}
70
+ aria-pressed={layout.compact ? layout.drawer === panel.id : panel.open}
71
+ onclick={() => layout.toggle(panel)}>{panel.title}</button
72
+ >
73
+ {/each}
74
+ </div>
75
+ <div class="panel-buttons right">
76
+ {#each layout.ordered().filter((p) => p.side === 'right') as panel (panel.id)}
77
+ <button
78
+ aria-label={panel.title}
79
+ aria-pressed={layout.compact ? layout.drawer === panel.id : panel.open}
80
+ onclick={() => layout.toggle(panel)}>{panel.title}</button
81
+ >
82
+ {/each}
83
+ <button
84
+ class="reset-layout"
85
+ onclick={() => layout.reset()}
86
+ title="Restore the default panel positions">Reset layout</button
87
+ >
88
+ </div>
89
+ </div>
90
+ {/if}
91
+ {#if layout.storageError}<p class="layout-error" role="status" style:top="{panelTop}px">
92
+ {layout.storageError}
93
+ </p>{/if}
94
+ <!-- The docks and the drawer take focus (tabindex -1) when a closed panel has
95
+ no toolbar button or opener to hand it back to. The drawer stays rendered,
96
+ at zero height, while no panel is open in it. -->
97
+ {#if layout.compact}
98
+ <div
99
+ class="drawer"
100
+ data-panel-drawer
101
+ role="region"
102
+ aria-label="Panels"
103
+ tabindex="-1"
104
+ style:bottom="{bottomReserve}px"
105
+ style:height="{drawer ? drawerHeight : 0}px"
106
+ >
107
+ {#if drawer}<PanelCard panel={drawer} mobile />{/if}
108
+ </div>
109
+ {:else}
110
+ <div
111
+ class="dock left"
112
+ style:bottom="max(var(--tellegen-dock-bottom, 100px), {bottomReserve}px)"
113
+ data-panel-dock="left"
114
+ role="region"
115
+ aria-label="Left panels"
116
+ tabindex="-1"
117
+ style:top="{panelTop}px"
118
+ style:width="{layout.dockWidth('left')}px"
119
+ >
120
+ {#each left as panel (panel.id)}<PanelCard {panel} />{/each}
121
+ </div>
122
+ <div
123
+ class="dock right"
124
+ style:bottom="max(var(--tellegen-dock-bottom, 100px), {bottomReserve}px)"
125
+ data-panel-dock="right"
126
+ role="region"
127
+ aria-label="Right panels"
128
+ tabindex="-1"
129
+ style:top="{panelTop}px"
130
+ style:width="{layout.dockWidth('right')}px"
131
+ >
132
+ {#each right as panel (panel.id)}<PanelCard {panel} />{/each}
133
+ </div>
134
+ {#each floating as panel (panel.id)}<PanelCard {panel} floating />{/each}
135
+ {/if}
136
+
137
+ <style>
138
+ .panel-toolbar {
139
+ position: fixed;
140
+ z-index: 30;
141
+ left: var(--tellegen-dock-left, 20px);
142
+ right: var(--tellegen-dock-right, 20px);
143
+ display: flex;
144
+ justify-content: space-between;
145
+ align-items: center;
146
+ gap: 12px;
147
+ pointer-events: none;
148
+ height: 34px;
149
+ }
150
+ .panel-buttons {
151
+ display: flex;
152
+ gap: 6px;
153
+ align-items: center;
154
+ pointer-events: auto;
155
+ }
156
+ button {
157
+ border: 1px solid var(--line);
158
+ border-radius: 4px;
159
+ background: var(--paper);
160
+ color: var(--ink);
161
+ font: 12px/1.4 var(--font-display);
162
+ min-height: 32px;
163
+ padding: 6px 12px;
164
+ cursor: pointer;
165
+ box-shadow: var(--elev-1);
166
+ }
167
+ button[aria-pressed='true'] {
168
+ border-color: var(--accent);
169
+ background: var(--accent-soft);
170
+ }
171
+ button:focus-visible {
172
+ outline: 2px solid var(--focus-ring);
173
+ outline-offset: 2px;
174
+ }
175
+ button:hover {
176
+ border-color: var(--accent);
177
+ }
178
+ .reset-layout {
179
+ color: var(--text-secondary);
180
+ font-size: 11px;
181
+ padding: 6px 8px;
182
+ background: var(--panel);
183
+ }
184
+ .dock {
185
+ position: fixed;
186
+ z-index: 25;
187
+ bottom: var(--tellegen-dock-bottom, 100px);
188
+ display: flex;
189
+ flex-direction: column;
190
+ align-items: stretch;
191
+ gap: 12px;
192
+ pointer-events: none;
193
+ }
194
+ .dock.left {
195
+ left: var(--tellegen-dock-left, 20px);
196
+ }
197
+ .dock.right {
198
+ right: var(--tellegen-dock-right, 20px);
199
+ }
200
+ .drawer {
201
+ position: fixed;
202
+ bottom: 88px;
203
+ left: 12px;
204
+ right: 12px;
205
+ z-index: 25;
206
+ }
207
+ .layout-error {
208
+ position: fixed;
209
+ left: 50%;
210
+ transform: translateX(-50%);
211
+ z-index: 42;
212
+ max-width: 400px;
213
+ margin: 0;
214
+ padding: 8px 12px;
215
+ font: 12px/1.5 var(--font-display);
216
+ color: var(--ink);
217
+ background: var(--paper);
218
+ border: 1px solid var(--line);
219
+ }
220
+ @media (max-width: 960px) {
221
+ .panel-toolbar {
222
+ left: var(--tellegen-dock-left, 12px);
223
+ right: var(--tellegen-dock-right, 12px);
224
+ gap: 6px;
225
+ }
226
+ .panel-buttons {
227
+ gap: 4px;
228
+ }
229
+ button {
230
+ padding: 6px 8px;
231
+ font-size: 11px;
232
+ }
233
+ .reset-layout {
234
+ display: none;
235
+ }
236
+ }
237
+ </style>
@@ -0,0 +1,6 @@
1
+ type $$ComponentProps = {
2
+ showToolbar?: boolean;
3
+ };
4
+ declare const PanelHost: import("svelte").Component<$$ComponentProps, {}, "">;
5
+ type PanelHost = ReturnType<typeof PanelHost>;
6
+ export default PanelHost;
@@ -21,7 +21,7 @@
21
21
  border-radius: 3px;
22
22
  color: var(--text-accent);
23
23
  font-size: 11px;
24
- box-shadow: 0 4px 18px rgba(32, 36, 43, 0.1);
24
+ box-shadow: var(--elev-cue);
25
25
  pointer-events: none;
26
26
  }
27
27
 
@@ -0,0 +1,99 @@
1
+ <script lang="ts">
2
+ import { getController } from '../context.svelte.js';
3
+ import { caseDeltas } from '../display.js';
4
+ import { fmt } from '../format.js';
5
+
6
+ const ctrl = getController();
7
+ const active = $derived(ctrl.activeSolvable);
8
+ const bus = $derived(ctrl.selectedBusData);
9
+ const branch = $derived(ctrl.selectedBranchData);
10
+ const voltage = $derived(active?.solution?.vm?.find((value) => value.bus === bus?.id)?.value);
11
+ const angle = $derived(active?.solution?.va.find((value) => value.bus === bus?.id)?.value);
12
+ const flow = $derived(active?.solution?.flows.find((value) => value.branch === branch?.id));
13
+ const demand = $derived.by(() => {
14
+ if (!bus || !active) return null;
15
+ const deltas = caseDeltas(active);
16
+ return bus.demand_mw + (deltas[bus.uid ?? bus.id] ?? deltas[bus.id] ?? 0);
17
+ });
18
+ </script>
19
+
20
+ {#if bus || branch}
21
+ <section class="equipment" aria-label="Power-flow equipment results">
22
+ {#if bus}
23
+ <h3>
24
+ Bus {bus.id}{#if bus.name}<span>{bus.name}</span>{/if}
25
+ </h3>
26
+ <dl>
27
+ {#if demand !== null}<div>
28
+ <dt>Demand</dt>
29
+ <dd>{fmt.format(demand)} MW</dd>
30
+ </div>{/if}
31
+ {#if voltage != null}<div>
32
+ <dt>Voltage</dt>
33
+ <dd>{voltage.toFixed(4)} pu</dd>
34
+ </div>{/if}
35
+ {#if angle != null}<div>
36
+ <dt>Voltage angle</dt>
37
+ <dd>{angle.toFixed(4)} rad</dd>
38
+ </div>{/if}
39
+ </dl>
40
+ {:else if branch}
41
+ <h3>Line {branch.id}<span>Bus {branch.from} to bus {branch.to}</span></h3>
42
+ <dl>
43
+ <div>
44
+ <dt>Capacity</dt>
45
+ <dd>{branch.rate_mw > 0 ? `${fmt.format(branch.rate_mw)} MVA` : 'Unlimited'}</dd>
46
+ </div>
47
+ {#if flow}
48
+ <div>
49
+ <dt>Active power</dt>
50
+ <dd>{fmt.format(flow.mw)} MW</dd>
51
+ </div>
52
+ {#if branch.rate_mw > 0}<div>
53
+ <dt>Loading</dt>
54
+ <dd>{fmt.format(flow.loading * 100)}%</dd>
55
+ </div>{/if}
56
+ {/if}
57
+ </dl>
58
+ {/if}
59
+ </section>
60
+ {/if}
61
+
62
+ <style>
63
+ .equipment {
64
+ border-top: 1px solid var(--line);
65
+ margin-top: 14px;
66
+ padding-top: 14px;
67
+ }
68
+ h3 {
69
+ margin: 0 0 12px;
70
+ font-size: 13px;
71
+ font-weight: 600;
72
+ }
73
+ h3 span {
74
+ display: block;
75
+ margin-top: 3px;
76
+ font-size: 13px;
77
+ font-weight: 400;
78
+ color: var(--text-secondary);
79
+ }
80
+ dl {
81
+ margin: 0;
82
+ display: grid;
83
+ gap: 6px;
84
+ font: 12px/1.5 var(--font-mono);
85
+ }
86
+ dl > div {
87
+ display: flex;
88
+ justify-content: space-between;
89
+ align-items: baseline;
90
+ gap: 12px;
91
+ }
92
+ dt {
93
+ color: var(--text-secondary);
94
+ }
95
+ dd {
96
+ margin: 0;
97
+ text-align: right;
98
+ }
99
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const PowerFlowDetails: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type PowerFlowDetails = ReturnType<typeof PowerFlowDetails>;
3
+ export default PowerFlowDetails;
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { getController } from '../context.svelte.js';
3
3
  import { fmt, signed } from '../format.js';
4
+ import { sliderKeyValue } from '../slider.js';
4
5
 
5
6
  const ctrl = getController();
6
7
 
@@ -25,7 +26,7 @@
25
26
  'The black tick marks the last committed rating change. Drag the knob to preview a new thermal rating; release to solve and move the tick to that point.';
26
27
  const disabledTip = "this line's rating is synthesized; not adjustable";
27
28
  const scoreTip =
28
- 'Gradient is the estimate of total cost change versus base before the solve finishes, from the engine preview at the committed point. Exact is the resolved OPF objective change.';
29
+ 'Gradient estimates the declared objective change versus base before the solve finishes, from the engine preview at the committed point. Exact is the resolved OPF objective change.';
29
30
  </script>
30
31
 
31
32
  {#if ctrl.activeSolvable}
@@ -39,9 +40,7 @@
39
40
  <div class="line-row mono">
40
41
  {#if branch}
41
42
  <span class="dim"
42
- >line {branch.from}&#8201;&ndash;&#8201;{branch.to} &middot; {fmt.format(
43
- branch.rate_mw
44
- )} MW</span
43
+ >line {branch.from}&#8201;&ndash;&#8201;{branch.to}, {fmt.format(branch.rate_mw)} MW</span
45
44
  >
46
45
  {/if}
47
46
  <span class="dim">
@@ -66,7 +65,15 @@
66
65
  bind:value={ctrl.ratingSliderCurrent, ctrl.setRatingPreview}
67
66
  aria-label="rating delta at selected line"
68
67
  onpointerdown={() => ctrl.setRatingPreview(ctrl.ratingSliderValue)}
69
- onkeydown={() => ctrl.setRatingPreview(ctrl.ratingSliderValue)}
68
+ onkeydown={(e) => {
69
+ const next = sliderKeyValue(e.key, ctrl.ratingSliderValue, bounds.min, bounds.max);
70
+ if (next === null) {
71
+ ctrl.setRatingPreview(ctrl.ratingSliderValue);
72
+ return;
73
+ }
74
+ e.preventDefault();
75
+ ctrl.setRatingPreview(next);
76
+ }}
70
77
  onpointerup={(e) => ctrl.finishRatingInput(Number(e.currentTarget.value))}
71
78
  onkeyup={(e) => ctrl.finishRatingInput(Number(e.currentTarget.value))}
72
79
  onchange={(e) => ctrl.finishRatingInput(Number(e.currentTarget.value))}
@@ -75,7 +82,7 @@
75
82
  <div class="rating-feedback" class:idle={!ctrl.previewing && !ctrl.isPerturbed(c)}>
76
83
  <p class="pred mono dim" aria-hidden={!(ctrl.predictedDeltaObj !== null && ctrl.previewing)}>
77
84
  {#if ctrl.predictedDeltaObj !== null && ctrl.previewing}
78
- predicted &Delta;cost {signed(ctrl.predictedDeltaObj)} $/h
85
+ predicted &Delta;objective {signed(ctrl.predictedDeltaObj)}
79
86
  {:else}
80
87
  &nbsp;
81
88
  {/if}
@@ -83,10 +90,8 @@
83
90
  <p class="score mono" aria-hidden={!(ctrl.gradientScore && ctrl.isPerturbed(c))}>
84
91
  {#if ctrl.gradientScore && ctrl.isPerturbed(c)}
85
92
  <span title={scoreTip}>
86
- gradient {signed(ctrl.gradientScore.pred)} &middot; exact {signed(
87
- ctrl.gradientScore.exact
88
- )}
89
- $/h
93
+ gradient {signed(ctrl.gradientScore.pred)}, exact {signed(ctrl.gradientScore.exact)} objective
94
+ units
90
95
  </span>
91
96
  {:else}
92
97
  &nbsp;
@@ -109,7 +114,7 @@
109
114
  .slider-head {
110
115
  display: flex;
111
116
  justify-content: space-between;
112
- font-size: 11.5px;
117
+ font-size: 13.5px;
113
118
  color: var(--text-secondary);
114
119
  margin-bottom: 4px;
115
120
  }
@@ -124,7 +129,7 @@
124
129
  justify-content: space-between;
125
130
  gap: 8px;
126
131
  margin: 6px 0 7px;
127
- font-size: 10.5px;
132
+ font-size: 12.5px;
128
133
  }
129
134
 
130
135
  .slider-track {
@@ -240,13 +245,13 @@
240
245
 
241
246
  .pred {
242
247
  margin: 2px 0 0;
243
- font-size: 11px;
248
+ font-size: 13px;
244
249
  min-height: 16px;
245
250
  }
246
251
 
247
252
  .score {
248
253
  margin: 8px 0 0;
249
- font-size: 11px;
254
+ font-size: 13px;
250
255
  color: var(--ink);
251
256
  min-height: 16px;
252
257
  }
@@ -24,7 +24,7 @@
24
24
  color: var(--text-secondary);
25
25
  font-size: 11px;
26
26
  cursor: pointer;
27
- box-shadow: 0 2px 10px rgba(32, 36, 43, 0.1);
27
+ box-shadow: var(--elev-1);
28
28
  }
29
29
 
30
30
  .restore-defaults:hover {
@@ -0,0 +1,214 @@
1
+ <script lang="ts">
2
+ import { getAppState } from '../context.svelte.js';
3
+ import { fmt, formulationLabel, signed } from '../format.js';
4
+ import BusPicker from './BusPicker.svelte';
5
+ import ModelDetails from './ModelDetails.svelte';
6
+
7
+ const app = getAppState();
8
+ const saved = $derived(app.studyView);
9
+ const bus = $derived(saved?.network.buses.find((value) => value.id === app.selectedBus));
10
+ const branch = $derived(saved?.network.branches.find((value) => value.id === app.selectedBranch));
11
+ const lmp = $derived(saved?.solution?.lmp?.find((value) => value.bus === bus?.id)?.value);
12
+ const voltage = $derived(saved?.solution?.vm?.find((value) => value.bus === bus?.id)?.value);
13
+ const squaredVoltage = $derived(
14
+ saved?.solution?.w?.find((value) => value.bus === bus?.id)?.value
15
+ );
16
+ const angle = $derived(saved?.solution?.va?.find((value) => value.bus === bus?.id)?.value);
17
+ const flow = $derived(saved?.solution?.flows?.find((value) => value.branch === branch?.id));
18
+ const baseDemand = $derived(bus ? saved?.baseDemandMw?.[String(bus.id)] : undefined);
19
+ function live() {
20
+ app.studyView = null;
21
+ app.selectedBus = null;
22
+ app.selectedBranch = null;
23
+ }
24
+ </script>
25
+
26
+ {#if saved}
27
+ <section aria-label="Saved network details">
28
+ <h2>{saved.network.name}</h2>
29
+ <p class="state-label">{saved.label}<span>Saved state</span></p>
30
+ <dl>
31
+ <div>
32
+ <dt>Buses</dt>
33
+ <dd>{saved.network.buses.length.toLocaleString()}</dd>
34
+ </div>
35
+ <div>
36
+ <dt>Lines</dt>
37
+ <dd>{saved.network.branches.length.toLocaleString()}</dd>
38
+ </div>
39
+ <div>
40
+ <dt>Calculation</dt>
41
+ <dd>{formulationLabel(saved.formulation)}</dd>
42
+ </div>
43
+ <div>
44
+ <dt>Result</dt>
45
+ <dd>{saved.solution?.status ?? 'Not solved'}</dd>
46
+ </div>
47
+ {#if saved.solution?.objective != null}
48
+ <div>
49
+ <dt>OPF objective</dt>
50
+ <dd>{fmt.format(saved.solution.objective)}</dd>
51
+ </div>
52
+ {/if}
53
+ </dl>
54
+ <button class="live" onclick={live}>Return to live case</button>
55
+ <ModelDetails details={saved.network.model_details} />
56
+ <BusPicker inline />
57
+ {#if saved.solution}
58
+ <label class="map-values"
59
+ >{saved.network.coordinate_space === 'diagram' ? 'Diagram values' : 'Map values'}
60
+ <select bind:value={app.displayMode}>
61
+ {#if saved.solution.lmp?.length}<option value="price">LMP</option>{/if}
62
+ {#if saved.solution.va?.length}<option value="angle">Voltage angle</option>{/if}
63
+ {#if saved.solution.vm?.length || saved.solution.w?.length}<option value="voltage"
64
+ >Voltage magnitude</option
65
+ >{/if}
66
+ </select>
67
+ </label>
68
+ {/if}
69
+ {#if bus}
70
+ <div class="equipment">
71
+ <h3>
72
+ Bus {bus.id}{#if bus.name}<span>{bus.name}</span>{/if}
73
+ </h3>
74
+ <dl>
75
+ {#if baseDemand != null}<div>
76
+ <dt>Base demand</dt>
77
+ <dd>{fmt.format(baseDemand)} MW</dd>
78
+ </div>{/if}
79
+ <div>
80
+ <dt>Current demand</dt>
81
+ <dd>{fmt.format(bus.demand_mw)} MW</dd>
82
+ </div>
83
+ {#if baseDemand != null}<div>
84
+ <dt>Change</dt>
85
+ <dd>{signed(bus.demand_mw - baseDemand)} MW</dd>
86
+ </div>{/if}
87
+ {#if lmp != null}<div>
88
+ <dt>LMP</dt>
89
+ <dd>{fmt.format(lmp)}<small>objective units/MW</small></dd>
90
+ </div>{/if}
91
+ {#if voltage != null || squaredVoltage != null}<div>
92
+ <dt>Voltage</dt>
93
+ <dd>{(voltage ?? Math.sqrt(Math.max(0, squaredVoltage!))).toFixed(4)} pu</dd>
94
+ </div>{/if}
95
+ {#if angle != null}<div>
96
+ <dt>Voltage angle</dt>
97
+ <dd>{angle.toFixed(4)} rad</dd>
98
+ </div>{/if}
99
+ </dl>
100
+ </div>
101
+ {:else if branch}
102
+ <div class="equipment">
103
+ <h3>Line {branch.id}<span>Bus {branch.from} to bus {branch.to}</span></h3>
104
+ <dl>
105
+ <div>
106
+ <dt>Capacity</dt>
107
+ <dd>
108
+ {branch.rate_mw > 0
109
+ ? `${fmt.format(branch.rate_mw)} ${saved.formulation === 'dcopf' ? 'MW' : 'MVA'}`
110
+ : 'Unlimited'}
111
+ </dd>
112
+ </div>
113
+ {#if flow}<div>
114
+ <dt>Active power</dt>
115
+ <dd>{fmt.format(flow.pf)} MW</dd>
116
+ </div>
117
+ <div>
118
+ <dt>Loading</dt>
119
+ <dd>{fmt.format(flow.loading * 100)}%</dd>
120
+ </div>{/if}
121
+ </dl>
122
+ </div>
123
+ {/if}
124
+ </section>
125
+ {/if}
126
+
127
+ <style>
128
+ h2 {
129
+ margin: 0 0 8px;
130
+ font-size: 15px;
131
+ font-weight: 600;
132
+ }
133
+ .state-label {
134
+ display: flex;
135
+ justify-content: space-between;
136
+ gap: 12px;
137
+ margin: 0 0 16px;
138
+ font-size: 12px;
139
+ }
140
+ .state-label span {
141
+ flex: none;
142
+ color: var(--text-secondary);
143
+ font-size: 13px;
144
+ }
145
+ dl {
146
+ margin: 0;
147
+ display: grid;
148
+ gap: 6px;
149
+ font: 12px/1.5 var(--font-mono);
150
+ }
151
+ dl > div {
152
+ display: flex;
153
+ justify-content: space-between;
154
+ align-items: baseline;
155
+ gap: 12px;
156
+ }
157
+ dt {
158
+ color: var(--text-secondary);
159
+ }
160
+ dd {
161
+ margin: 0;
162
+ text-align: right;
163
+ }
164
+ small {
165
+ display: block;
166
+ font-size: 12px;
167
+ color: var(--text-secondary);
168
+ }
169
+ .live {
170
+ margin: 16px 0;
171
+ }
172
+ button,
173
+ select {
174
+ border: 1px solid var(--line);
175
+ border-radius: 4px;
176
+ color: var(--ink);
177
+ background: var(--paper);
178
+ padding: 6px 8px;
179
+ font: 12px/1.4 var(--font-display);
180
+ }
181
+ button {
182
+ cursor: pointer;
183
+ }
184
+ button:focus-visible,
185
+ select:focus-visible {
186
+ outline: 2px solid var(--focus-ring);
187
+ outline-offset: 2px;
188
+ }
189
+ .map-values {
190
+ display: flex;
191
+ justify-content: space-between;
192
+ align-items: center;
193
+ gap: 12px;
194
+ margin: 16px 0;
195
+ font-size: 12px;
196
+ }
197
+ .equipment {
198
+ border-top: 1px solid var(--line);
199
+ margin-top: 16px;
200
+ padding-top: 16px;
201
+ }
202
+ h3 {
203
+ margin: 0 0 12px;
204
+ font-size: 13px;
205
+ font-weight: 600;
206
+ }
207
+ h3 span {
208
+ display: block;
209
+ margin-top: 3px;
210
+ color: var(--text-secondary);
211
+ font-size: 13px;
212
+ font-weight: 400;
213
+ }
214
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const SavedNetworkDetails: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type SavedNetworkDetails = ReturnType<typeof SavedNetworkDetails>;
3
+ export default SavedNetworkDetails;