@tellegen/svelte 0.4.3 → 0.4.4

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 (105) hide show
  1. package/README.md +20 -0
  2. package/dist/TellegenMap.svelte +63 -22
  3. package/dist/TellegenProvider.svelte +7 -4
  4. package/dist/TellegenShell.svelte +10 -1
  5. package/dist/TellegenViewer.svelte +2 -0
  6. package/dist/basemap.d.ts +14 -0
  7. package/dist/basemap.js +27 -0
  8. package/dist/colors.d.ts +6 -1
  9. package/dist/colors.js +11 -4
  10. package/dist/components/FormulationSelector.svelte +1 -1
  11. package/dist/components/LocalCaseDetails.svelte +18 -3
  12. package/dist/components/McResults.svelte +18 -3
  13. package/dist/components/McWorkbench.svelte +688 -0
  14. package/dist/components/McWorkbench.svelte.d.ts +38 -0
  15. package/dist/components/MultiWire.svelte +136 -0
  16. package/dist/components/MultiWire.svelte.d.ts +7 -0
  17. package/dist/components/MulticonductorDetails.svelte +7 -0
  18. package/dist/components/OmniSearch.svelte +163 -0
  19. package/dist/components/OmniSearch.svelte.d.ts +14 -0
  20. package/dist/components/ResultsTable.svelte +259 -0
  21. package/dist/components/ResultsTable.svelte.d.ts +13 -0
  22. package/dist/components/SchematicCanvas.svelte +1896 -0
  23. package/dist/components/SchematicCanvas.svelte.d.ts +13 -0
  24. package/dist/components/SolveCard.svelte +6 -0
  25. package/dist/components/schematic/PickCard.svelte +126 -0
  26. package/dist/components/schematic/PickCard.svelte.d.ts +56 -0
  27. package/dist/components/schematic/export.d.ts +21 -0
  28. package/dist/components/schematic/export.js +110 -0
  29. package/dist/components/schematic/layers.d.ts +161 -0
  30. package/dist/components/schematic/layers.js +756 -0
  31. package/dist/components/schematic/paint.d.ts +28 -0
  32. package/dist/components/schematic/paint.js +69 -0
  33. package/dist/components/schematic/palette.d.ts +34 -0
  34. package/dist/components/schematic/palette.js +54 -0
  35. package/dist/components/schematic/text.d.ts +83 -0
  36. package/dist/components/schematic/text.js +190 -0
  37. package/dist/components/schematic/view-prefs.svelte.d.ts +30 -0
  38. package/dist/components/schematic/view-prefs.svelte.js +77 -0
  39. package/dist/components.d.ts +1 -0
  40. package/dist/components.js +1 -0
  41. package/dist/context.svelte.d.ts +2 -0
  42. package/dist/context.svelte.js +2 -0
  43. package/dist/controller.svelte.d.ts +33 -5
  44. package/dist/controller.svelte.js +173 -20
  45. package/dist/diagram/basemap.d.ts +28 -0
  46. package/dist/diagram/basemap.js +39 -0
  47. package/dist/diagram/bus-stacks.d.ts +19 -0
  48. package/dist/diagram/bus-stacks.js +99 -0
  49. package/dist/diagram/colour-modes.d.ts +18 -0
  50. package/dist/diagram/colour-modes.js +33 -0
  51. package/dist/diagram/connections.d.ts +30 -0
  52. package/dist/diagram/connections.js +129 -0
  53. package/dist/diagram/display-tree.d.ts +21 -0
  54. package/dist/diagram/display-tree.js +65 -0
  55. package/dist/diagram/energisation.d.ts +10 -0
  56. package/dist/diagram/energisation.js +31 -0
  57. package/dist/diagram/feeders.d.ts +30 -0
  58. package/dist/diagram/feeders.js +57 -0
  59. package/dist/diagram/galvanic-zones.d.ts +60 -0
  60. package/dist/diagram/galvanic-zones.js +210 -0
  61. package/dist/diagram/graph-inputs.d.ts +18 -0
  62. package/dist/diagram/graph-inputs.js +103 -0
  63. package/dist/diagram/index.d.ts +28 -0
  64. package/dist/diagram/index.js +28 -0
  65. package/dist/diagram/landmarks.d.ts +50 -0
  66. package/dist/diagram/landmarks.js +153 -0
  67. package/dist/diagram/layout-morph.d.ts +15 -0
  68. package/dist/diagram/layout-morph.js +32 -0
  69. package/dist/diagram/multi-wire.d.ts +44 -0
  70. package/dist/diagram/multi-wire.js +65 -0
  71. package/dist/diagram/phase-bars.d.ts +35 -0
  72. package/dist/diagram/phase-bars.js +44 -0
  73. package/dist/diagram/results-table.d.ts +47 -0
  74. package/dist/diagram/results-table.js +173 -0
  75. package/dist/diagram/schematic-equipment.d.ts +133 -0
  76. package/dist/diagram/schematic-equipment.js +645 -0
  77. package/dist/diagram/schematic-layout.d.ts +51 -0
  78. package/dist/diagram/schematic-layout.js +240 -0
  79. package/dist/diagram/search.d.ts +36 -0
  80. package/dist/diagram/search.js +48 -0
  81. package/dist/diagram/shared-routes.d.ts +21 -0
  82. package/dist/diagram/shared-routes.js +40 -0
  83. package/dist/diagram/short-names.d.ts +11 -0
  84. package/dist/diagram/short-names.js +54 -0
  85. package/dist/diagram/snapshot.d.ts +115 -0
  86. package/dist/diagram/snapshot.js +242 -0
  87. package/dist/diagram/summary.d.ts +65 -0
  88. package/dist/diagram/summary.js +144 -0
  89. package/dist/diagram/svg-export.d.ts +80 -0
  90. package/dist/diagram/svg-export.js +108 -0
  91. package/dist/diagram/tree-walk.d.ts +13 -0
  92. package/dist/diagram/tree-walk.js +83 -0
  93. package/dist/diagram/value-labels.d.ts +24 -0
  94. package/dist/diagram/value-labels.js +69 -0
  95. package/dist/diagram/view-state.d.ts +12 -0
  96. package/dist/diagram/view-state.js +22 -0
  97. package/dist/diagram/voltage-levels.d.ts +14 -0
  98. package/dist/diagram/voltage-levels.js +20 -0
  99. package/dist/index.d.ts +1 -0
  100. package/dist/index.js +1 -0
  101. package/dist/state.svelte.d.ts +37 -1
  102. package/dist/state.svelte.js +35 -0
  103. package/dist/transformer-icon.d.ts +22 -13
  104. package/dist/transformer-icon.js +23 -13
  105. package/package.json +2 -2
@@ -0,0 +1,28 @@
1
+ import type { RGBA } from '../../colors.js';
2
+ import type { Landmark, SchematicGeometry } from '../../diagram/index.js';
3
+ /** Per-segment widths: MV heavier than LV. */
4
+ export declare function segmentWidths(g: SchematicGeometry, edgeMv: Uint8Array, resultColours: boolean): Float32Array;
5
+ /** Drawn segments replaced by their fanned path right now. */
6
+ export interface FanState {
7
+ edges: Set<number>;
8
+ host: Int32Array | null;
9
+ geometry: SchematicGeometry;
10
+ }
11
+ export declare function hiddenUnderFan(d: SchematicGeometry, s: number, fan: FanState): boolean;
12
+ /** Per-segment colours: the edge's colour, quieter for open or unreachable branches, hidden under
13
+ * a fanned route, and faded for LV when zoomed out. */
14
+ export declare function segmentColours(g: SchematicGeometry, edgeColours: RGBA[], edgeMv: Uint8Array, zoomedOut: boolean, fan: FanState): Uint8Array;
15
+ /** Per symbol stroke: ink, or faint when its bus or branch is not energised; hidden for a branch
16
+ * whose route is fanned (its marks are redrawn on the fanned path). An owner is an edge index, or
17
+ * `-bus - 1` for a bus. */
18
+ export declare function strokeColours(owner: Int32Array, fannedEdges: Set<number>, live: {
19
+ edges: ArrayLike<boolean | number>;
20
+ buses: ArrayLike<boolean | number>;
21
+ }): Uint8Array;
22
+ /** Landmark strokes in ink; a transformer whose network has a problem takes that state's colour. */
23
+ export declare function landmarkColours(m: {
24
+ placed: Landmark[];
25
+ strokes: {
26
+ owner: Int32Array;
27
+ };
28
+ }): Uint8Array;
@@ -0,0 +1,69 @@
1
+ import { DANGER, DEAD, HIDDEN, INK, SYMBOL_INK, WARNING, lineWidths } from './palette.js';
2
+ /** Per-segment widths: MV heavier than LV. */
3
+ export function segmentWidths(g, edgeMv, resultColours) {
4
+ const [mv, lv] = lineWidths(resultColours);
5
+ return Float32Array.from(g.segmentEdge, (k) => (edgeMv[k] ? mv : lv));
6
+ }
7
+ /** Whether drawn segment `s` of edge `k` lies on the edge's shared (host) segment of `geometry`,
8
+ * so it is replaced by the fanned path while its route is fanned. */
9
+ function onHost(d, s, k, host, geometry) {
10
+ const h = host[k] ?? -1;
11
+ if (h < 0)
12
+ return false;
13
+ const [ax, ay, bx, by] = [
14
+ geometry.sources[h * 3],
15
+ geometry.sources[h * 3 + 1],
16
+ geometry.targets[h * 3],
17
+ geometry.targets[h * 3 + 1]
18
+ ];
19
+ const len = Math.hypot(bx - ax, by - ay) || 1;
20
+ const off = (x, y) => Math.abs((bx - ax) * (ay - y) - (ax - x) * (by - ay)) / len;
21
+ const tol = len * 1e-6 + 1e-9;
22
+ return (off(d.sources[s * 3], d.sources[s * 3 + 1]) <= tol &&
23
+ off(d.targets[s * 3], d.targets[s * 3 + 1]) <= tol);
24
+ }
25
+ export function hiddenUnderFan(d, s, fan) {
26
+ const k = d.segmentEdge[s];
27
+ return !!fan.host && fan.edges.has(k) && onHost(d, s, k, fan.host, fan.geometry);
28
+ }
29
+ /** Per-segment colours: the edge's colour, quieter for open or unreachable branches, hidden under
30
+ * a fanned route, and faded for LV when zoomed out. */
31
+ export function segmentColours(g, edgeColours, edgeMv, zoomedOut, fan) {
32
+ const out = new Uint8Array(g.segmentEdge.length * 4);
33
+ for (let s = 0; s < g.segmentEdge.length; s++) {
34
+ const k = g.segmentEdge[s];
35
+ const c = edgeColours[k];
36
+ let a = g.role[k] === 2 ? Math.min(110, c[3]) : c[3];
37
+ if (hiddenUnderFan(g, s, fan))
38
+ a = 0;
39
+ if (zoomedOut && !edgeMv[k])
40
+ a *= 0.45;
41
+ out.set([c[0], c[1], c[2], a], s * 4);
42
+ }
43
+ return out;
44
+ }
45
+ /** Per symbol stroke: ink, or faint when its bus or branch is not energised; hidden for a branch
46
+ * whose route is fanned (its marks are redrawn on the fanned path). An owner is an edge index, or
47
+ * `-bus - 1` for a bus. */
48
+ export function strokeColours(owner, fannedEdges, live) {
49
+ const out = new Uint8Array(owner.length * 4);
50
+ for (let s = 0; s < owner.length; s++) {
51
+ const o = owner[s];
52
+ if (o >= 0 && fannedEdges.has(o)) {
53
+ out.set(HIDDEN, s * 4);
54
+ continue;
55
+ }
56
+ const on = o >= 0 ? live.edges[o] : live.buses[-o - 1];
57
+ out.set(on ? SYMBOL_INK : DEAD, s * 4);
58
+ }
59
+ return out;
60
+ }
61
+ /** Landmark strokes in ink; a transformer whose network has a problem takes that state's colour. */
62
+ export function landmarkColours(m) {
63
+ const out = new Uint8Array(m.strokes.owner.length * 4);
64
+ for (let s = 0; s < m.strokes.owner.length; s++) {
65
+ const l = m.placed[m.strokes.owner[s]];
66
+ out.set(l.state === 'violation' ? DANGER : l.state === 'warning' ? WARNING : INK, s * 4);
67
+ }
68
+ return out;
69
+ }
@@ -0,0 +1,34 @@
1
+ import type { RGBA } from '../../colors.js';
2
+ import type { VoltageClass, ZoneState } from '../../diagram/index.js';
3
+ export declare const INK: RGBA;
4
+ export declare const ACCENT: RGBA;
5
+ export declare const PAPER: RGBA;
6
+ export declare const DANGER: RGBA;
7
+ export declare const WARNING: RGBA;
8
+ export declare const SYMBOL_INK: RGBA;
9
+ /** Not connected to any source through closed branches: drawn faint in every colour mode. */
10
+ export declare const DEAD: RGBA;
11
+ export declare const HIDDEN: RGBA;
12
+ /** LV networks within limits: corridors, outlines and tags in the warm grey of LV lines, so blue
13
+ * stays the colour of MV and above. */
14
+ export declare const ZONE_INK: RGBA;
15
+ export declare const MONO = "IBM Plex Mono, ui-monospace, monospace";
16
+ /** A colour at another opacity. */
17
+ export declare const alpha: (c: RGBA, a: number) => RGBA;
18
+ /** Ink for a label or mark by severity. */
19
+ export declare const stateInk: (state: string | undefined) => RGBA;
20
+ /** Screen width of a zone's corridor on geographic positions. */
21
+ export declare const CORRIDOR_PX = 9;
22
+ /** Line colour per voltage class in the topology view: shades of one blue, darker for higher
23
+ * levels, and LV in the quiet grey that carries most of a distribution drawing. Clear of the
24
+ * warning and violation colours and of the accent used for selection and flow. */
25
+ export declare const LEVEL_COLOURS: Record<VoltageClass, RGBA>;
26
+ /** Hover emphasis for a network and its feeder, beneath the lines, whatever the network's state. */
27
+ export declare const HALO: RGBA;
28
+ /** The hovered element's path back to the source: green, the one family no level, state or
29
+ * selection uses, so it reads over the halos. */
30
+ export declare const PATH: RGBA;
31
+ /** Fill and outline per zone state. */
32
+ export declare const ZONE_COLOURS: Record<ZoneState, [RGBA, RGBA]>;
33
+ /** MV and LV line widths in pixels: heavier when results colour the lines. */
34
+ export declare const lineWidths: (resultColours: boolean) => [number, number];
@@ -0,0 +1,54 @@
1
+ export const INK = [32, 36, 43, 255];
2
+ export const ACCENT = [178, 94, 0, 255];
3
+ export const PAPER = [252, 251, 247, 255];
4
+ export const DANGER = [184, 50, 42, 255];
5
+ export const WARNING = [178, 94, 0, 255];
6
+ export const SYMBOL_INK = [52, 56, 62, 255];
7
+ /** Not connected to any source through closed branches: drawn faint in every colour mode. */
8
+ export const DEAD = [196, 191, 182, 110];
9
+ export const HIDDEN = [0, 0, 0, 0];
10
+ /** LV networks within limits: corridors, outlines and tags in the warm grey of LV lines, so blue
11
+ * stays the colour of MV and above. */
12
+ export const ZONE_INK = [112, 104, 90, 255];
13
+ export const MONO = 'IBM Plex Mono, ui-monospace, monospace';
14
+ /** A colour at another opacity. */
15
+ export const alpha = (c, a) => [c[0], c[1], c[2], a];
16
+ /** Ink for a label or mark by severity. */
17
+ export const stateInk = (state) => state === 'violation' ? DANGER : state === 'warning' ? WARNING : INK;
18
+ /** Screen width of a zone's corridor on geographic positions. */
19
+ export const CORRIDOR_PX = 9;
20
+ /** Line colour per voltage class in the topology view: shades of one blue, darker for higher
21
+ * levels, and LV in the quiet grey that carries most of a distribution drawing. Clear of the
22
+ * warning and violation colours and of the accent used for selection and flow. */
23
+ export const LEVEL_COLOURS = {
24
+ hv: [38, 44, 112, 240],
25
+ sub: [30, 76, 140, 240],
26
+ mv: [58, 118, 178, 235],
27
+ lv: [138, 131, 117, 210]
28
+ };
29
+ /** Hover emphasis for a network and its feeder, beneath the lines, whatever the network's state. */
30
+ export const HALO = [64, 72, 92, 255];
31
+ /** The hovered element's path back to the source: green, the one family no level, state or
32
+ * selection uses, so it reads over the halos. */
33
+ export const PATH = [16, 138, 96, 245];
34
+ /** Fill and outline per zone state. */
35
+ export const ZONE_COLOURS = {
36
+ missing: [
37
+ [112, 104, 90, 16],
38
+ [112, 104, 90, 90]
39
+ ],
40
+ normal: [
41
+ [112, 104, 90, 16],
42
+ [112, 104, 90, 90]
43
+ ],
44
+ warning: [
45
+ [178, 94, 0, 30],
46
+ [178, 94, 0, 170]
47
+ ],
48
+ violation: [
49
+ [184, 50, 42, 34],
50
+ [184, 50, 42, 190]
51
+ ]
52
+ };
53
+ /** MV and LV line widths in pixels: heavier when results colour the lines. */
54
+ export const lineWidths = (resultColours) => resultColours ? [3, 1.8] : [2.4, 1.1];
@@ -0,0 +1,83 @@
1
+ import type { PickingInfo } from '@deck.gl/core';
2
+ import type { DistGraph } from '@tellegen/engine';
3
+ import { type DisplayTree, type EquipmentItem, type Feeders, type EquipmentSymbols, type GalvanicZone, type GalvanicZones, type Landmark, type ResultSnapshot, type ZoneState } from '../../diagram/index.js';
4
+ /** What the diagram's words are made from: tooltips, announcements and the cards' rows. */
5
+ export interface TextContext {
6
+ graph: DistGraph;
7
+ zones: GalvanicZones;
8
+ zoneStates: ZoneState[];
9
+ /** Whether a zone is drawn as a network of its own (an LV network). */
10
+ outlined: (zone: GalvanicZone) => boolean;
11
+ transformerNames: Map<number, string>;
12
+ live: {
13
+ edges: ArrayLike<number>;
14
+ };
15
+ equipment: EquipmentSymbols | null;
16
+ snapshot: ResultSnapshot | null;
17
+ powers: Map<string, {
18
+ p: number;
19
+ q: number;
20
+ }> | null;
21
+ valuesOn: boolean;
22
+ edgeMv: Uint8Array;
23
+ zoneMv: boolean[];
24
+ /** Per bus, for source positions: 1 drawn next to a connected bus, 2 left at the origin. */
25
+ inferred: Uint8Array | null;
26
+ feeders: Feeders;
27
+ tree: DisplayTree;
28
+ /** Short bus names, as drawn. */
29
+ busNames: string[];
30
+ /** Close enough for terminal and transformer labels beside the symbols. */
31
+ labelsOn: boolean;
32
+ }
33
+ export declare class SchematicText {
34
+ private readonly c;
35
+ constructor(c: TextContext);
36
+ /** What supplies a network at bus `s`: its feeding transformer, or the source bus itself. */
37
+ private supplyName;
38
+ /** "feeder 2 of TX 1234", "feeder 1 of SOURCEBUS": the same words on MV and LV. */
39
+ feederName(f: number): string;
40
+ /** Where a bus sits: on a feeder, or as the bus a network is supplied at. */
41
+ networkNote(i: number): string;
42
+ /** A bus's worst pair voltage against its band, for announcements and the stack card. */
43
+ voltageNote(i: number): string | null;
44
+ bus(i: number): string;
45
+ /** Whether a branch is drawn on a route shared with others. */
46
+ isShared(k: number): boolean;
47
+ branch(k: number): string;
48
+ item(d: EquipmentItem): string;
49
+ /** An attachment's text beside its symbol: its count when several, and its solved P and Q when
50
+ * values show. Generators, IBRs and sources read as output; loads and shunts as consumption. */
51
+ itemValue(d: EquipmentItem): string;
52
+ landmark(l: Landmark): string;
53
+ zone(z: number): string;
54
+ /** Rows of a shared route's card. */
55
+ routeRows(members: number[]): {
56
+ k: number;
57
+ id: string;
58
+ kind: import("@tellegen/engine").DistEdgeKind;
59
+ level: string;
60
+ wires: string;
61
+ loading: string | null;
62
+ state: "normal" | "warning" | "violation";
63
+ }[];
64
+ /** Rows of a bus stack's card. */
65
+ stackRows(members: number[]): {
66
+ i: number;
67
+ id: string;
68
+ level: string;
69
+ attached: string;
70
+ voltage: string;
71
+ }[];
72
+ }
73
+ /** Picks and how the canvas reads them, for the tooltip. */
74
+ export interface TooltipPicks {
75
+ /** Whether this pick belongs to the stack whose card is open (the card names its buses). */
76
+ inOpenStack: (info: PickingInfo) => boolean;
77
+ /** Edge of a drawn branch segment. */
78
+ segmentEdge: (index: number) => number;
79
+ /** Whether symbols show, so shared routes are named by their card. */
80
+ symbolsOn: boolean;
81
+ }
82
+ /** The tooltip for a pick, or null where a card or nothing says it better. */
83
+ export declare function tooltip(info: PickingInfo, text: SchematicText, picks: TooltipPicks): string | null;
@@ -0,0 +1,190 @@
1
+ import { ATTACHMENT_NAMES, bandPosition, conductorSet, formatPower, loadingState, terminalLabel } from '../../diagram/index.js';
2
+ const list = (ids) => ids.slice(0, 4).join(', ') + (ids.length > 4 ? ` +${ids.length - 4}` : '');
3
+ const counted = (n, name) => (n > 1 ? `${n} ${name}s` : name);
4
+ const volts = (x, digits) => x >= 1000 ? `${(x / 1000).toFixed(digits)} kV` : `${x.toFixed(1)} V`;
5
+ const attachmentKinds = (graph, i) => new Set(Object.values(graph.buses[i].terminal_attachments ?? {}).flatMap((l) => l.map((a) => a.kind)));
6
+ export class SchematicText {
7
+ c;
8
+ constructor(c) {
9
+ this.c = c;
10
+ }
11
+ /** What supplies a network at bus `s`: its feeding transformer, or the source bus itself. */
12
+ supplyName(s) {
13
+ const { tree, graph, transformerNames, busNames } = this.c;
14
+ const k = tree.parentEdge[s];
15
+ return k >= 0 ? (transformerNames.get(k) ?? graph.edges[k].id) : busNames[s];
16
+ }
17
+ /** "feeder 2 of TX 1234", "feeder 1 of SOURCEBUS": the same words on MV and LV. */
18
+ feederName(f) {
19
+ const feeder = this.c.feeders.feeders[f];
20
+ return `feeder ${feeder.number} of ${this.supplyName(feeder.supply)}`;
21
+ }
22
+ /** Where a bus sits: on a feeder, or as the bus a network is supplied at. */
23
+ networkNote(i) {
24
+ const f = this.c.feeders.busFeeder[i];
25
+ if (f >= 0)
26
+ return ` · ${this.feederName(f)}`;
27
+ return this.c.tree.parentEdge[i] >= 0
28
+ ? ` · supply bus of the network fed by ${this.supplyName(i)}`
29
+ : ' · source bus';
30
+ }
31
+ /** A bus's worst pair voltage against its band, for announcements and the stack card. */
32
+ voltageNote(i) {
33
+ const pairs = this.c.snapshot?.buses[i]?.pairs ?? [];
34
+ if (!pairs.length)
35
+ return null;
36
+ const worst = pairs.reduce((a, b) => Math.abs(bandPosition(b) ?? 0) > Math.abs(bandPosition(a) ?? 0) ? b : a);
37
+ const t = bandPosition(worst);
38
+ const band = worst.min !== null && worst.max !== null
39
+ ? `, ${t !== null && Math.abs(t) > 1 ? 'outside' : 'within'} ${volts(worst.min, 3)} to ${volts(worst.max, 3)}`
40
+ : '';
41
+ return `Worst voltage ${worst.pair.join('–')} ${volts(worst.volts, 3)}${band}`;
42
+ }
43
+ bus(i) {
44
+ const how = this.c.inferred?.[i] ?? 0;
45
+ const id = this.c.graph.buses[i].id + this.networkNote(i);
46
+ return how === 1
47
+ ? `${id} · no position in the case data; drawn next to a connected bus`
48
+ : how === 2
49
+ ? `${id} · no position in the case data`
50
+ : id;
51
+ }
52
+ /** Whether a branch is drawn on a route shared with others. */
53
+ isShared(k) {
54
+ return (this.c.equipment?.routeOf[k] ?? -1) >= 0;
55
+ }
56
+ branch(k) {
57
+ const { graph, live, equipment } = this.c;
58
+ const e = graph.edges[k];
59
+ const { phases, neutral } = conductorSet(graph, k);
60
+ const wires = `${phases} phase${phases === 1 ? '' : 's'}${neutral ? ' + neutral' : ''}`;
61
+ const shared = equipment?.sharedRoute[k] ?? 1;
62
+ const route = shared > 1
63
+ ? ` · drawn on a route shared with ${shared - 1} other branch${shared > 2 ? 'es' : ''}`
64
+ : '';
65
+ const f = this.c.feeders.edgeFeeder[k];
66
+ const feeder = f >= 0 ? ` · ${this.feederName(f)}` : '';
67
+ return `${e.kind} ${e.id} · ${wires}${e.closed ? '' : ' · open'}${live.edges[k] ? '' : ' · not energised'}${feeder}${route}`;
68
+ }
69
+ item(d) {
70
+ const { graph } = this.c;
71
+ const name = d.kind in ATTACHMENT_NAMES ? ATTACHMENT_NAMES[d.kind] : d.kind;
72
+ if (d.kind === 'ground')
73
+ return `solid earth: terminal${d.ids.length > 1 ? 's' : ''} ${d.ids.join(', ')} of ${graph.buses[d.bus].id} perfectly grounded`;
74
+ const terminals = d.terminals ? ` · terminals ${terminalLabel(d.terminals, d.connection)}` : '';
75
+ const label = d.label ? ` · ${d.label}` : '';
76
+ return d.bus >= 0
77
+ ? `${counted(d.ids.length, name)} ${list(d.ids)} at ${graph.buses[d.bus].id}${terminals}${this.networkNote(d.bus)}`
78
+ : `${name} ${list(d.ids)}${label}${graph.edges[d.edge].closed ? '' : ' (open)'}`;
79
+ }
80
+ /** An attachment's text beside its symbol: its count when several, and its solved P and Q when
81
+ * values show. Generators, IBRs and sources read as output; loads and shunts as consumption. */
82
+ itemValue(d) {
83
+ const { valuesOn, powers, labelsOn } = this.c;
84
+ const parts = [];
85
+ if (labelsOn && d.terminals)
86
+ parts.push(terminalLabel(d.terminals, d.connection));
87
+ if (d.ids.length > 1)
88
+ parts.push(`×${d.ids.length}`);
89
+ if (valuesOn && powers) {
90
+ let p = 0;
91
+ let q = 0;
92
+ let found = false;
93
+ for (const id of d.ids) {
94
+ const row = powers.get(id);
95
+ if (!row)
96
+ continue;
97
+ p += row.p;
98
+ q += row.q;
99
+ found = true;
100
+ }
101
+ const output = d.kind === 'generator' || d.kind === 'ibr' || d.kind === 'source';
102
+ // Below 1 VA (an earthing shunt, say) the value is noise, not information.
103
+ if (found && Math.hypot(p, q) >= 1)
104
+ parts.push(output ? formatPower(-p, -q) : formatPower(p, q));
105
+ }
106
+ return parts.join(' ');
107
+ }
108
+ landmark(l) {
109
+ const name = {
110
+ source: 'source',
111
+ generator: 'generator',
112
+ ibr: 'inverter-based resource',
113
+ transformer: 'transformer'
114
+ }[l.kind];
115
+ const where = l.bus >= 0 ? ` at ${this.c.graph.buses[l.bus].id}` : '';
116
+ return `${counted(l.ids.length, name)} ${list(l.ids)}${where}`;
117
+ }
118
+ zone(z) {
119
+ const { zones, zoneStates, graph } = this.c;
120
+ const zone = zones.zones[z];
121
+ const t = zone.transformers[0];
122
+ const verdict = {
123
+ violation: 'a limit is violated',
124
+ warning: 'a branch is near its rating',
125
+ normal: 'within limits',
126
+ missing: 'no result'
127
+ }[zoneStates[z]];
128
+ const fed = t === undefined ? 'Network' : `Network fed by ${graph.edges[t].kind} ${graph.edges[t].id}`;
129
+ return `${fed}: ${zone.buses.length} buses, ${verdict}`;
130
+ }
131
+ /** Rows of a shared route's card. */
132
+ routeRows(members) {
133
+ const { graph, snapshot, edgeMv } = this.c;
134
+ return members.map((k) => {
135
+ const e = graph.edges[k];
136
+ const { phases, neutral } = conductorSet(graph, k);
137
+ const loading = snapshot?.branches[k]?.loading ?? null;
138
+ const state = loadingState(loading);
139
+ return {
140
+ k,
141
+ id: e.id,
142
+ kind: e.kind,
143
+ level: edgeMv[k] ? 'MV' : 'LV',
144
+ wires: `${phases}${neutral ? '+N' : ''}`,
145
+ loading: loading === null ? null : `${loading.toFixed(0)} %`,
146
+ state: state === 'missing' ? 'normal' : state
147
+ };
148
+ });
149
+ }
150
+ /** Rows of a bus stack's card. */
151
+ stackRows(members) {
152
+ const { graph, zones, zoneMv } = this.c;
153
+ return members.map((i) => {
154
+ const note = this.voltageNote(i);
155
+ return {
156
+ i,
157
+ id: graph.buses[i].id,
158
+ level: zoneMv[zones.busZone[i]] ? 'MV' : 'LV',
159
+ attached: [...attachmentKinds(graph, i)].join(', '),
160
+ voltage: note ? note.replace(/^Worst voltage /, '').replace(/, (within|outside).*/, '') : ''
161
+ };
162
+ });
163
+ }
164
+ }
165
+ /** The tooltip for a pick, or null where a card or nothing says it better. */
166
+ export function tooltip(info, text, picks) {
167
+ const id = info.layer?.id;
168
+ if (info.index < 0 || !id)
169
+ return null;
170
+ // A stack's ring opens its card, which says the rest; inside an open stack the card names the
171
+ // buses, and a tooltip would cover them. A fanned route's card names its branches.
172
+ if (id === 'stack-rings' || picks.inOpenStack(info) || id === 'fan-branches')
173
+ return null;
174
+ if (id === 'route-badges')
175
+ return `${info.object.n} branches share this route. Hover to separate them; click to keep them open.`;
176
+ if (id === 'landmark-halos')
177
+ return text.landmark(info.object);
178
+ if (id.startsWith('zones'))
179
+ return `${text.zone(info.object.z)}. Click to fit.`;
180
+ if (id.startsWith('buses-'))
181
+ return text.bus(info.object);
182
+ if (id.startsWith('branches-')) {
183
+ const k = picks.segmentEdge(info.index);
184
+ // A shared route's card names its branches; its hidden true route stays silent.
185
+ return picks.symbolsOn && text.isShared(k) ? null : text.branch(k);
186
+ }
187
+ if (id === 'equipment-hits')
188
+ return text.item(info.object);
189
+ return null;
190
+ }
@@ -0,0 +1,30 @@
1
+ /** A viewer preference remembered in local storage. Storage can be unavailable (private windows,
2
+ * blocked site data); the preference then still works for this view. */
3
+ export declare class StoredFlag {
4
+ private readonly key;
5
+ /** Stored values for on and off. */
6
+ private readonly words;
7
+ value: boolean;
8
+ constructor(key: string,
9
+ /** Stored values for on and off. */
10
+ words: [string, string], fallback: boolean);
11
+ set(on: boolean): void;
12
+ }
13
+ /** One of several hover targets whose card opens on hover and can be pinned open by a click:
14
+ * shared routes and stacked buses. Leaving starts a short grace period so the pointer can reach
15
+ * the card; while the card itself is hovered it stays open. */
16
+ export declare class HoverPin {
17
+ hovered: number | null;
18
+ pinned: number | null;
19
+ private cardHovered;
20
+ private timer;
21
+ /** The target whose card shows: pinned, else hovered. */
22
+ get active(): number | null;
23
+ open(g: number): void;
24
+ leave(): void;
25
+ enterCard(): void;
26
+ leaveCard(): void;
27
+ close(): void;
28
+ dispose(): void;
29
+ private cancelLeave;
30
+ }
@@ -0,0 +1,77 @@
1
+ /** A viewer preference remembered in local storage. Storage can be unavailable (private windows,
2
+ * blocked site data); the preference then still works for this view. */
3
+ export class StoredFlag {
4
+ key;
5
+ words;
6
+ value = $state(false);
7
+ constructor(key,
8
+ /** Stored values for on and off. */
9
+ words, fallback) {
10
+ this.key = key;
11
+ this.words = words;
12
+ let stored = null;
13
+ try {
14
+ stored = localStorage.getItem(key);
15
+ }
16
+ catch {
17
+ // Unavailable storage leaves the default.
18
+ }
19
+ this.value = stored === words[0] ? true : stored === words[1] ? false : fallback;
20
+ }
21
+ set(on) {
22
+ this.value = on;
23
+ try {
24
+ localStorage.setItem(this.key, this.words[on ? 0 : 1]);
25
+ }
26
+ catch {
27
+ // The preference still applies to this view.
28
+ }
29
+ }
30
+ }
31
+ /** One of several hover targets whose card opens on hover and can be pinned open by a click:
32
+ * shared routes and stacked buses. Leaving starts a short grace period so the pointer can reach
33
+ * the card; while the card itself is hovered it stays open. */
34
+ export class HoverPin {
35
+ hovered = $state(null);
36
+ pinned = $state(null);
37
+ cardHovered = false;
38
+ timer = null;
39
+ /** The target whose card shows: pinned, else hovered. */
40
+ get active() {
41
+ return this.pinned ?? this.hovered;
42
+ }
43
+ open(g) {
44
+ this.cancelLeave();
45
+ if (this.hovered !== g)
46
+ this.hovered = g;
47
+ }
48
+ leave() {
49
+ if (this.hovered === null || this.timer || this.cardHovered)
50
+ return;
51
+ this.timer = setTimeout(() => {
52
+ this.timer = null;
53
+ if (!this.cardHovered)
54
+ this.hovered = null;
55
+ }, 350);
56
+ }
57
+ enterCard() {
58
+ this.cardHovered = true;
59
+ this.cancelLeave();
60
+ }
61
+ leaveCard() {
62
+ this.cardHovered = false;
63
+ this.leave();
64
+ }
65
+ close() {
66
+ this.pinned = null;
67
+ this.hovered = null;
68
+ }
69
+ dispose() {
70
+ this.cancelLeave();
71
+ }
72
+ cancelLeave() {
73
+ if (this.timer)
74
+ clearTimeout(this.timer);
75
+ this.timer = null;
76
+ }
77
+ }
@@ -21,3 +21,4 @@ export { default as SolveCard } from './components/SolveCard.svelte';
21
21
  export { default as TopMovers } from './components/TopMovers.svelte';
22
22
  export { default as Sparkline } from './Sparkline.svelte';
23
23
  export { default as McResults } from './components/McResults.svelte';
24
+ export { default as McWorkbench } from './components/McWorkbench.svelte';
@@ -21,3 +21,4 @@ export { default as SolveCard } from './components/SolveCard.svelte';
21
21
  export { default as TopMovers } from './components/TopMovers.svelte';
22
22
  export { default as Sparkline } from './Sparkline.svelte';
23
23
  export { default as McResults } from './components/McResults.svelte';
24
+ export { default as McWorkbench } from './components/McWorkbench.svelte';
@@ -3,6 +3,8 @@ import type { Controller } from './controller.svelte.js';
3
3
  export interface TellegenUiConfig {
4
4
  /** Backend base path; `null` for a host with no backend at all. */
5
5
  apiBase: string | null;
6
+ /** URL of the separately built, development-only AC OPF WASI asset. */
7
+ acOpfWasmUrl: string | null;
6
8
  loadDefaultCases: boolean;
7
9
  docsHref: string;
8
10
  orgHref: string;
@@ -5,6 +5,7 @@ const CTRL = Symbol('tellegen.controller');
5
5
  const CONFIG = Symbol('tellegen.config');
6
6
  export const DEFAULT_TELLEGEN_UI_CONFIG = {
7
7
  apiBase: '/api',
8
+ acOpfWasmUrl: null,
8
9
  loadDefaultCases: true,
9
10
  docsHref: 'https://eigenergy.github.io/tellegen/',
10
11
  orgHref: 'https://github.com/eigenergy',
@@ -17,6 +18,7 @@ export function resolveTellegenUiConfig(options = {}) {
17
18
  return {
18
19
  apiBase: options.apiBase === undefined ? DEFAULT_TELLEGEN_UI_CONFIG.apiBase : options.apiBase,
19
20
  loadDefaultCases: options.loadDefaultCases ?? DEFAULT_TELLEGEN_UI_CONFIG.loadDefaultCases,
21
+ acOpfWasmUrl: options.acOpfWasmUrl ?? DEFAULT_TELLEGEN_UI_CONFIG.acOpfWasmUrl,
20
22
  docsHref: options.docsHref ?? DEFAULT_TELLEGEN_UI_CONFIG.docsHref,
21
23
  orgHref: options.orgHref ?? DEFAULT_TELLEGEN_UI_CONFIG.orgHref,
22
24
  orgLabel: options.orgLabel ?? DEFAULT_TELLEGEN_UI_CONFIG.orgLabel,