@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.151 → 2.0.0-next.152

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 (136) hide show
  1. package/.storybook/vitest.setup.ts +12 -1
  2. package/bundle/openbridge-webcomponents.bundle.js +989 -319
  3. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  4. package/custom-elements.json +1514 -77
  5. package/dist/alert-severity.d.ts +12 -9
  6. package/dist/alert-severity.d.ts.map +1 -1
  7. package/dist/alert-severity.js +30 -38
  8. package/dist/alert-severity.js.map +1 -1
  9. package/dist/building-blocks/bar-horizontal/bar-horizontal.d.ts +6 -0
  10. package/dist/building-blocks/bar-horizontal/bar-horizontal.d.ts.map +1 -1
  11. package/dist/building-blocks/bar-horizontal/bar-horizontal.js +6 -1
  12. package/dist/building-blocks/bar-horizontal/bar-horizontal.js.map +1 -1
  13. package/dist/building-blocks/bar-vertical/bar-vertical.d.ts +6 -0
  14. package/dist/building-blocks/bar-vertical/bar-vertical.d.ts.map +1 -1
  15. package/dist/building-blocks/bar-vertical/bar-vertical.js +6 -1
  16. package/dist/building-blocks/bar-vertical/bar-vertical.js.map +1 -1
  17. package/dist/building-blocks/chart-line/chart-line-base.d.ts +129 -0
  18. package/dist/building-blocks/chart-line/chart-line-base.d.ts.map +1 -1
  19. package/dist/building-blocks/chart-line/chart-line-base.js +464 -79
  20. package/dist/building-blocks/chart-line/chart-line-base.js.map +1 -1
  21. package/dist/building-blocks/external-scale/external-scale.d.ts +11 -0
  22. package/dist/building-blocks/external-scale/external-scale.d.ts.map +1 -1
  23. package/dist/building-blocks/external-scale/external-scale.js +45 -12
  24. package/dist/building-blocks/external-scale/external-scale.js.map +1 -1
  25. package/dist/charthelpers/index.d.ts +1 -0
  26. package/dist/charthelpers/index.d.ts.map +1 -1
  27. package/dist/charthelpers/index.js +7 -1
  28. package/dist/charthelpers/index.js.map +1 -1
  29. package/dist/charthelpers/range-labels.d.ts +55 -0
  30. package/dist/charthelpers/range-labels.d.ts.map +1 -0
  31. package/dist/charthelpers/range-labels.js +36 -0
  32. package/dist/charthelpers/range-labels.js.map +1 -0
  33. package/dist/components/alert-button/alert-button.css.js +13 -21
  34. package/dist/components/alert-button/alert-button.css.js.map +1 -1
  35. package/dist/components/alert-button/alert-button.d.ts +9 -4
  36. package/dist/components/alert-button/alert-button.d.ts.map +1 -1
  37. package/dist/components/alert-button/alert-button.js +24 -15
  38. package/dist/components/alert-button/alert-button.js.map +1 -1
  39. package/dist/components/alert-frame/alert-frame.css.js +109 -33
  40. package/dist/components/alert-frame/alert-frame.css.js.map +1 -1
  41. package/dist/components/alert-frame/alert-frame.d.ts +34 -7
  42. package/dist/components/alert-frame/alert-frame.d.ts.map +1 -1
  43. package/dist/components/alert-frame/alert-frame.js +134 -30
  44. package/dist/components/alert-frame/alert-frame.js.map +1 -1
  45. package/dist/components/alert-icon/alert-icon.d.ts +10 -7
  46. package/dist/components/alert-icon/alert-icon.d.ts.map +1 -1
  47. package/dist/components/alert-icon/alert-icon.js +37 -60
  48. package/dist/components/alert-icon/alert-icon.js.map +1 -1
  49. package/dist/components/alert-menu-item/alert-menu-item.css.js +1 -4
  50. package/dist/components/alert-menu-item/alert-menu-item.css.js.map +1 -1
  51. package/dist/components/date-item/date-item.css.js +3 -3
  52. package/dist/components/keyboard-full/keyboard-full.css.js +4 -4
  53. package/dist/components/message-menu-item/message-menu-item.css.js +2 -6
  54. package/dist/components/message-menu-item/message-menu-item.css.js.map +1 -1
  55. package/dist/components/sequence-toolbar/sequence-toolbar.css.js +1 -2
  56. package/dist/components/sequence-toolbar/sequence-toolbar.css.js.map +1 -1
  57. package/dist/index.js +2 -1
  58. package/dist/navigation-instruments/gauge-trend/gauge-trend.d.ts +6 -0
  59. package/dist/navigation-instruments/gauge-trend/gauge-trend.d.ts.map +1 -1
  60. package/dist/navigation-instruments/gauge-trend/gauge-trend.js +8 -0
  61. package/dist/navigation-instruments/gauge-trend/gauge-trend.js.map +1 -1
  62. package/dist/navigation-instruments/instrument-field/instrument-field.css.js +2 -2
  63. package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
  64. package/dist/navigation-instruments/readout/readout.js +2 -1
  65. package/dist/navigation-instruments/readout/readout.js.map +1 -1
  66. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
  67. package/dist/navigation-instruments/readout-list-item/readout-list-item.js +2 -1
  68. package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
  69. package/dist/openbridge.css +19 -4
  70. package/dist/palettes/blinking.d.ts +14 -4
  71. package/dist/palettes/blinking.d.ts.map +1 -1
  72. package/dist/palettes/blinking.js +41 -35
  73. package/dist/palettes/blinking.js.map +1 -1
  74. package/dist/palettes/flashing-controller.d.ts +21 -0
  75. package/dist/palettes/flashing-controller.d.ts.map +1 -0
  76. package/dist/palettes/flashing-controller.js +45 -0
  77. package/dist/palettes/flashing-controller.js.map +1 -0
  78. package/dist/svghelpers/index.d.ts +1 -0
  79. package/dist/svghelpers/index.d.ts.map +1 -1
  80. package/dist/svghelpers/index.js +2 -0
  81. package/dist/svghelpers/index.js.map +1 -1
  82. package/dist/svghelpers/rounded-rect.d.ts +15 -0
  83. package/dist/svghelpers/rounded-rect.d.ts.map +1 -0
  84. package/dist/svghelpers/rounded-rect.js +29 -0
  85. package/dist/svghelpers/rounded-rect.js.map +1 -0
  86. package/dist/types.d.ts +14 -0
  87. package/dist/types.d.ts.map +1 -1
  88. package/dist/types.js +9 -0
  89. package/dist/types.js.map +1 -1
  90. package/package.json +1 -1
  91. package/script/check-css-variables.ts +2 -15
  92. package/src/alert-severity.spec.ts +56 -0
  93. package/src/alert-severity.ts +45 -43
  94. package/src/bars-graphs/area-graph/area-graph.stories.ts +235 -1
  95. package/src/bars-graphs/line-graph/line-graph.stories.ts +24 -0
  96. package/src/building-blocks/bar-horizontal/bar-horizontal.ts +7 -0
  97. package/src/building-blocks/bar-vertical/bar-vertical.ts +7 -0
  98. package/src/building-blocks/chart-line/chart-line-base.spec.ts +767 -0
  99. package/src/building-blocks/chart-line/chart-line-base.stories.ts +124 -0
  100. package/src/building-blocks/chart-line/chart-line-base.ts +671 -97
  101. package/src/building-blocks/chart-line/chart-sizing-battleground.stories.ts +17 -0
  102. package/src/building-blocks/external-scale/external-scale.spec.ts +154 -0
  103. package/src/building-blocks/external-scale/external-scale.ts +78 -20
  104. package/src/charthelpers/index.ts +1 -0
  105. package/src/charthelpers/range-labels.spec.ts +46 -0
  106. package/src/charthelpers/range-labels.ts +93 -0
  107. package/src/components/alert-button/alert-button.css +10 -23
  108. package/src/components/alert-button/alert-button.spec.ts +56 -15
  109. package/src/components/alert-button/alert-button.stories.ts +5 -1
  110. package/src/components/alert-button/alert-button.ts +33 -24
  111. package/src/components/alert-frame/alert-flash-scenario.stories.ts +669 -0
  112. package/src/components/alert-frame/alert-frame.css +106 -33
  113. package/src/components/alert-frame/alert-frame.spec.ts +176 -17
  114. package/src/components/alert-frame/alert-frame.stories.ts +61 -0
  115. package/src/components/alert-frame/alert-frame.ts +179 -41
  116. package/src/components/alert-icon/alert-icon.spec.ts +82 -27
  117. package/src/components/alert-icon/alert-icon.stories.ts +7 -1
  118. package/src/components/alert-icon/alert-icon.ts +48 -74
  119. package/src/components/alert-menu-item/alert-menu-item.css +1 -4
  120. package/src/components/date-item/date-item.css +3 -3
  121. package/src/components/keyboard-full/keyboard-full.css +4 -4
  122. package/src/components/message-menu-item/message-menu-item.css +2 -6
  123. package/src/components/sequence-toolbar/sequence-toolbar.css +1 -2
  124. package/src/navigation-instruments/gauge-trend/gauge-trend.stories.ts +29 -0
  125. package/src/navigation-instruments/gauge-trend/gauge-trend.ts +11 -0
  126. package/src/navigation-instruments/instrument-field/instrument-field.css +2 -2
  127. package/src/navigation-instruments/readout/readout.ts +2 -1
  128. package/src/navigation-instruments/readout-list-item/readout-list-item.ts +2 -1
  129. package/src/palettes/blinking.spec.ts +83 -0
  130. package/src/palettes/blinking.ts +47 -35
  131. package/src/palettes/flashing-controller.ts +62 -0
  132. package/src/palettes/manual.css +18 -4
  133. package/src/svghelpers/index.ts +1 -0
  134. package/src/svghelpers/rounded-rect.spec.ts +47 -0
  135. package/src/svghelpers/rounded-rect.ts +42 -0
  136. package/src/types.ts +20 -0
@@ -0,0 +1,669 @@
1
+ import type {Meta, StoryObj} from '@storybook/web-components-vite';
2
+ import {LitElement, html, nothing, type TemplateResult} from 'lit';
3
+ import {property, state} from 'lit/decorators.js';
4
+ import {repeat} from 'lit/directives/repeat.js';
5
+ import {
6
+ ObcAlertFrameFlashEffect,
7
+ ObcAlertFrameMode,
8
+ ObcAlertFrameType,
9
+ } from './alert-frame.js';
10
+ import './alert-frame.js';
11
+ import '../alert-button/alert-button.js';
12
+ import '../alert-icon/alert-icon.js';
13
+ import '../alert-menu/alert-menu.js';
14
+ import {ObcAlertMenuItemStatus} from '../alert-menu-item/alert-menu-item.js';
15
+ import '../alert-menu-item/alert-menu-item.js';
16
+ import {ButtonVariant} from '../button/button.js';
17
+ import '../button/button.js';
18
+ import '../card/card.js';
19
+ import '../clock/clock.js';
20
+ import '../top-bar/top-bar.js';
21
+ import {ObcTopbarMessageItemType} from '../topbar-message-item/topbar-message-item.js';
22
+ import '../topbar-message-item/topbar-message-item.js';
23
+ import '../../navigation-instruments/readout-list-item/readout-list-item.js';
24
+ import {requiresAcknowledgement} from '../../alert-severity.js';
25
+ import {ALERT_SEVERITY_PRIORITY, AlertType} from '../../types.js';
26
+
27
+ interface ScenarioReadout {
28
+ label: string;
29
+ unit: string;
30
+ /** Value while the alert condition is present. */
31
+ alerting: number;
32
+ normal: number;
33
+ }
34
+
35
+ interface AlertState {
36
+ present: boolean;
37
+ active: boolean;
38
+ acknowledged: boolean;
39
+ /** Timeline second the alert was raised; negative when raised before the start. */
40
+ raisedAt: number;
41
+ }
42
+
43
+ interface ScenarioPanel {
44
+ id: string;
45
+ title: string;
46
+ alertType: AlertType;
47
+ alertTitle: string;
48
+ alertDescription: string;
49
+ frameType: ObcAlertFrameType;
50
+ readouts: ScenarioReadout[];
51
+ initial: AlertState;
52
+ }
53
+
54
+ enum ScenarioChange {
55
+ Raise = 'raise',
56
+ Rectify = 'rectify',
57
+ Clear = 'clear',
58
+ }
59
+
60
+ interface TimelineStep {
61
+ at: number;
62
+ panel: string;
63
+ change: ScenarioChange;
64
+ }
65
+
66
+ const raised = (raisedAt: number): AlertState => ({
67
+ present: true,
68
+ active: true,
69
+ acknowledged: false,
70
+ raisedAt,
71
+ });
72
+
73
+ const rectified = (raisedAt: number): AlertState => ({
74
+ present: true,
75
+ active: false,
76
+ acknowledged: false,
77
+ raisedAt,
78
+ });
79
+
80
+ const acknowledged = (raisedAt: number): AlertState => ({
81
+ present: true,
82
+ active: true,
83
+ acknowledged: true,
84
+ raisedAt,
85
+ });
86
+
87
+ const QUIET: AlertState = {
88
+ present: false,
89
+ active: false,
90
+ acknowledged: false,
91
+ raisedAt: 0,
92
+ };
93
+
94
+ /**
95
+ * At the start every design level (critical, alarm, warning, caution) shows
96
+ * one solid and one dashed frame, and one alarm is acknowledged.
97
+ */
98
+ const PANELS: ScenarioPanel[] = [
99
+ {
100
+ id: 'fire',
101
+ title: 'Fire detection',
102
+ alertType: AlertType.LevelCritical,
103
+ alertTitle: 'Fire in engine room',
104
+ alertDescription: 'Smoke and heat detectors triggered in zone 3',
105
+ frameType: ObcAlertFrameType.BottomFlip,
106
+ readouts: [
107
+ {label: 'Zone 3 temperature', unit: '°C', alerting: 86, normal: 31},
108
+ {label: 'Smoke density', unit: '%', alerting: 42, normal: 0},
109
+ ],
110
+ initial: raised(-95),
111
+ },
112
+ {
113
+ id: 'switchboard',
114
+ title: 'Main switchboard',
115
+ alertType: AlertType.LevelCritical,
116
+ alertTitle: 'Blackout on bus B',
117
+ alertDescription: 'Bus B supplied from the emergency generator',
118
+ frameType: ObcAlertFrameType.SmallSideFlip,
119
+ readouts: [
120
+ {label: 'Bus A', unit: 'V', alerting: 440, normal: 440},
121
+ {label: 'Bus B', unit: 'V', alerting: 0, normal: 438},
122
+ ],
123
+ initial: rectified(-240),
124
+ },
125
+ {
126
+ id: 'generator',
127
+ title: 'Generator 2',
128
+ alertType: AlertType.Alarm,
129
+ alertTitle: 'Generator 2 overload',
130
+ alertDescription: 'Load above 95 % of rated power',
131
+ frameType: ObcAlertFrameType.SmallSideFlip,
132
+ readouts: [
133
+ {label: 'Load', unit: '%', alerting: 97, normal: 78},
134
+ {label: 'Output', unit: 'kW', alerting: 1940, normal: 1560},
135
+ ],
136
+ initial: raised(-60),
137
+ },
138
+ {
139
+ id: 'tank',
140
+ title: 'Service tank',
141
+ alertType: AlertType.Alarm,
142
+ alertTitle: 'Service tank level low',
143
+ alertDescription: 'Fuel level below 30 %',
144
+ frameType: ObcAlertFrameType.SmallSideFlip,
145
+ readouts: [
146
+ {label: 'Level', unit: '%', alerting: 27, normal: 34},
147
+ {label: 'Consumption', unit: 'L/h', alerting: 210, normal: 210},
148
+ ],
149
+ initial: rectified(-420),
150
+ },
151
+ {
152
+ id: 'ballast',
153
+ title: 'Ballast pump 1',
154
+ alertType: AlertType.Alarm,
155
+ alertTitle: 'Ballast pump 1 tripped',
156
+ alertDescription: 'Motor overcurrent protection released',
157
+ frameType: ObcAlertFrameType.SmallSideFlip,
158
+ readouts: [
159
+ {label: 'Motor current', unit: 'A', alerting: 0, normal: 42},
160
+ {label: 'Flow', unit: 'm³/h', alerting: 0, normal: 180},
161
+ ],
162
+ initial: acknowledged(-300),
163
+ },
164
+ {
165
+ id: 'cooling',
166
+ title: 'Main engine',
167
+ alertType: AlertType.Warning,
168
+ alertTitle: 'Cooling water temperature high',
169
+ alertDescription: 'Jacket water outlet above 90 °C',
170
+ frameType: ObcAlertFrameType.SmallSideFlip,
171
+ readouts: [
172
+ {label: 'Cooling water', unit: '°C', alerting: 92, normal: 84},
173
+ {label: 'Speed', unit: 'rpm', alerting: 84, normal: 84},
174
+ ],
175
+ initial: raised(-150),
176
+ },
177
+ {
178
+ id: 'bilge',
179
+ title: 'Bilge',
180
+ alertType: AlertType.Warning,
181
+ alertTitle: 'Bilge level high',
182
+ alertDescription: 'Engine room bilge well 2',
183
+ frameType: ObcAlertFrameType.SmallSideFlip,
184
+ readouts: [
185
+ {label: 'Well 2 level', unit: '%', alerting: 71, normal: 12},
186
+ {label: 'Well 3 level', unit: '%', alerting: 9, normal: 9},
187
+ ],
188
+ initial: rectified(-380),
189
+ },
190
+ {
191
+ id: 'steering',
192
+ title: 'Steering gear',
193
+ alertType: AlertType.Warning,
194
+ alertTitle: 'Steering pump 2 oil level low',
195
+ alertDescription: 'Hydraulic oil below the refill mark',
196
+ frameType: ObcAlertFrameType.SmallSideFlip,
197
+ readouts: [
198
+ {label: 'Pump 2 oil level', unit: '%', alerting: 18, normal: 64},
199
+ {label: 'Rudder', unit: '°', alerting: 3, normal: 3},
200
+ ],
201
+ initial: QUIET,
202
+ },
203
+ {
204
+ id: 'gnss',
205
+ title: 'GNSS',
206
+ alertType: AlertType.Caution,
207
+ alertTitle: 'GNSS 2 signal degraded',
208
+ alertDescription: 'Position accuracy reduced',
209
+ frameType: ObcAlertFrameType.SmallSideFlip,
210
+ readouts: [
211
+ {label: 'Satellites', unit: 'sats', alerting: 6, normal: 14},
212
+ {label: 'Accuracy', unit: 'm', alerting: 12, normal: 2},
213
+ ],
214
+ initial: raised(-40),
215
+ },
216
+ {
217
+ id: 'freshwater',
218
+ title: 'Fresh water',
219
+ alertType: AlertType.Caution,
220
+ alertTitle: 'Fresh water pressure low',
221
+ alertDescription: 'Hydrophore pressure below 300 kPa',
222
+ frameType: ObcAlertFrameType.SmallSideFlip,
223
+ readouts: [
224
+ {label: 'Pressure', unit: 'kPa', alerting: 280, normal: 390},
225
+ {label: 'Tank level', unit: '%', alerting: 72, normal: 72},
226
+ ],
227
+ initial: rectified(-200),
228
+ },
229
+ ];
230
+
231
+ const PANEL_BY_ID = new Map(PANELS.map((panel) => [panel.id, panel]));
232
+
233
+ const TIMELINE: TimelineStep[] = [
234
+ {at: 5, panel: 'steering', change: ScenarioChange.Raise},
235
+ {at: 10, panel: 'generator', change: ScenarioChange.Rectify},
236
+ {at: 15, panel: 'gnss', change: ScenarioChange.Rectify},
237
+ {at: 20, panel: 'cooling', change: ScenarioChange.Rectify},
238
+ {at: 25, panel: 'freshwater', change: ScenarioChange.Clear},
239
+ {at: 30, panel: 'gnss', change: ScenarioChange.Clear},
240
+ {at: 35, panel: 'ballast', change: ScenarioChange.Rectify},
241
+ ];
242
+
243
+ const TIMELINE_LENGTH_S = 40;
244
+
245
+ const CHANGE_LABEL: Record<ScenarioChange, string> = {
246
+ [ScenarioChange.Raise]: 'raises an alert',
247
+ [ScenarioChange.Rectify]: 'rectifies',
248
+ [ScenarioChange.Clear]: 'clears',
249
+ };
250
+
251
+ const START_UTC_MS = Date.UTC(2026, 8, 14, 9, 41, 0);
252
+
253
+ function clockDate(second: number): string {
254
+ return new Date(START_UTC_MS + second * 1000).toISOString();
255
+ }
256
+
257
+ function clockTime(second: number): string {
258
+ return clockDate(second).slice(11, 19);
259
+ }
260
+
261
+ function elapsedTime(seconds: number): string {
262
+ const minutes = String(Math.floor(seconds / 60)).padStart(2, '0');
263
+ return `${minutes}:${String(seconds % 60).padStart(2, '0')}`;
264
+ }
265
+
266
+ function initialAlerts(): Record<string, AlertState> {
267
+ return Object.fromEntries(
268
+ PANELS.map((panel) => [panel.id, {...panel.initial}])
269
+ );
270
+ }
271
+
272
+ /** Acknowledged and rectified alerts are gone, so they have no frame. */
273
+ function frameMode(alert: AlertState): ObcAlertFrameMode | undefined {
274
+ if (!alert.present) {
275
+ return undefined;
276
+ }
277
+ if (!alert.active) {
278
+ return ObcAlertFrameMode.unackedRectified;
279
+ }
280
+ return alert.acknowledged
281
+ ? ObcAlertFrameMode.ackedActive
282
+ : ObcAlertFrameMode.unackedActive;
283
+ }
284
+
285
+ function needsAcknowledgement(
286
+ panel: ScenarioPanel,
287
+ alert: AlertState
288
+ ): boolean {
289
+ return (
290
+ alert.present &&
291
+ !alert.acknowledged &&
292
+ requiresAcknowledgement(panel.alertType)
293
+ );
294
+ }
295
+
296
+ function menuStatus(
297
+ panel: ScenarioPanel,
298
+ alert: AlertState
299
+ ): ObcAlertMenuItemStatus {
300
+ if (!requiresAcknowledgement(panel.alertType)) {
301
+ return ObcAlertMenuItemStatus.Caution;
302
+ }
303
+ return alert.acknowledged
304
+ ? ObcAlertMenuItemStatus.Acknowledged
305
+ : ObcAlertMenuItemStatus.Unacknowledged;
306
+ }
307
+
308
+ /**
309
+ * `<alert-flash-scenario-story>` – Live alert scenario for judging the flash
310
+ * tempos and frame effects together.
311
+ *
312
+ * Ten panels in alert frames, the top bar message and alert button, and the
313
+ * alert menu read one alert state. A 40-second timeline raises, rectifies and
314
+ * clears alerts, then restarts; ACK in the top bar or the menu acknowledges.
315
+ *
316
+ * @property flashEffect - Flash effect of every frame in the scenario.
317
+ * @property runTimeline - Advances the timeline once a second; off holds the
318
+ * current state.
319
+ * @property showAlertMenu - Shows the alert menu beside the panels; the alert
320
+ * button toggles it.
321
+ */
322
+ class AlertFlashScenarioStory extends LitElement {
323
+ @property({type: String}) flashEffect = ObcAlertFrameFlashEffect.Outline;
324
+ @property({type: Boolean, attribute: false}) runTimeline = true;
325
+ @property({type: Boolean, attribute: false}) showAlertMenu = true;
326
+
327
+ @state() private second = 0;
328
+ @state() private alerts = initialAlerts();
329
+
330
+ private timer?: ReturnType<typeof setInterval>;
331
+
332
+ override createRenderRoot() {
333
+ return this;
334
+ }
335
+
336
+ override connectedCallback() {
337
+ super.connectedCallback();
338
+ this.syncTimer();
339
+ }
340
+
341
+ override disconnectedCallback() {
342
+ super.disconnectedCallback();
343
+ this.syncTimer();
344
+ }
345
+
346
+ override updated() {
347
+ this.syncTimer();
348
+ }
349
+
350
+ tick() {
351
+ const next = this.second + 1;
352
+ if (next >= TIMELINE_LENGTH_S) {
353
+ this.restart();
354
+ return;
355
+ }
356
+ this.second = next;
357
+ for (const step of TIMELINE.filter((s) => s.at === next)) {
358
+ this.apply(step);
359
+ }
360
+ }
361
+
362
+ restart() {
363
+ this.second = 0;
364
+ this.alerts = initialAlerts();
365
+ }
366
+
367
+ acknowledge(id: string) {
368
+ const alert = this.alerts[id];
369
+ this.alerts = {
370
+ ...this.alerts,
371
+ [id]: alert.active
372
+ ? {...alert, acknowledged: true}
373
+ : {...alert, present: false},
374
+ };
375
+ }
376
+
377
+ private apply({panel, change}: TimelineStep) {
378
+ const alert = this.alerts[panel];
379
+ let next: AlertState;
380
+ switch (change) {
381
+ case ScenarioChange.Raise:
382
+ next = raised(this.second);
383
+ break;
384
+ case ScenarioChange.Rectify:
385
+ next = alert.acknowledged
386
+ ? {...alert, present: false}
387
+ : {...alert, active: false};
388
+ break;
389
+ case ScenarioChange.Clear:
390
+ next = {...alert, present: false};
391
+ break;
392
+ }
393
+ this.alerts = {...this.alerts, [panel]: next};
394
+ }
395
+
396
+ private syncTimer() {
397
+ const wanted = this.runTimeline && this.isConnected;
398
+ if (wanted && !this.timer) {
399
+ this.timer = setInterval(() => this.tick(), 1000);
400
+ } else if (!wanted && this.timer) {
401
+ clearInterval(this.timer);
402
+ this.timer = undefined;
403
+ }
404
+ }
405
+
406
+ /** Pending acknowledgements first, then by severity, newest first. */
407
+ private visiblePanels(): ScenarioPanel[] {
408
+ const rank = (panel: ScenarioPanel) =>
409
+ ALERT_SEVERITY_PRIORITY.indexOf(panel.alertType);
410
+ const pending = (panel: ScenarioPanel) =>
411
+ needsAcknowledgement(panel, this.alerts[panel.id]);
412
+ return PANELS.filter((panel) => this.alerts[panel.id].present).sort(
413
+ (a, b) =>
414
+ Number(pending(b)) - Number(pending(a)) ||
415
+ rank(a) - rank(b) ||
416
+ this.alerts[b.id].raisedAt - this.alerts[a.id].raisedAt
417
+ );
418
+ }
419
+
420
+ private renderIcon(panel: ScenarioPanel, slot: string): TemplateResult {
421
+ const alert = this.alerts[panel.id];
422
+ return html`<obc-alert-icon
423
+ slot=${slot}
424
+ .alertType=${panel.alertType}
425
+ .active=${alert.active}
426
+ .acknowledged=${alert.acknowledged}
427
+ ></obc-alert-icon>`;
428
+ }
429
+
430
+ private renderMessage(top: ScenarioPanel | undefined): TemplateResult {
431
+ if (!top) {
432
+ return html`<obc-topbar-message-item
433
+ slot="alerts"
434
+ .type=${ObcTopbarMessageItemType.Inactive}
435
+ ></obc-topbar-message-item>`;
436
+ }
437
+ const alert = this.alerts[top.id];
438
+ return html`<obc-topbar-message-item
439
+ slot="alerts"
440
+ .type=${needsAcknowledgement(top, alert)
441
+ ? ObcTopbarMessageItemType.WithButton
442
+ : ObcTopbarMessageItemType.Simple}
443
+ @action-click=${() => this.acknowledge(top.id)}
444
+ >
445
+ ${this.renderIcon(top, 'primary-icon')}
446
+ <div slot="title">${top.alertTitle}</div>
447
+ <div slot="description">${top.alertDescription}</div>
448
+ <div slot="time">${clockTime(alert.raisedAt)}</div>
449
+ <div slot="action-text">ACK</div>
450
+ </obc-topbar-message-item>`;
451
+ }
452
+
453
+ private renderTopBar(visible: ScenarioPanel[]): TemplateResult {
454
+ const top = visible[0];
455
+ return html`<obc-top-bar
456
+ .appTitle=${'Machinery'}
457
+ .pageName=${'Alert flash scenario'}
458
+ .showClock=${true}
459
+ >
460
+ ${this.renderMessage(top)}
461
+ <obc-alert-button
462
+ slot="alerts"
463
+ .nAlerts=${visible.length}
464
+ .alertType=${top?.alertType}
465
+ .counter=${true}
466
+ .blinking=${visible.some((panel) =>
467
+ needsAcknowledgement(panel, this.alerts[panel.id])
468
+ )}
469
+ @click-alert=${() => (this.showAlertMenu = !this.showAlertMenu)}
470
+ ></obc-alert-button>
471
+ <obc-clock slot="clock" .date=${clockDate(this.second)}></obc-clock>
472
+ </obc-top-bar>`;
473
+ }
474
+
475
+ private renderPanel(panel: ScenarioPanel): TemplateResult {
476
+ const alert = this.alerts[panel.id];
477
+ const mode = frameMode(alert);
478
+ const alerting = alert.present && alert.active;
479
+ const card = html`<obc-card>
480
+ <span slot="title">${panel.title}</span>
481
+ ${panel.readouts.map(
482
+ (readout) =>
483
+ html`<obc-readout-list-item
484
+ .label=${readout.label}
485
+ .unit=${readout.unit}
486
+ .value=${alerting ? readout.alerting : readout.normal}
487
+ ></obc-readout-list-item>`
488
+ )}
489
+ </obc-card>`;
490
+ if (!mode) {
491
+ return html`<div>${card}</div>`;
492
+ }
493
+ return html`<obc-alert-frame
494
+ .type=${panel.frameType}
495
+ .status=${panel.alertType}
496
+ .mode=${mode}
497
+ .flashEffect=${this.flashEffect}
498
+ .wrapContent=${true}
499
+ .fullWidth=${true}
500
+ >
501
+ ${card}
502
+ ${panel.frameType === ObcAlertFrameType.BottomFlip
503
+ ? html`<div slot="label">${panel.alertTitle}</div>
504
+ <div slot="timer">
505
+ ${elapsedTime(this.second - alert.raisedAt)}
506
+ </div>`
507
+ : nothing}
508
+ </obc-alert-frame>`;
509
+ }
510
+
511
+ private renderMenu(visible: ScenarioPanel[]): TemplateResult {
512
+ const pending = visible.filter((panel) =>
513
+ needsAcknowledgement(panel, this.alerts[panel.id])
514
+ );
515
+ return html`<obc-alert-menu
516
+ .hasShelved=${false}
517
+ .showSilenceButton=${false}
518
+ .showAlertListButton=${false}
519
+ .canAckAll=${pending.length > 0}
520
+ @ack-all-visible-click=${() =>
521
+ pending.forEach((panel) => this.acknowledge(panel.id))}
522
+ >
523
+ ${repeat(
524
+ visible,
525
+ (panel) => panel.id,
526
+ (panel) =>
527
+ html`<obc-alert-menu-item
528
+ .status=${menuStatus(panel, this.alerts[panel.id])}
529
+ .title=${panel.alertTitle}
530
+ .description=${panel.alertDescription}
531
+ .time=${clockTime(this.alerts[panel.id].raisedAt)}
532
+ @ack-click=${() => this.acknowledge(panel.id)}
533
+ >
534
+ ${this.renderIcon(panel, 'alert-icon')}
535
+ </obc-alert-menu-item>`
536
+ )}
537
+ </obc-alert-menu>`;
538
+ }
539
+
540
+ private renderStatus(): TemplateResult {
541
+ const next = TIMELINE.find((step) => step.at > this.second);
542
+ const upcoming = next
543
+ ? `${PANEL_BY_ID.get(next.panel)?.title} ${CHANGE_LABEL[next.change]} at ${next.at} s`
544
+ : `restarts at ${TIMELINE_LENGTH_S} s`;
545
+ return html`<div class="afs-status">
546
+ <span>
547
+ ${this.runTimeline ? 'Timeline' : 'Timeline paused at'} ${this.second} s
548
+ of ${TIMELINE_LENGTH_S} s · next: ${upcoming}
549
+ </span>
550
+ <obc-button .variant=${ButtonVariant.flat} @click=${() => this.restart()}
551
+ >Restart</obc-button
552
+ >
553
+ </div>`;
554
+ }
555
+
556
+ override render() {
557
+ const visible = this.visiblePanels();
558
+ return html`
559
+ <style>
560
+ .afs {
561
+ display: flex;
562
+ flex-direction: column;
563
+ height: 100vh;
564
+ background: var(--container-background-color);
565
+ font-family: var(--global-typography-font-family);
566
+ }
567
+ .afs-body {
568
+ display: flex;
569
+ flex: 1;
570
+ gap: 16px;
571
+ /* lets the grid scroll inside the viewport instead of growing the page */
572
+ min-height: 0;
573
+ padding: 16px;
574
+ }
575
+ .afs-main {
576
+ display: flex;
577
+ flex: 1;
578
+ flex-direction: column;
579
+ gap: 8px;
580
+ min-width: 0;
581
+ }
582
+ .afs-status {
583
+ display: flex;
584
+ align-items: center;
585
+ justify-content: space-between;
586
+ gap: 16px;
587
+ font-size: var(--global-typography-ui-label-font-size);
588
+ line-height: var(--global-typography-ui-label-line-height);
589
+ color: var(--element-neutral-color);
590
+ }
591
+ .afs-grid {
592
+ display: grid;
593
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
594
+ /* the side and bottom flaps sit outside their frames */
595
+ gap: 48px 40px;
596
+ align-content: start;
597
+ overflow: auto;
598
+ padding: 8px 32px 40px 8px;
599
+ }
600
+ </style>
601
+ <div class="afs">
602
+ ${this.renderTopBar(visible)}
603
+ <div class="afs-body">
604
+ <div class="afs-main">
605
+ ${this.renderStatus()}
606
+ <div class="afs-grid">
607
+ ${PANELS.map((panel) => this.renderPanel(panel))}
608
+ </div>
609
+ </div>
610
+ ${this.showAlertMenu ? this.renderMenu(visible) : nothing}
611
+ </div>
612
+ </div>
613
+ `;
614
+ }
615
+ }
616
+
617
+ if (!customElements.get('alert-flash-scenario-story')) {
618
+ customElements.define('alert-flash-scenario-story', AlertFlashScenarioStory);
619
+ }
620
+
621
+ interface ScenarioArgs {
622
+ flashEffect: ObcAlertFrameFlashEffect;
623
+ runTimeline: boolean;
624
+ showAlertMenu: boolean;
625
+ }
626
+
627
+ const meta: Meta<ScenarioArgs> = {
628
+ title: 'UI Components/Message and Alerts/Alert Flash Scenario',
629
+ tags: ['skip-test'],
630
+ parameters: {
631
+ layout: 'fullscreen',
632
+ docs: {
633
+ description: {
634
+ component:
635
+ 'A live scenario for comparing the frame flash effects in context. ' +
636
+ 'At the start every alert level shows a solid (active) and a dashed ' +
637
+ '(rectified) frame; a 40-second timeline then raises, rectifies and ' +
638
+ 'clears alerts and restarts. ACK in the top bar message or the alert ' +
639
+ 'menu acknowledges. `flashEffect` switches every frame between the ' +
640
+ 'instant step and the eased growth; the icons and the alert button ' +
641
+ 'flash on the same tempo table either way.',
642
+ },
643
+ },
644
+ },
645
+ args: {
646
+ flashEffect: ObcAlertFrameFlashEffect.Outline,
647
+ runTimeline: true,
648
+ showAlertMenu: true,
649
+ },
650
+ argTypes: {
651
+ flashEffect: {
652
+ options: Object.values(ObcAlertFrameFlashEffect),
653
+ control: {type: 'radio'},
654
+ },
655
+ runTimeline: {control: {type: 'boolean'}},
656
+ showAlertMenu: {control: {type: 'boolean'}},
657
+ },
658
+ render: (args) =>
659
+ html`<alert-flash-scenario-story
660
+ .flashEffect=${args.flashEffect}
661
+ .runTimeline=${args.runTimeline}
662
+ .showAlertMenu=${args.showAlertMenu}
663
+ ></alert-flash-scenario-story>`,
664
+ };
665
+
666
+ export default meta;
667
+ type Story = StoryObj<ScenarioArgs>;
668
+
669
+ export const Scenario: Story = {};