@tellegen/svelte 0.4.1 → 0.4.3

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 (35) hide show
  1. package/README.md +38 -10
  2. package/dist/Sparkline.svelte +4 -4
  3. package/dist/TellegenMap.svelte +41 -46
  4. package/dist/TellegenProvider.svelte +29 -13
  5. package/dist/components/AppFooter.svelte +4 -4
  6. package/dist/components/AppHeader.svelte +15 -19
  7. package/dist/components/BindingLines.svelte +10 -9
  8. package/dist/components/BusPicker.svelte +5 -6
  9. package/dist/components/ControlPanel.svelte +12 -9
  10. package/dist/components/ControlPanel.svelte.d.ts +6 -1
  11. package/dist/components/DemandSlider.svelte +24 -29
  12. package/dist/components/DisplayControls.svelte +5 -24
  13. package/dist/components/DropZone.svelte +7 -3
  14. package/dist/components/DropZone.svelte.d.ts +6 -17
  15. package/dist/components/FormulationSelector.svelte +2 -7
  16. package/dist/components/LocalCaseDetails.svelte +126 -126
  17. package/dist/components/LocalCaseDetails.svelte.d.ts +6 -1
  18. package/dist/components/MulticonductorDetails.svelte +48 -38
  19. package/dist/components/NetworkStats.svelte +14 -16
  20. package/dist/components/PanelCard.svelte +1 -1
  21. package/dist/components/PlacementCue.svelte +1 -1
  22. package/dist/components/RatingSlider.svelte +22 -23
  23. package/dist/components/RestoreDefaultsButton.svelte +2 -3
  24. package/dist/components/SensitivityReadout.svelte +9 -27
  25. package/dist/components/SolveCard.svelte +69 -35
  26. package/dist/components/TopMovers.svelte +5 -5
  27. package/dist/context.svelte.d.ts +6 -2
  28. package/dist/context.svelte.js +4 -2
  29. package/dist/controller.svelte.d.ts +7 -0
  30. package/dist/controller.svelte.js +33 -6
  31. package/dist/display.js +8 -9
  32. package/dist/format.d.ts +1 -9
  33. package/dist/format.js +0 -13
  34. package/dist/styles.css +3 -0
  35. package/package.json +10 -6
@@ -46,51 +46,60 @@
46
46
  {#if app.activeMulti}
47
47
  {@const mc = app.activeMulti}
48
48
  {@const s = mc.summary}
49
- <h2>{mc.label} <span class="region mono">via {mc.fileName}</span></h2>
49
+ <h2>{mc.label}</h2>
50
50
  {#if s}
51
- <p class="tag mono">Multiconductor</p>
51
+ <p class="tag">Multiconductor</p>
52
52
  <details class="case-details">
53
53
  <summary>{s.n_bus} buses, {s.n_edge} {s.n_edge === 1 ? 'branch' : 'branches'}</summary>
54
- <dl class="mono">
54
+ <dl>
55
55
  <div>
56
- <dt>buses</dt>
57
- <dd>{s.n_bus}</dd>
56
+ <dt>Buses</dt>
57
+ <dd class="mono">{s.n_bus}</dd>
58
58
  </div>
59
59
  <div>
60
60
  <dt>Lines / switches / transformers</dt>
61
- <dd>{s.n_line} / {s.n_switch} / {s.n_transformer}</dd>
61
+ <dd class="mono">{s.n_line} / {s.n_switch} / {s.n_transformer}</dd>
62
62
  </div>
63
63
  <div>
64
- <dt>load</dt>
65
- <dd>{fmt.format(s.load_kw)} kW</dd>
64
+ <dt>Load</dt>
65
+ <dd class="mono">{fmt.format(s.load_kw)} kW</dd>
66
66
  </div>
67
67
  <div>
68
68
  <dt>Generation capacity</dt>
69
- <dd>{fmt.format(s.gen_kw)} kW</dd>
69
+ <dd class="mono">{fmt.format(s.gen_kw)} kW</dd>
70
70
  </div>
71
71
  <div>
72
- <dt>sources / loads / gens</dt>
73
- <dd>{s.n_source} / {s.n_load} / {s.n_generator}</dd>
72
+ <dt>Sources / loads / generators</dt>
73
+ <dd class="mono">{s.n_source} / {s.n_load} / {s.n_generator}</dd>
74
74
  </div>
75
75
  {#if s.n_ibr > 0 || s.n_shunt > 0 || (s.n_capacitor ?? 0) > 0}
76
76
  <div>
77
- <dt>IBRs / shunts / caps</dt>
78
- <dd>{s.n_ibr} / {s.n_shunt} / {s.n_capacitor ?? 0}</dd>
77
+ <dt>IBRs / shunts / capacitors</dt>
78
+ <dd class="mono">{s.n_ibr} / {s.n_shunt} / {s.n_capacitor ?? 0}</dd>
79
79
  </div>
80
80
  {/if}
81
+ <div>
82
+ <dt>File</dt>
83
+ <dd class="mono">{mc.fileName}</dd>
84
+ </div>
81
85
  </dl>
82
86
  </details>
83
87
 
84
- <p class="footnote mono">
85
- {mc.coordsKind === 'geographic'
86
- ? 'Geographic coordinates'
87
- : mc.coordsKind === 'planar'
88
- ? 'Drawing coordinates'
89
- : 'Generated diagram'}
90
- </p>
88
+ <dl>
89
+ <div>
90
+ <dt>Coordinates</dt>
91
+ <dd>
92
+ {mc.coordsKind === 'geographic'
93
+ ? 'Geographic'
94
+ : mc.coordsKind === 'planar'
95
+ ? 'Drawing'
96
+ : 'Generated diagram'}
97
+ </dd>
98
+ </div>
99
+ </dl>
91
100
  <div class="calculation-actions">
92
101
  {#if mc.solving}
93
- <span role="status">Calculating...</span>{#if mc.solveAbort}<button
102
+ <span role="status">Calculating&hellip;</span>{#if mc.solveAbort}<button
94
103
  onclick={() => mc.solveAbort?.abort()}>Cancel</button
95
104
  >{/if}
96
105
  {:else}
@@ -110,27 +119,27 @@
110
119
  notices.push({
111
120
  kind: 'warning',
112
121
  title: 'AC power flow unavailable',
113
- details: mc.mcPfReason ?? 'This case cannot run AC power flow in this browser'
122
+ details: mc.mcPfReason ?? 'AC power flow does not support this case'
114
123
  })}>Why unavailable</button
115
124
  >
116
125
  {/if}
117
126
  {/if}
118
127
  </div>
119
128
  {#if mc.result}<p class="footnote">Converged, {mc.result.iterations} iterations</p>
120
- <button class="reset mono" onclick={openResults}>View results in Studies</button>{/if}
129
+ <button class="reset" onclick={openResults}>View results in Studies</button>{/if}
121
130
  {/if}
122
131
 
123
132
  {#if mc.selectedBus}
124
133
  {@const b = mc.selectedBus}
125
134
  <hr />
126
- <h3 class="mono">bus {b.id}</h3>
135
+ <h3>Bus <span class="mono">{b.id}</span></h3>
127
136
  <div class="terminals">
128
137
  {#each b.terminals as t (t)}
129
138
  <span
130
139
  class="term"
131
140
  class:grounded={b.grounded.includes(t)}
132
141
  style={`--tc:${terminalColor(t)}`}
133
- title={b.grounded.includes(t) ? `terminal ${t} (grounded)` : `terminal ${t}`}
142
+ title={b.grounded.includes(t) ? `Terminal ${t}, grounded` : `Terminal ${t}`}
134
143
  >
135
144
  {t}{#if b.grounded.includes(t)}<span class="gnd" aria-hidden="true">&#9178;</span>{/if}
136
145
  </span>
@@ -145,7 +154,7 @@
145
154
  {/each}
146
155
  </div>
147
156
  {:else}
148
- <p class="footnote mono">no attachments on this bus</p>
157
+ <p class="footnote">No attachments</p>
149
158
  {/if}
150
159
  {@const loadBranches = mc.mcLoadBranches.filter((load) => load.bus === b.id)}
151
160
  {#if loadBranches.length > 0}
@@ -154,7 +163,7 @@
154
163
  {#each loadBranches as load (`${load.load}:${load.branch}`)}
155
164
  <div class="load-row">
156
165
  <span class="mono"
157
- >{load.load}{loadBranches.length > 1 ? ` · ${load.branch + 1}` : ''}</span
166
+ >{load.load}{loadBranches.length > 1 ? ` (${load.branch + 1})` : ''}</span
158
167
  >
159
168
  <label
160
169
  >P kW<input
@@ -200,7 +209,7 @@
200
209
  >
201
210
  </div>
202
211
  {/each}
203
- <p class="footnote">Changes re-solve automatically using the retained factorization.</p>
212
+ <p class="footnote">Changes solve automatically.</p>
204
213
  </div>
205
214
  {/if}
206
215
  {:else if mc.selectedEdge}
@@ -211,15 +220,19 @@
211
220
  {e.kind}
212
221
  {e.from}&#8201;&ndash;&#8201;{e.to}
213
222
  </h3>
214
- <dl class="mono">
223
+ <dl>
224
+ <div>
225
+ <dt>Phases</dt>
226
+ <dd class="mono">{e.n_phases}</dd>
227
+ </div>
215
228
  <div>
216
- <dt>phases</dt>
217
- <dd>{e.n_phases}&#966; , {e.conductors.length} conductors</dd>
229
+ <dt>Conductors</dt>
230
+ <dd class="mono">{e.conductors.length}</dd>
218
231
  </div>
219
232
  {#if e.kind === 'switch'}
220
233
  <div>
221
- <dt>state</dt>
222
- <dd>{e.closed ? 'closed' : 'open'}</dd>
234
+ <dt>State</dt>
235
+ <dd>{e.closed ? 'Closed' : 'Open'}</dd>
223
236
  </div>
224
237
  {/if}
225
238
  </dl>
@@ -232,8 +245,6 @@
232
245
  </span>
233
246
  {/each}
234
247
  </div>
235
- {:else if mc.placed}
236
- <p class="footnote mono">select a bus or a line to expand its detail</p>
237
248
  {/if}
238
249
 
239
250
  <div class="mc-legend mono">
@@ -251,8 +262,8 @@
251
262
  </span>
252
263
  </div>
253
264
 
254
- <button class="reset mono" onclick={() => app.requestFrame(mc.id)}>Fit case</button>
255
- <button class="reset mono" onclick={() => ctrl.removeMultiCase(mc)}>remove</button>
265
+ <button class="reset" onclick={() => app.requestFrame(mc.id)}>Fit case</button>
266
+ <button class="reset" onclick={() => ctrl.removeMultiCase(mc)}>Remove</button>
256
267
  {/if}
257
268
 
258
269
  <style>
@@ -309,7 +320,6 @@
309
320
  font-size: 12px;
310
321
  color: var(--text-secondary);
311
322
  letter-spacing: 0;
312
- text-transform: uppercase;
313
323
  }
314
324
 
315
325
  dl {
@@ -11,34 +11,32 @@
11
11
  {@const [cname, cregion] = splitName(app.active?.name ?? '')}
12
12
  {@const deltaObjective = stats.deltaObjective}
13
13
  <h2>{cname} <span class="region mono">{cregion}</span></h2>
14
- <dl class="mono">
14
+ <dl>
15
15
  <div>
16
- <dt>buses</dt>
17
- <dd>{stats.buses}</dd>
16
+ <dt>Buses</dt>
17
+ <dd class="mono">{stats.buses}</dd>
18
18
  </div>
19
19
  <div>
20
- <dt>lines</dt>
21
- <dd>{stats.branches}</dd>
20
+ <dt>Lines</dt>
21
+ <dd class="mono">{stats.branches}</dd>
22
22
  </div>
23
23
  {#if ctrl.activeFormulation !== 'acpf'}
24
24
  <div>
25
- <dt>binding lines</dt>
26
- <dd>{stats.binding ?? '...'}</dd>
25
+ <dt>Binding lines</dt>
26
+ <dd class="mono">{stats.binding ?? '…'}</dd>
27
27
  </div>
28
28
  <div>
29
29
  <dt>OPF objective</dt>
30
- <dd>
31
- {#if stats.objective === null}
32
- <span class="blink">solving...</span>
33
- {:else}
34
- {fmt.format(stats.objective)}
35
- {/if}
36
- </dd>
30
+ {#if stats.objective === null}
31
+ <dd class="blink">Solving&hellip;</dd>
32
+ {:else}
33
+ <dd class="mono">{fmt.format(stats.objective)}</dd>
34
+ {/if}
37
35
  </div>
38
36
  {#if ctrl.isPerturbed(ctrl.activeSolvable) && deltaObjective !== null}
39
37
  <div class="delta">
40
- <dt>vs base</dt>
41
- <dd>{signed(deltaObjective)}</dd>
38
+ <dt>Change from base</dt>
39
+ <dd class="mono">{signed(deltaObjective)}</dd>
42
40
  </div>
43
41
  {/if}
44
42
  {/if}
@@ -158,7 +158,7 @@
158
158
  <button
159
159
  class="title"
160
160
  aria-label={`Move ${panel.title} panel`}
161
- title="Click to expand. Drag or use arrow keys to move. Open panel options to dock."
161
+ title="Drag or use arrow keys to move"
162
162
  onpointerdown={(event) => start(event, 'move')}
163
163
  onkeydown={key}
164
164
  onclick={() => layout.activate(panel)}>{panel.title}</button
@@ -5,7 +5,7 @@
5
5
  </script>
6
6
 
7
7
  {#if app.placingId}
8
- <div class="placement-cue mono">click the map to place the topology layout</div>
8
+ <div class="placement-cue">Click the map to place the topology layout</div>
9
9
  {/if}
10
10
 
11
11
  <style>
@@ -22,34 +22,33 @@
22
22
  const neutralPos = $derived(thumbAlignedPos(neutralFrac));
23
23
  const fillLo = $derived(valFrac < neutralFrac ? valPos : neutralPos);
24
24
  const fillHi = $derived(valFrac < neutralFrac ? neutralPos : valPos);
25
- const sliderTip =
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.';
27
- const disabledTip = "this line's rating is synthesized; not adjustable";
25
+ const sliderTip = 'Drag to preview, release to solve. The tick marks the committed rating.';
26
+ const disabledTip = 'Synthesized rating; not adjustable';
28
27
  const scoreTip =
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.';
28
+ 'Gradient: first-order estimate from the committed point. Exact: solved objective change.';
30
29
  </script>
31
30
 
32
31
  {#if ctrl.activeSolvable}
33
32
  {@const c = ctrl.activeSolvable}
34
33
  {@const branch = ctrl.selectedBranchData}
35
34
  <div class="slider-block">
36
- <div class="slider-head mono">
37
- <span>&Delta; rating</span>
38
- <span class="val">{signed(ctrl.ratingSliderValue)} MW</span>
35
+ <div class="slider-head">
36
+ <span>Rating change</span>
37
+ <span class="val mono">{signed(ctrl.ratingSliderValue)} MW</span>
39
38
  </div>
40
- <div class="line-row mono">
39
+ <div class="line-row">
41
40
  {#if branch}
42
41
  <span class="dim"
43
- >line {branch.from}&#8201;&ndash;&#8201;{branch.to}, {fmt.format(branch.rate_mw)} MW</span
42
+ >Line <span class="mono"
43
+ >{branch.from}&#8201;&ndash;&#8201;{branch.to}, {fmt.format(branch.rate_mw)} MW</span
44
+ ></span
44
45
  >
45
46
  {/if}
46
- <span class="dim">
47
- {#if bounds.disabled}
48
- not adjustable
49
- {:else}
50
- {fmt.format(bounds.min)} to +{fmt.format(bounds.max)} MW
51
- {/if}
52
- </span>
47
+ {#if bounds.disabled}
48
+ <span class="dim">Not adjustable</span>
49
+ {:else}
50
+ <span class="mono dim">{fmt.format(bounds.min)} to +{fmt.format(bounds.max)} MW</span>
51
+ {/if}
53
52
  </div>
54
53
  <div
55
54
  class="slider-track"
@@ -63,7 +62,7 @@
63
62
  step="any"
64
63
  disabled={bounds.disabled}
65
64
  bind:value={ctrl.ratingSliderCurrent, ctrl.setRatingPreview}
66
- aria-label="rating delta at selected line"
65
+ aria-label="Rating delta at selected line"
67
66
  onpointerdown={() => ctrl.setRatingPreview(ctrl.ratingSliderValue)}
68
67
  onkeydown={(e) => {
69
68
  const next = sliderKeyValue(e.key, ctrl.ratingSliderValue, bounds.min, bounds.max);
@@ -80,18 +79,18 @@
80
79
  />
81
80
  </div>
82
81
  <div class="rating-feedback" class:idle={!ctrl.previewing && !ctrl.isPerturbed(c)}>
83
- <p class="pred mono dim" aria-hidden={!(ctrl.predictedDeltaObj !== null && ctrl.previewing)}>
82
+ <p class="pred dim" aria-hidden={!(ctrl.predictedDeltaObj !== null && ctrl.previewing)}>
84
83
  {#if ctrl.predictedDeltaObj !== null && ctrl.previewing}
85
- predicted &Delta;objective {signed(ctrl.predictedDeltaObj)}
84
+ Predicted objective change <span class="mono">{signed(ctrl.predictedDeltaObj)}</span>
86
85
  {:else}
87
86
  &nbsp;
88
87
  {/if}
89
88
  </p>
90
- <p class="score mono" aria-hidden={!(ctrl.gradientScore && ctrl.isPerturbed(c))}>
89
+ <p class="score" aria-hidden={!(ctrl.gradientScore && ctrl.isPerturbed(c))}>
91
90
  {#if ctrl.gradientScore && ctrl.isPerturbed(c)}
92
91
  <span title={scoreTip}>
93
- gradient {signed(ctrl.gradientScore.pred)}, exact {signed(ctrl.gradientScore.exact)} objective
94
- units
92
+ Gradient <span class="mono">{signed(ctrl.gradientScore.pred)}</span>, exact
93
+ <span class="mono">{signed(ctrl.gradientScore.exact)}</span> objective units
95
94
  </span>
96
95
  {:else}
97
96
  &nbsp;
@@ -99,7 +98,7 @@
99
98
  </p>
100
99
  <div class="reset-row">
101
100
  {#if ctrl.isPerturbed(c)}
102
- <button class="reset mono" onclick={() => ctrl.resetCase(c)}>reset rating</button>
101
+ <button class="reset" onclick={() => ctrl.resetCase(c)}>Reset rating</button>
103
102
  {/if}
104
103
  </div>
105
104
  </div>
@@ -6,9 +6,7 @@
6
6
  </script>
7
7
 
8
8
  {#if config.loadDefaultCases && ctrl.casesLoaded && ctrl.hiddenDefaults.size > 0}
9
- <button class="restore-defaults mono" onclick={ctrl.restoreDefaultCases}>
10
- &#8634; restore default cases
11
- </button>
9
+ <button class="restore-defaults" onclick={ctrl.restoreDefaultCases}>Restore default cases</button>
12
10
  {/if}
13
11
 
14
12
  <style>
@@ -22,6 +20,7 @@
22
20
  border: 1px solid var(--line);
23
21
  border-radius: 3px;
24
22
  color: var(--text-secondary);
23
+ font-family: inherit;
25
24
  font-size: 11px;
26
25
  cursor: pointer;
27
26
  box-shadow: var(--elev-1);
@@ -11,17 +11,11 @@
11
11
  'objective units'
12
12
  )
13
13
  );
14
- const busUnitTitle = $derived(
15
- `LMP response to active demand, in ${reportedUnits}. The response is small while the active set stays unchanged.`
16
- );
17
- const branchUnitTitle = $derived(
18
- `LMP response to the thermal rating, in ${reportedUnits}. The column is zero when the limit is inactive.`
19
- );
14
+ const unitTitle = $derived(`Units: ${reportedUnits}`);
20
15
 
21
16
  // Branch mode: the selection is a line and the column is price/rating; the
22
17
  // legend and preview pipeline are identical (both columns are bus-keyed).
23
18
  const branchMode = $derived(app.selectedBranch !== null);
24
- const unitTitle = $derived(branchMode ? branchUnitTitle : busUnitTitle);
25
19
  const previewUnits = $derived(app.previewPrices?.units ?? 'objective units/MW');
26
20
 
27
21
  const previewStep = $derived(
@@ -42,25 +36,22 @@
42
36
  <span class="chip">
43
37
  {ctrl.previewing ? 'LMP preview' : branchMode ? '∂LMP/∂rating' : '∂LMP/∂d'}
44
38
  </span>
45
- <span class="mono dim">
46
- {#if branchMode}line {branchLabel}{:else}bus {app.selectedBus}{/if}
39
+ <span class="dim">
40
+ {#if branchMode}Line <span class="mono">{branchLabel}</span>{:else}Bus
41
+ <span class="mono">{app.selectedBus}</span>{/if}
47
42
  </span>
48
- <button class="mono" onclick={ctrl.clearSelection}
49
- ><span class="key-hint">esc&nbsp;</span>clear</button
50
- >
43
+ <button onclick={ctrl.clearSelection} aria-keyshortcuts="Escape" title="Esc">Clear</button>
51
44
  </div>
52
45
  <div class="sensitivity-readout" aria-live="polite">
53
46
  {#if ctrl.previewing}
54
47
  <p class="dim small">
55
- {ctrl.activeSolvable?.solving
56
- ? 'Exact solve running; the map keeps the LMP preview.'
57
- : 'First order LMP preview. Release for the exact solve.'}
48
+ {ctrl.activeSolvable?.solving ? 'Solving…' : 'First-order preview. Release to solve.'}
58
49
  </p>
59
50
  {#if ctrl.sensSummary?.flat}
60
51
  <div class="legend flat" style:background={ctrl.flatSensBackground}></div>
61
52
  <div class="legend-labels mono single">
62
53
  <span
63
- >uniform {signedExp(ctrl.sensSummary.mean * previewStep)}
54
+ >Uniform {signedExp(ctrl.sensSummary.mean * previewStep)}
64
55
  {previewUnits}</span
65
56
  >
66
57
  </div>
@@ -88,7 +79,7 @@
88
79
  {#if ctrl.sensSummary?.flat}
89
80
  <div class="legend flat" style:background={ctrl.flatSensBackground}></div>
90
81
  <div class="legend-labels mono single">
91
- <span>uniform {signedExp(ctrl.sensSummary.mean)} {reportedUnits}</span>
82
+ <span>Uniform {signedExp(ctrl.sensSummary.mean)} {reportedUnits}</span>
92
83
  </div>
93
84
  {:else if ctrl.sensSummary}
94
85
  <div class="legend" style:background={sensGradient}></div>
@@ -100,9 +91,7 @@
100
91
  {:else if app.sensitivityLoading}
101
92
  <div class="legend" style:background="var(--line)" style:opacity="0.4"></div>
102
93
  <div class="legend-labels mono single">
103
- <span class="blink">
104
- computing {#if branchMode}&part;value/&part;fmax{:else}&part;value/&part;d{/if}&hellip;
105
- </span>
94
+ <span class="blink">Computing&hellip;</span>
106
95
  </div>
107
96
  {/if}
108
97
  {/if}
@@ -133,11 +122,4 @@
133
122
  line-height: 1.35;
134
123
  white-space: nowrap;
135
124
  }
136
-
137
- /* No Esc key to name on a touch device. */
138
- @media (hover: none), (pointer: coarse) {
139
- .key-hint {
140
- display: none;
141
- }
142
- }
143
125
  </style>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { getAppState, getController } from '../context.svelte.js';
3
- import { formulationLabel, solveMetaLabel } from '../format.js';
3
+ import { formulationLabel } from '../format.js';
4
4
  import Sparkline from '../Sparkline.svelte';
5
5
  import PanelFrame from './PanelFrame.svelte';
6
6
  let open = $state(true);
@@ -16,67 +16,101 @@
16
16
  : []
17
17
  : (active?.iterations ?? [])
18
18
  );
19
+ /** Named only when a server took part; the browser engine is the default. */
20
+ const server = $derived(
21
+ active?.solveBackend === 'rust-server'
22
+ ? 'Server'
23
+ : active?.solveBackend === 'clarabel-wasm-server-sensitivity'
24
+ ? 'Server price sensitivity'
25
+ : null
26
+ );
19
27
  </script>
20
28
 
21
29
  {#if saved || (active && (active.solving || active.solveMs != null))}
22
30
  <PanelFrame id="solver" title="Solver" side="right" order={10} width={320} bind:open>
23
31
  <div class="solvecard">
24
- <div class="solve-label mono">
25
- {saved ? 'Saved result' : active?.formulation === 'acpf' ? 'Power flow' : 'OPF solve'}
26
- </div>
27
32
  {#if iterations.length > 1}
28
33
  {#key iterations}
29
34
  <Sparkline {iterations} />
30
35
  {/key}
31
36
  {/if}
32
37
  {#if saved}
33
- <div class="solve-meta mono dim">
34
- <span class="solve-formulation">{formulationLabel(saved.formulation)}</span>
35
- <span>{saved.solution?.status ?? 'Not solved'}</span>
36
- <span>Saved state: {saved.label}</span>
37
- </div>
38
+ <dl>
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
+ <div>
48
+ <dt>Saved state</dt>
49
+ <dd>{saved.label}</dd>
50
+ </div>
51
+ </dl>
38
52
  {:else if active}
39
- <div class="solve-meta mono dim">
40
- <span class="solve-formulation">{formulationLabel(active.formulation)}</span>
41
- <span>{solveMetaLabel(active)}</span>
42
- {#if active.solveMs != null}<span>{active.solveMs} ms</span>{/if}
43
- </div>
44
- {#if active.solveBackend === 'rust-server' && active.solveFallbackReason}
45
- <p class="fallback-reason mono dim" title={active.solveFallbackReason}>
46
- fallback: {active.solveFallbackReason}
47
- </p>
48
- {/if}
53
+ <dl>
54
+ <div>
55
+ <dt>Calculation</dt>
56
+ <dd>{formulationLabel(active.formulation)}</dd>
57
+ </div>
58
+ {#if iterations.length > 1}
59
+ <div>
60
+ <dt>Iterations</dt>
61
+ <dd class="mono">{iterations.length}</dd>
62
+ </div>
63
+ {/if}
64
+ {#if server}
65
+ <div>
66
+ <dt>Solver</dt>
67
+ <dd>{server}</dd>
68
+ </div>
69
+ {/if}
70
+ {#if active.solveMs != null}
71
+ <div>
72
+ <dt>Time</dt>
73
+ <dd class="mono">{active.solveMs} ms</dd>
74
+ </div>
75
+ {/if}
76
+ {#if active.solveBackend === 'rust-server' && active.solveFallbackReason}
77
+ <div>
78
+ <dt>Fallback</dt>
79
+ <dd title={active.solveFallbackReason}>{active.solveFallbackReason}</dd>
80
+ </div>
81
+ {/if}
82
+ </dl>
49
83
  {/if}
50
84
  </div>
51
85
  </PanelFrame>
52
86
  {/if}
53
87
 
54
88
  <style>
55
- .solve-label {
56
- font-size: 13px;
57
- margin-bottom: 8px;
58
- }
59
89
  .solvecard {
60
90
  padding: 13px 15px 11px;
61
91
  }
62
92
 
63
- .solve-meta {
64
- display: flex;
65
- align-items: center;
66
- flex-wrap: wrap;
67
- gap: 10px 14px;
93
+ dl {
94
+ margin: 6px 0 0;
68
95
  font-size: 12px;
69
- margin-top: 6px;
70
96
  }
71
97
 
72
- .solve-formulation {
73
- color: var(--text-accent);
98
+ dl div {
99
+ display: flex;
100
+ justify-content: space-between;
101
+ gap: 12px;
102
+ padding: 2px 0;
74
103
  }
75
104
 
76
- .fallback-reason {
77
- margin: 6px 0 0;
78
- font-size: 12px;
79
- line-height: 1.35;
105
+ dt {
106
+ flex: none;
107
+ color: var(--text-secondary);
108
+ }
109
+
110
+ dd {
111
+ min-width: 0;
112
+ margin: 0;
113
+ text-align: right;
80
114
  overflow-wrap: anywhere;
81
115
  }
82
116
  </style>
@@ -14,15 +14,15 @@
14
14
 
15
15
  <div class="movers-block">
16
16
  {#if !ctrl.previewing && ctrl.topMovers.length > 0}
17
- <table class="mono">
18
- <caption class="mono dim" title={unitTitle}>
19
- largest LMP response <span class="unit">{units}</span>
17
+ <table>
18
+ <caption class="dim" title={unitTitle}>
19
+ Largest LMP response <span class="mono unit">{units}</span>
20
20
  </caption>
21
21
  <tbody>
22
22
  {#each ctrl.topMovers as mover (mover.bus)}
23
23
  <tr>
24
- <td>bus {mover.bus}</td>
25
- <td class:pos={mover.value > 0} class:neg={mover.value < 0}>
24
+ <td>Bus <span class="mono">{mover.bus}</span></td>
25
+ <td class="mono" class:pos={mover.value > 0} class:neg={mover.value < 0}>
26
26
  {signedExp(mover.value)}
27
27
  </td>
28
28
  </tr>
@@ -9,15 +9,19 @@ export interface TellegenUiConfig {
9
9
  orgLabel: string;
10
10
  showFooter: boolean;
11
11
  /** `'dark'` sets `data-theme="dark"` on the document root while the
12
- * provider is mounted, which switches the `styles.css` tokens. */
12
+ * provider is mounted, which switches the `styles.css` tokens. The
13
+ * provider keeps it in step with its prop. */
13
14
  theme: 'light' | 'dark';
14
15
  /** CARTO basemap key for the default shell's map, trimmed; empty for
15
- * none. See `TellegenMap`'s `cartoApiKey`. */
16
+ * none. See `TellegenMap`'s `cartoApiKey`. The provider keeps it in step
17
+ * with its prop. */
16
18
  cartoApiKey: string;
17
19
  }
18
20
  export type TellegenUiOptions = Partial<TellegenUiConfig>;
19
21
  export declare const DEFAULT_TELLEGEN_UI_CONFIG: TellegenUiConfig;
20
22
  export declare function resolveTellegenUiConfig(options?: TellegenUiOptions): TellegenUiConfig;
23
+ export declare const resolveTheme: (theme: TellegenUiOptions["theme"]) => TellegenUiConfig["theme"];
24
+ export declare const resolveCartoApiKey: (key: TellegenUiOptions["cartoApiKey"]) => string;
21
25
  export declare const setAppState: (a: AppState) => AppState;
22
26
  export declare const setController: (c: Controller) => Controller;
23
27
  export declare const setUiConfig: (config: TellegenUiConfig) => TellegenUiConfig;
@@ -21,10 +21,12 @@ export function resolveTellegenUiConfig(options = {}) {
21
21
  orgHref: options.orgHref ?? DEFAULT_TELLEGEN_UI_CONFIG.orgHref,
22
22
  orgLabel: options.orgLabel ?? DEFAULT_TELLEGEN_UI_CONFIG.orgLabel,
23
23
  showFooter: options.showFooter ?? DEFAULT_TELLEGEN_UI_CONFIG.showFooter,
24
- theme: options.theme ?? DEFAULT_TELLEGEN_UI_CONFIG.theme,
25
- cartoApiKey: normalizeCartoApiKey(options.cartoApiKey) ?? DEFAULT_TELLEGEN_UI_CONFIG.cartoApiKey
24
+ theme: resolveTheme(options.theme),
25
+ cartoApiKey: resolveCartoApiKey(options.cartoApiKey)
26
26
  };
27
27
  }
28
+ export const resolveTheme = (theme) => theme ?? DEFAULT_TELLEGEN_UI_CONFIG.theme;
29
+ export const resolveCartoApiKey = (key) => normalizeCartoApiKey(key) ?? DEFAULT_TELLEGEN_UI_CONFIG.cartoApiKey;
28
30
  export const setAppState = (a) => setContext(APP, a);
29
31
  export const setController = (c) => setContext(CTRL, c);
30
32
  export const setUiConfig = (config) => setContext(CONFIG, config);