@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.
- package/README.md +38 -10
- package/dist/Sparkline.svelte +4 -4
- package/dist/TellegenMap.svelte +41 -46
- package/dist/TellegenProvider.svelte +29 -13
- package/dist/components/AppFooter.svelte +4 -4
- package/dist/components/AppHeader.svelte +15 -19
- package/dist/components/BindingLines.svelte +10 -9
- package/dist/components/BusPicker.svelte +5 -6
- package/dist/components/ControlPanel.svelte +12 -9
- package/dist/components/ControlPanel.svelte.d.ts +6 -1
- package/dist/components/DemandSlider.svelte +24 -29
- package/dist/components/DisplayControls.svelte +5 -24
- package/dist/components/DropZone.svelte +7 -3
- package/dist/components/DropZone.svelte.d.ts +6 -17
- package/dist/components/FormulationSelector.svelte +2 -7
- package/dist/components/LocalCaseDetails.svelte +126 -126
- package/dist/components/LocalCaseDetails.svelte.d.ts +6 -1
- package/dist/components/MulticonductorDetails.svelte +48 -38
- package/dist/components/NetworkStats.svelte +14 -16
- package/dist/components/PanelCard.svelte +1 -1
- package/dist/components/PlacementCue.svelte +1 -1
- package/dist/components/RatingSlider.svelte +22 -23
- package/dist/components/RestoreDefaultsButton.svelte +2 -3
- package/dist/components/SensitivityReadout.svelte +9 -27
- package/dist/components/SolveCard.svelte +69 -35
- package/dist/components/TopMovers.svelte +5 -5
- package/dist/context.svelte.d.ts +6 -2
- package/dist/context.svelte.js +4 -2
- package/dist/controller.svelte.d.ts +7 -0
- package/dist/controller.svelte.js +33 -6
- package/dist/display.js +8 -9
- package/dist/format.d.ts +1 -9
- package/dist/format.js +0 -13
- package/dist/styles.css +3 -0
- package/package.json +10 -6
|
@@ -23,48 +23,43 @@
|
|
|
23
23
|
const neutralPos = $derived(thumbAlignedPos(neutralFrac));
|
|
24
24
|
const fillLo = $derived(valFrac < neutralFrac ? valPos : neutralPos);
|
|
25
25
|
const fillHi = $derived(valFrac < neutralFrac ? neutralPos : valPos);
|
|
26
|
-
const sliderTip =
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const scoreTip =
|
|
30
|
-
'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.';
|
|
26
|
+
const sliderTip = 'Drag to preview, release to solve. The tick marks the committed demand.';
|
|
27
|
+
const disabledTip = 'Display-only bus; demand is fixed';
|
|
28
|
+
const scoreTip = 'Gradient: first-order estimate from the LMP. Exact: solved objective change.';
|
|
31
29
|
</script>
|
|
32
30
|
|
|
33
31
|
{#if ctrl.activeSolvable}
|
|
34
32
|
{@const c = ctrl.activeSolvable}
|
|
35
33
|
<div class="slider-block">
|
|
36
|
-
<div class="slider-head
|
|
37
|
-
<span
|
|
38
|
-
<span class="val">{signed(ctrl.sliderValue)} MW</span>
|
|
34
|
+
<div class="slider-head">
|
|
35
|
+
<span>Demand change</span>
|
|
36
|
+
<span class="val mono">{signed(ctrl.sliderValue)} MW</span>
|
|
39
37
|
</div>
|
|
40
38
|
<div class="range-mode">
|
|
41
|
-
<div class="segment
|
|
39
|
+
<div class="segment" role="group" aria-label="Demand range">
|
|
42
40
|
<button
|
|
43
41
|
type="button"
|
|
44
42
|
class:active={app.demandRangeMode === 'local'}
|
|
45
43
|
aria-pressed={app.demandRangeMode === 'local'}
|
|
46
|
-
aria-label="nearby demand range"
|
|
47
44
|
{disabled}
|
|
48
|
-
title="
|
|
49
|
-
onclick={() => ctrl.setDemandRangeMode('local')}>
|
|
45
|
+
title="Range near the committed demand"
|
|
46
|
+
onclick={() => ctrl.setDemandRangeMode('local')}>Nearby</button
|
|
50
47
|
>
|
|
51
48
|
<button
|
|
52
49
|
type="button"
|
|
53
50
|
class:active={app.demandRangeMode === 'full'}
|
|
54
51
|
aria-pressed={app.demandRangeMode === 'full'}
|
|
55
|
-
aria-label="full range of demand"
|
|
56
52
|
{disabled}
|
|
57
|
-
title="
|
|
58
|
-
onclick={() => ctrl.setDemandRangeMode('full')}>
|
|
53
|
+
title="Zero load to the local physical limit"
|
|
54
|
+
onclick={() => ctrl.setDemandRangeMode('full')}>Full range</button
|
|
59
55
|
>
|
|
60
56
|
</div>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
</span>
|
|
57
|
+
{#if disabled}
|
|
58
|
+
<span class="dim">Not adjustable</span>
|
|
59
|
+
{:else}
|
|
60
|
+
<span class="mono dim">{fmt.format(ctrl.sliderMin)} to {fmt.format(ctrl.sliderMax)} MW</span
|
|
61
|
+
>
|
|
62
|
+
{/if}
|
|
68
63
|
</div>
|
|
69
64
|
<div
|
|
70
65
|
class="slider-track"
|
|
@@ -78,7 +73,7 @@
|
|
|
78
73
|
step="any"
|
|
79
74
|
{disabled}
|
|
80
75
|
bind:value={ctrl.sliderCurrent, ctrl.setSliderPreview}
|
|
81
|
-
aria-label="
|
|
76
|
+
aria-label="Demand delta at selected bus"
|
|
82
77
|
onpointerdown={() => ctrl.setSliderPreview(ctrl.sliderValue)}
|
|
83
78
|
onkeydown={(e) => {
|
|
84
79
|
const next = sliderKeyValue(e.key, ctrl.sliderValue, ctrl.sliderMin, ctrl.sliderMax);
|
|
@@ -95,18 +90,18 @@
|
|
|
95
90
|
/>
|
|
96
91
|
</div>
|
|
97
92
|
<div class="demand-feedback" class:idle={!ctrl.previewing && !ctrl.isPerturbed(c)}>
|
|
98
|
-
<p class="pred
|
|
93
|
+
<p class="pred dim" aria-hidden={!(ctrl.predictedDeltaObj !== null && ctrl.previewing)}>
|
|
99
94
|
{#if ctrl.predictedDeltaObj !== null && ctrl.previewing}
|
|
100
|
-
|
|
95
|
+
Predicted objective change <span class="mono">{signed(ctrl.predictedDeltaObj)}</span>
|
|
101
96
|
{:else}
|
|
102
97
|
|
|
103
98
|
{/if}
|
|
104
99
|
</p>
|
|
105
|
-
<p class="score
|
|
100
|
+
<p class="score" aria-hidden={!(ctrl.gradientScore && ctrl.isPerturbed(c))}>
|
|
106
101
|
{#if ctrl.gradientScore && ctrl.isPerturbed(c)}
|
|
107
102
|
<span title={scoreTip}>
|
|
108
|
-
|
|
109
|
-
units
|
|
103
|
+
Gradient <span class="mono">{signed(ctrl.gradientScore.pred)}</span>, exact
|
|
104
|
+
<span class="mono">{signed(ctrl.gradientScore.exact)}</span> objective units
|
|
110
105
|
</span>
|
|
111
106
|
{:else}
|
|
112
107
|
|
|
@@ -114,7 +109,7 @@
|
|
|
114
109
|
</p>
|
|
115
110
|
<div class="reset-row">
|
|
116
111
|
{#if ctrl.isPerturbed(c)}
|
|
117
|
-
<button class="reset
|
|
112
|
+
<button class="reset" onclick={() => ctrl.resetCase(c)}>Reset demand</button>
|
|
118
113
|
{/if}
|
|
119
114
|
</div>
|
|
120
115
|
</div>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
9
|
<div class="mode display-mode">
|
|
10
|
-
<div class="segment
|
|
10
|
+
<div class="segment" aria-label="Bus color variable">
|
|
11
11
|
{#each ctrl.displayOptions as option (option.mode)}
|
|
12
12
|
<button
|
|
13
13
|
type="button"
|
|
@@ -19,27 +19,16 @@
|
|
|
19
19
|
</div>
|
|
20
20
|
<span class="mono dim">{ctrl.activeDisplay?.unit ?? ''}</span>
|
|
21
21
|
{#if app.sensitivityLoading}
|
|
22
|
-
<span class="
|
|
22
|
+
<span class="dim blink">Loading…</span>
|
|
23
23
|
{/if}
|
|
24
24
|
</div>
|
|
25
25
|
{#if ctrl.activeDisplay && ctrl.displayStats}
|
|
26
|
-
{
|
|
27
|
-
<div class="legend-heading">
|
|
28
|
-
<span class="legend-title">Locational marginal price</span>
|
|
29
|
-
<span
|
|
30
|
-
class="hint-dot mono"
|
|
31
|
-
title={ctrl.activeDisplay.copy}
|
|
32
|
-
aria-label={ctrl.activeDisplay.copy}>i</span
|
|
33
|
-
>
|
|
34
|
-
</div>
|
|
35
|
-
{:else}
|
|
36
|
-
<p class="dim small">{ctrl.activeDisplay.copy}</p>
|
|
37
|
-
{/if}
|
|
26
|
+
<p class="legend-title">{ctrl.activeDisplay.copy}</p>
|
|
38
27
|
<div class="legend" style:background={ctrl.activeDisplay.gradient}></div>
|
|
39
28
|
<div class="legend-labels mono">
|
|
40
29
|
{#if ctrl.displayStats.uniform !== null}
|
|
41
30
|
<span>
|
|
42
|
-
|
|
31
|
+
Uniform {displayFmt(ctrl.activeDisplay.mode, ctrl.displayStats.uniform)}
|
|
43
32
|
{ctrl.activeDisplay.unit}
|
|
44
33
|
</span>
|
|
45
34
|
{:else}
|
|
@@ -70,18 +59,10 @@
|
|
|
70
59
|
{/if}
|
|
71
60
|
|
|
72
61
|
<style>
|
|
73
|
-
.legend-heading {
|
|
74
|
-
display: flex;
|
|
75
|
-
align-items: center;
|
|
76
|
-
gap: 6px;
|
|
77
|
-
margin-top: 10px;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
62
|
.legend-title {
|
|
63
|
+
margin: 10px 0 0;
|
|
81
64
|
font-size: 12px;
|
|
82
65
|
font-weight: 600;
|
|
83
66
|
color: var(--ink);
|
|
84
67
|
}
|
|
85
|
-
|
|
86
|
-
/* .hint-dot lives in the shared stylesheet; SensitivityReadout uses it too. */
|
|
87
68
|
</style>
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
import { getAppState } from '../context.svelte.js';
|
|
3
4
|
|
|
5
|
+
/** Optional host content rendered under the drop prompt. */
|
|
6
|
+
let { children }: { children?: Snippet } = $props();
|
|
7
|
+
|
|
4
8
|
const app = getAppState();
|
|
5
9
|
</script>
|
|
6
10
|
|
|
7
11
|
{#if app.dragOver}
|
|
8
12
|
<div class="dropzone" aria-hidden="true">
|
|
9
13
|
<div class="dropframe">
|
|
10
|
-
<p
|
|
11
|
-
<
|
|
14
|
+
<p>Drop case or geographic files</p>
|
|
15
|
+
{#if children}<div class="note">{@render children()}</div>{/if}
|
|
12
16
|
</div>
|
|
13
17
|
</div>
|
|
14
18
|
{/if}
|
|
@@ -39,7 +43,7 @@
|
|
|
39
43
|
font-size: 13px;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
.
|
|
46
|
+
.note {
|
|
43
47
|
font-size: 11px;
|
|
44
48
|
color: var(--text-secondary);
|
|
45
49
|
}
|
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
$$slots?: Slots;
|
|
8
|
-
}): Exports & {
|
|
9
|
-
$set?: any;
|
|
10
|
-
$on?: any;
|
|
11
|
-
};
|
|
12
|
-
z_$$bindings?: Bindings;
|
|
13
|
-
}
|
|
14
|
-
declare const DropZone: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
-
[evt: string]: CustomEvent<any>;
|
|
16
|
-
}, {}, {}, string>;
|
|
17
|
-
type DropZone = InstanceType<typeof DropZone>;
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
children?: Snippet;
|
|
4
|
+
};
|
|
5
|
+
declare const DropZone: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type DropZone = ReturnType<typeof DropZone>;
|
|
18
7
|
export default DropZone;
|
|
@@ -9,15 +9,10 @@
|
|
|
9
9
|
{#if ctrl.activeSolvable}
|
|
10
10
|
{@const c = ctrl.activeSolvable}
|
|
11
11
|
<div class="formulation">
|
|
12
|
-
<label
|
|
13
|
-
class="formulation-row mono"
|
|
14
|
-
for="formulation-select"
|
|
15
|
-
title={formulationHint(c.formulation)}
|
|
16
|
-
>
|
|
12
|
+
<label class="formulation-row" for="formulation-select" title={formulationHint(c.formulation)}>
|
|
17
13
|
<span>Calculation</span>
|
|
18
14
|
<select
|
|
19
15
|
id="formulation-select"
|
|
20
|
-
class="mono"
|
|
21
16
|
disabled={c.solving}
|
|
22
17
|
value={c.formulation}
|
|
23
18
|
onchange={(e) => ctrl.changeFormulation(c, e.currentTarget.value as Formulation)}
|
|
@@ -52,7 +47,7 @@
|
|
|
52
47
|
}
|
|
53
48
|
|
|
54
49
|
.formulation-row select {
|
|
55
|
-
font-family:
|
|
50
|
+
font-family: inherit;
|
|
56
51
|
font-size: 13px;
|
|
57
52
|
padding: 3px 22px 3px 8px;
|
|
58
53
|
border: 1px solid var(--line);
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
2
3
|
import { getAppState, getController } from '../context.svelte.js';
|
|
3
4
|
import { fmt } from '../format.js';
|
|
4
5
|
import type { LocalCase } from '../state.svelte.js';
|
|
5
6
|
import { formatPowerIoDiagnostic, type PowerIoDiagnostic } from '@tellegen/engine';
|
|
6
7
|
|
|
8
|
+
/** Optional host content rendered under the case stats. */
|
|
9
|
+
let { note }: { note?: Snippet<[LocalCase]> } = $props();
|
|
10
|
+
|
|
7
11
|
const app = getAppState();
|
|
8
12
|
const ctrl = getController();
|
|
9
13
|
|
|
@@ -22,6 +26,23 @@
|
|
|
22
26
|
let exportOpen = $state(false);
|
|
23
27
|
let exportDiagnostics = $state<PowerIoDiagnostic[]>([]);
|
|
24
28
|
|
|
29
|
+
/** Where the map coordinates came from. */
|
|
30
|
+
function coordinateSource(lc: LocalCase): string {
|
|
31
|
+
if (!lc.view) return 'None';
|
|
32
|
+
switch (lc.coordsKind) {
|
|
33
|
+
case 'file':
|
|
34
|
+
return 'From file';
|
|
35
|
+
case 'geofile':
|
|
36
|
+
return lc.geoSource ? `From ${lc.geoSource}` : 'From geographic file';
|
|
37
|
+
case 'manual':
|
|
38
|
+
return 'Manual layout';
|
|
39
|
+
case 'synthetic':
|
|
40
|
+
return lc.summary?.coords_method === 'generated' ? 'Generated' : 'Synthetic layout';
|
|
41
|
+
default:
|
|
42
|
+
return 'None';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
25
46
|
async function saveCase(lc: LocalCase) {
|
|
26
47
|
busy = true;
|
|
27
48
|
exportDiagnostics = [];
|
|
@@ -64,137 +85,128 @@
|
|
|
64
85
|
|
|
65
86
|
{#if app.activeLocal}
|
|
66
87
|
{@const lc = app.activeLocal}
|
|
67
|
-
<h2>{lc.label}
|
|
88
|
+
<h2>{lc.label}</h2>
|
|
68
89
|
{#if lc.substations}
|
|
69
|
-
<dl
|
|
90
|
+
<dl>
|
|
91
|
+
<div>
|
|
92
|
+
<dt>Substations</dt>
|
|
93
|
+
<dd class="mono">{lc.substations.points.length}</dd>
|
|
94
|
+
</div>
|
|
95
|
+
<div>
|
|
96
|
+
<dt>Positions</dt>
|
|
97
|
+
<dd>Inferred from diagram</dd>
|
|
98
|
+
</div>
|
|
70
99
|
<div>
|
|
71
|
-
<dt>
|
|
72
|
-
<dd>{lc.
|
|
100
|
+
<dt>File</dt>
|
|
101
|
+
<dd class="mono">{lc.fileName}</dd>
|
|
73
102
|
</div>
|
|
74
103
|
</dl>
|
|
75
|
-
<p class="footnote mono">
|
|
76
|
-
display only: positions inferred from the PowerWorld diagram, not surveyed latitude and
|
|
77
|
-
longitude
|
|
78
|
-
</p>
|
|
79
|
-
<p class="footnote mono">decoded in your browser by powerio (wasm); never uploaded</p>
|
|
80
104
|
{:else if lc.summary}
|
|
81
|
-
<dl
|
|
105
|
+
<dl>
|
|
82
106
|
<div>
|
|
83
|
-
<dt>
|
|
84
|
-
<dd>{lc.summary.n_bus}</dd>
|
|
107
|
+
<dt>Buses</dt>
|
|
108
|
+
<dd class="mono">{lc.summary.n_bus}</dd>
|
|
85
109
|
</div>
|
|
86
110
|
<div>
|
|
87
|
-
<dt>
|
|
88
|
-
<dd>{lc.summary.n_branch}</dd>
|
|
111
|
+
<dt>Branches</dt>
|
|
112
|
+
<dd class="mono">{lc.summary.n_branch}</dd>
|
|
89
113
|
</div>
|
|
90
114
|
<div>
|
|
91
|
-
<dt>
|
|
92
|
-
<dd>{lc.summary.n_gen}</dd>
|
|
115
|
+
<dt>Generators</dt>
|
|
116
|
+
<dd class="mono">{lc.summary.n_gen}</dd>
|
|
93
117
|
</div>
|
|
94
118
|
<div>
|
|
95
|
-
<dt>
|
|
96
|
-
<dd>{fmt.format(lc.summary.load_mw)} MW</dd>
|
|
119
|
+
<dt>Load</dt>
|
|
120
|
+
<dd class="mono">{fmt.format(lc.summary.load_mw)} MW</dd>
|
|
97
121
|
</div>
|
|
98
122
|
<div>
|
|
99
|
-
<dt>
|
|
100
|
-
<dd>{fmt.format(lc.summary.gen_mw)} MW</dd>
|
|
123
|
+
<dt>Generation capacity</dt>
|
|
124
|
+
<dd class="mono">{fmt.format(lc.summary.gen_mw)} MW</dd>
|
|
101
125
|
</div>
|
|
102
126
|
<div>
|
|
103
|
-
<dt>
|
|
104
|
-
<dd>{fmt.format(lc.summary.base_mva)}</dd>
|
|
127
|
+
<dt>Base MVA</dt>
|
|
128
|
+
<dd class="mono">{fmt.format(lc.summary.base_mva)}</dd>
|
|
105
129
|
</div>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
<details>
|
|
111
|
-
<summary>Drawing details</summary>
|
|
112
|
-
<ul class="warnings mono">
|
|
113
|
-
{#each lc.diagram.warnings as warning, i (i)}<li>{warning}</li>{/each}
|
|
114
|
-
</ul>
|
|
115
|
-
</details>
|
|
116
|
-
{/if}
|
|
117
|
-
{:else if !lc.view}
|
|
118
|
-
<p class="footnote mono">
|
|
119
|
-
no coordinates in this file: click the map or drop a geographic file
|
|
120
|
-
</p>
|
|
121
|
-
{:else if lc.coordsKind === 'synthetic' && lc.summary.coords_method === 'generated'}
|
|
122
|
-
<p class="footnote mono">coordinates: synthetic grid sited on real geography</p>
|
|
123
|
-
{:else if lc.coordsKind === 'synthetic' || lc.coordsKind === 'manual'}
|
|
124
|
-
<p class="footnote mono">
|
|
125
|
-
coordinates: {lc.coordsKind} topology layout centered where it was placed
|
|
126
|
-
</p>
|
|
127
|
-
{:else if lc.coordsKind === 'geofile'}
|
|
128
|
-
<p class="footnote mono">
|
|
129
|
-
coordinates: geographic file data from {lc.geoSource}
|
|
130
|
-
</p>
|
|
131
|
-
{/if}
|
|
132
|
-
<p class="footnote mono">parsed in your browser by powerio (wasm); never uploaded</p>
|
|
133
|
-
{#if lc.studyInputJson}
|
|
134
|
-
<div class="case-actions">
|
|
135
|
-
<button class="reset mono" disabled={busy} onclick={() => saveCase(lc)}>
|
|
136
|
-
save PowerIO module (.json)
|
|
137
|
-
</button>
|
|
138
|
-
{#if lc.formulation === 'dcopf' && lc.solution}
|
|
139
|
-
<button class="reset mono" disabled={busy} onclick={() => saveSolution(lc)}>
|
|
140
|
-
save exact solution (.json)
|
|
141
|
-
</button>
|
|
142
|
-
{/if}
|
|
143
|
-
<div class="export">
|
|
144
|
-
<button
|
|
145
|
-
class="reset mono"
|
|
146
|
-
disabled={busy}
|
|
147
|
-
aria-expanded={exportOpen}
|
|
148
|
-
onclick={() => (exportOpen = !exportOpen)}
|
|
149
|
-
>
|
|
150
|
-
export committed state…
|
|
151
|
-
</button>
|
|
152
|
-
{#if exportOpen}
|
|
153
|
-
<ul class="export-menu mono">
|
|
154
|
-
{#each EXPORT_FORMATS as f (f.token)}
|
|
155
|
-
<li>
|
|
156
|
-
<button
|
|
157
|
-
class="reset mono"
|
|
158
|
-
disabled={busy}
|
|
159
|
-
onclick={() => exportCase(lc, f.token)}
|
|
160
|
-
>
|
|
161
|
-
{f.label}
|
|
162
|
-
</button>
|
|
163
|
-
</li>
|
|
164
|
-
{/each}
|
|
165
|
-
</ul>
|
|
166
|
-
{/if}
|
|
130
|
+
{#if lc.displayMode === 'diagram' && lc.diagram}
|
|
131
|
+
<div>
|
|
132
|
+
<dt>Drawing</dt>
|
|
133
|
+
<dd>{lc.diagram.name}</dd>
|
|
167
134
|
</div>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
135
|
+
{:else}
|
|
136
|
+
<div>
|
|
137
|
+
<dt>Coordinates</dt>
|
|
138
|
+
<dd>{coordinateSource(lc)}</dd>
|
|
139
|
+
</div>
|
|
140
|
+
{/if}
|
|
141
|
+
<div>
|
|
142
|
+
<dt>File</dt>
|
|
143
|
+
<dd class="mono">{lc.fileName}</dd>
|
|
175
144
|
</div>
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
{#
|
|
182
|
-
<li>+{exportDiagnostics.length - 4} more</li>
|
|
183
|
-
{/if}
|
|
145
|
+
</dl>
|
|
146
|
+
{#if lc.displayMode === 'diagram' && lc.diagram?.warnings.length}
|
|
147
|
+
<details>
|
|
148
|
+
<summary>Drawing details</summary>
|
|
149
|
+
<ul class="warnings">
|
|
150
|
+
{#each lc.diagram.warnings as warning, i (i)}<li>{warning}</li>{/each}
|
|
184
151
|
</ul>
|
|
152
|
+
</details>
|
|
153
|
+
{/if}
|
|
154
|
+
{/if}
|
|
155
|
+
{@render note?.(lc)}
|
|
156
|
+
{#if lc.summary && lc.studyInputJson}
|
|
157
|
+
<div class="case-actions">
|
|
158
|
+
<button class="reset" disabled={busy} onclick={() => saveCase(lc)}>Save case</button>
|
|
159
|
+
{#if lc.formulation === 'dcopf' && lc.solution}
|
|
160
|
+
<button class="reset" disabled={busy} onclick={() => saveSolution(lc)}>Save solution</button
|
|
161
|
+
>
|
|
162
|
+
{/if}
|
|
163
|
+
<button
|
|
164
|
+
class="reset"
|
|
165
|
+
disabled={busy}
|
|
166
|
+
aria-expanded={exportOpen}
|
|
167
|
+
aria-controls="case-export-formats"
|
|
168
|
+
onclick={() => (exportOpen = !exportOpen)}
|
|
169
|
+
>
|
|
170
|
+
Export as
|
|
171
|
+
</button>
|
|
172
|
+
{#if lc.view || lc.diagram}
|
|
173
|
+
<button class="reset" disabled={busy} onclick={() => downloadLayout(lc)}>
|
|
174
|
+
{lc.displayMode === 'diagram' ? 'Save drawing' : 'Save geography'}
|
|
175
|
+
</button>
|
|
185
176
|
{/if}
|
|
177
|
+
</div>
|
|
178
|
+
{#if exportOpen}
|
|
179
|
+
<ul class="case-actions" id="case-export-formats" aria-label="Export formats">
|
|
180
|
+
{#each EXPORT_FORMATS as f (f.token)}
|
|
181
|
+
<li>
|
|
182
|
+
<button class="reset" disabled={busy} onclick={() => exportCase(lc, f.token)}>
|
|
183
|
+
{f.label}
|
|
184
|
+
</button>
|
|
185
|
+
</li>
|
|
186
|
+
{/each}
|
|
187
|
+
</ul>
|
|
188
|
+
{/if}
|
|
189
|
+
{#if exportDiagnostics.length > 0}
|
|
190
|
+
<ul class="warnings">
|
|
191
|
+
{#each exportDiagnostics.slice(0, 4) as diagnostic, i (i)}
|
|
192
|
+
<li>{formatPowerIoDiagnostic(diagnostic)}</li>
|
|
193
|
+
{/each}
|
|
194
|
+
{#if exportDiagnostics.length > 4}
|
|
195
|
+
<li>+{exportDiagnostics.length - 4} more</li>
|
|
196
|
+
{/if}
|
|
197
|
+
</ul>
|
|
186
198
|
{/if}
|
|
187
199
|
{/if}
|
|
188
200
|
{#if lc.displayMode !== 'diagram' && lc.topology && lc.coordsKind !== 'file'}
|
|
189
|
-
<button class="reset
|
|
201
|
+
<button class="reset" onclick={() => ctrl.moveLocalCase(lc)}>
|
|
190
202
|
{lc.coordsKind === 'synthetic_pending'
|
|
191
|
-
? '
|
|
203
|
+
? 'Place on map'
|
|
192
204
|
: lc.coordsKind === 'geofile'
|
|
193
|
-
? '
|
|
194
|
-
: '
|
|
205
|
+
? 'Place manually'
|
|
206
|
+
: 'Move layout'}
|
|
195
207
|
</button>
|
|
196
208
|
{/if}
|
|
197
|
-
<button class="reset
|
|
209
|
+
<button class="reset" onclick={() => ctrl.removeLocalCase(lc)}>Remove</button>
|
|
198
210
|
{/if}
|
|
199
211
|
|
|
200
212
|
<style>
|
|
@@ -212,22 +224,25 @@
|
|
|
212
224
|
dl div {
|
|
213
225
|
display: flex;
|
|
214
226
|
justify-content: space-between;
|
|
227
|
+
gap: 12px;
|
|
215
228
|
padding: 3px 0;
|
|
216
229
|
}
|
|
217
230
|
|
|
218
231
|
dt {
|
|
232
|
+
flex: none;
|
|
219
233
|
color: var(--text-secondary);
|
|
220
234
|
}
|
|
221
235
|
|
|
222
236
|
dd {
|
|
237
|
+
min-width: 0;
|
|
223
238
|
margin: 0;
|
|
239
|
+
text-align: right;
|
|
240
|
+
overflow-wrap: anywhere;
|
|
224
241
|
}
|
|
225
242
|
|
|
226
|
-
|
|
227
|
-
margin: 8px
|
|
243
|
+
details {
|
|
244
|
+
margin-top: 8px;
|
|
228
245
|
font-size: 12px;
|
|
229
|
-
color: var(--text-tertiary);
|
|
230
|
-
letter-spacing: 0;
|
|
231
246
|
}
|
|
232
247
|
|
|
233
248
|
.warnings {
|
|
@@ -243,27 +258,12 @@
|
|
|
243
258
|
display: flex;
|
|
244
259
|
flex-wrap: wrap;
|
|
245
260
|
gap: 8px;
|
|
246
|
-
margin
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
.export {
|
|
250
|
-
position: relative;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
.export-menu {
|
|
254
|
-
position: absolute;
|
|
255
|
-
z-index: 1;
|
|
256
|
-
margin: 4px 0 0;
|
|
257
|
-
padding: 4px;
|
|
261
|
+
margin: 10px 0 0;
|
|
262
|
+
padding: 0;
|
|
258
263
|
list-style: none;
|
|
259
|
-
min-width: 100%;
|
|
260
|
-
background: var(--surface, var(--surface-menu));
|
|
261
|
-
border: 1px solid var(--border, rgba(0, 0, 0, 0.15));
|
|
262
|
-
border-radius: 6px;
|
|
263
|
-
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
.
|
|
267
|
-
|
|
266
|
+
.case-actions .reset {
|
|
267
|
+
margin-top: 0;
|
|
268
268
|
}
|
|
269
269
|
</style>
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { LocalCase } from '../state.svelte.js';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
note?: Snippet<[LocalCase]>;
|
|
5
|
+
};
|
|
6
|
+
declare const LocalCaseDetails: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
2
7
|
type LocalCaseDetails = ReturnType<typeof LocalCaseDetails>;
|
|
3
8
|
export default LocalCaseDetails;
|