@oicl/openbridge-webcomponents 0.0.15-dev-20240916083108 → 0.0.15-dev-20240916185711

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 (33) hide show
  1. package/__snapshots__/navigation-instruments-azimuth-thruster--in-command-at-setpoint-disable-auto-setpoint.png +0 -0
  2. package/__snapshots__/navigation-instruments-azimuth-thruster--in-command-at-setpoint.png +0 -0
  3. package/__snapshots__/navigation-instruments-azimuth-thruster--in-command.png +0 -0
  4. package/__snapshots__/navigation-instruments-azimuth-thruster--pod.png +0 -0
  5. package/__snapshots__/navigation-instruments-azimuth-thruster--single-direction-with-propeller.png +0 -0
  6. package/__snapshots__/navigation-instruments-azimuth-thruster--single-direction.png +0 -0
  7. package/__snapshots__/navigation-instruments-azimuth-thruster-labeled--large.png +0 -0
  8. package/__snapshots__/navigation-instruments-main-engine--active.png +0 -0
  9. package/__snapshots__/navigation-instruments-main-engine--in-command.png +0 -0
  10. package/__snapshots__/navigation-instruments-main-engine--off.png +0 -0
  11. package/__snapshots__/navigation-instruments-thruster--tunnel.png +0 -0
  12. package/custom-elements.json +570 -7
  13. package/dist/navigation-instruments/azimuth-thruster/azimuth-thruster.d.ts.map +1 -1
  14. package/dist/navigation-instruments/azimuth-thruster/azimuth-thruster.js +2 -1
  15. package/dist/navigation-instruments/azimuth-thruster/azimuth-thruster.js.map +1 -1
  16. package/dist/navigation-instruments/main-engine/main-engine.css.js +22 -0
  17. package/dist/navigation-instruments/main-engine/main-engine.css.js.map +1 -0
  18. package/dist/navigation-instruments/main-engine/main-engine.d.ts +29 -0
  19. package/dist/navigation-instruments/main-engine/main-engine.d.ts.map +1 -0
  20. package/dist/navigation-instruments/main-engine/main-engine.js +196 -0
  21. package/dist/navigation-instruments/main-engine/main-engine.js.map +1 -0
  22. package/dist/navigation-instruments/thruster/thruster.d.ts +54 -1
  23. package/dist/navigation-instruments/thruster/thruster.d.ts.map +1 -1
  24. package/dist/navigation-instruments/thruster/thruster.js +163 -99
  25. package/dist/navigation-instruments/thruster/thruster.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/navigation-instruments/azimuth-thruster/azimuth-thruster.ts +1 -0
  28. package/src/navigation-instruments/main-engine/main-engine.css +17 -0
  29. package/src/navigation-instruments/main-engine/main-engine.stories.ts +54 -0
  30. package/src/navigation-instruments/main-engine/main-engine.ts +160 -0
  31. package/src/navigation-instruments/thruster/thruster.stories.ts +1 -0
  32. package/src/navigation-instruments/thruster/thruster.ts +205 -113
  33. package/src/palettes/variables.css +1343 -1343
@@ -0,0 +1,160 @@
1
+ import {LitElement, html, unsafeCSS, svg, nothing} from 'lit';
2
+ import {customElement, property} from 'lit/decorators.js';
3
+ import compentStyle from './main-engine.css?inline';
4
+ import {InstrumentState} from '../types';
5
+ import {
6
+ atSetpoint,
7
+ convertThrustAdvices,
8
+ thrusterColors,
9
+ thrusterTopSingleSided,
10
+ setpointSvg,
11
+ } from '../thruster/thruster';
12
+ import {LinearAdvice} from '../thruster/advice';
13
+
14
+ @customElement('obc-main-engine')
15
+ export class ObcMainEngine extends LitElement {
16
+ @property({type: Number}) thrust: number = 0;
17
+ @property({type: Number}) thrustSetpoint: number | undefined;
18
+ @property({type: Boolean}) thrustTouching: boolean = false;
19
+ @property({type: Boolean}) atThrustSetpoint: boolean = false;
20
+ @property({type: Number}) speed: number = 0;
21
+ @property({type: Number}) speedSetpoint: number | undefined;
22
+ @property({type: Boolean}) speedTouching: boolean = false;
23
+ @property({type: Boolean}) atSpeedSetpoint: boolean = false;
24
+ @property({type: Boolean}) disableAutoAtThrustSetpoint: boolean = false;
25
+ @property({type: Boolean}) disableAutoAtSpeedSetpoint: boolean = false;
26
+ @property({type: Number}) autoAtThrustSetpointDeadband: number = 1;
27
+ @property({type: Number}) autoAtSpeedSetpointDeadband: number = 1;
28
+ @property({type: Number}) thrustSetpointAtZeroDeadband: number = 0.5;
29
+ @property({type: Number}) speedSetpointAtZeroDeadband: number = 0.5;
30
+ @property({type: String}) state: InstrumentState = InstrumentState.inCommand;
31
+ @property({type: Array}) thrustAdvices: LinearAdvice[] = [];
32
+
33
+ override render() {
34
+ const thrustSetpointAtZero =
35
+ Math.abs(this.thrustSetpoint || 0) < this.thrustSetpointAtZeroDeadband;
36
+ const speedSetpointAtZero =
37
+ Math.abs(this.speedSetpoint || 0) < this.speedSetpointAtZeroDeadband;
38
+ const thrustAtSetpoint = atSetpoint(this.thrust, this.thrustSetpoint, {
39
+ atSetpoint: this.atThrustSetpoint,
40
+ autoAtSetpoint: !this.disableAutoAtThrustSetpoint,
41
+ autoSetpointDeadband: this.autoAtThrustSetpointDeadband,
42
+ touching: this.thrustTouching,
43
+ });
44
+ const cThrust = thrusterColors(
45
+ {
46
+ atSetpoint: thrustAtSetpoint,
47
+ touching: this.thrustTouching,
48
+ },
49
+ this.state
50
+ );
51
+ const speedAtSetpoint = atSetpoint(this.speed, this.speedSetpoint, {
52
+ atSetpoint: this.atSpeedSetpoint,
53
+ autoAtSetpoint: !this.disableAutoAtSpeedSetpoint,
54
+ autoSetpointDeadband: this.autoAtSpeedSetpointDeadband,
55
+ touching: this.speedTouching,
56
+ });
57
+ const cSpeed = thrusterColors(
58
+ {
59
+ atSetpoint: speedAtSetpoint,
60
+ touching: this.speedTouching,
61
+ },
62
+ this.state
63
+ );
64
+ const container = svg`<rect x="-80" y="-176" width="160" height="352" fill="var(--instrument-frame-primary-color)" stroke="var(--instrument-frame-tertiary-color)" rx="8"/>`;
65
+ const border = svg`<rect x="-80" y="-176" width="160" height="352" fill="none" stroke="var(--instrument-frame-tertiary-color)" rx="8" vector-effect="non-scaling-stroke"/>`;
66
+ const frameLeft = svg`<rect x="-56" y="-176" width="48" height="352" fill="var(--instrument-frame-secondary-color)" vector-effect="non-scaling-stroke" stroke="var(--instrument-frame-secondary-color)"/>`;
67
+ const frameRight = svg`<rect x="8" y="-176" width="48" height="352" fill="var(--instrument-frame-secondary-color)" vector-effect="non-scaling-stroke" stroke="var(--instrument-frame-secondary-color)"/>`;
68
+ const thrustCenter = svg`<rect x="8" y="-2" height="4" width="72" fill="${cThrust.zeroLineColor}" stroke=${cThrust.zeroLineColor} vector-effect="non-scaling-stroke"/>`;
69
+ const {topAdvices: topThrustAdvice, bottomAdvices: bottomThrustAdvice} =
70
+ convertThrustAdvices(this.thrustAdvices, this.thrust);
71
+ const thrustTop = svg`<g transform="translate(44, 0)">
72
+ ${thrusterTopSingleSided(
73
+ 174,
74
+ Math.max(this.thrust, 0),
75
+ {box: cThrust.boxColor, container: ''},
76
+ {
77
+ hideContainer: true,
78
+ hideTicks: cThrust.hideTicks,
79
+ flipAdicePattern: false,
80
+ narrow: false,
81
+ },
82
+ topThrustAdvice
83
+ )}</g>`;
84
+ const thrusterBottom = svg`<g transform="rotate(180) scale(-1,1) translate(44)">
85
+ ${thrusterTopSingleSided(
86
+ 174,
87
+ Math.max(-this.thrust, 0),
88
+ {box: cThrust.boxColor, container: ''},
89
+ {
90
+ hideContainer: true,
91
+ hideTicks: cThrust.hideTicks,
92
+ flipAdicePattern: false,
93
+ narrow: false,
94
+ },
95
+ bottomThrustAdvice
96
+ )}</g>`;
97
+ const thrustSetpoint =
98
+ this.thrustSetpoint !== undefined
99
+ ? svg`<g transform="translate(44, 0)">${setpointSvg(
100
+ 174,
101
+ this.thrustSetpoint,
102
+ thrustSetpointAtZero,
103
+ {
104
+ fill: cThrust.setPointColor,
105
+ stroke: 'var(--border-silhouette-color)',
106
+ },
107
+ {
108
+ inCommand: this.state === InstrumentState.inCommand,
109
+ singleSided: true,
110
+ narrow: false,
111
+ }
112
+ )}</g>`
113
+ : nothing;
114
+
115
+ const speedHeight = 352 * (this.speed / 100) + 2;
116
+ const speedY = 176 - speedHeight;
117
+ const speedBoxColor =
118
+ this.state === InstrumentState.inCommand
119
+ ? 'var(--instrument-enhanced-tertiary-color)'
120
+ : 'var(--instrument-regular-tertiary-color)';
121
+ const speedBox = svg`<rect x="-56" y=${speedY} width="48" height=${speedHeight} fill=${speedBoxColor} stroke=${speedBoxColor} vector-effect="non-scaling-stroke">`;
122
+ const speedLine = svg`<rect x="-56" y=${speedY - 2} width="48" height="4" rx="2" fill=${cSpeed.boxColor} stroke=${cSpeed.boxColor}/>
123
+ `;
124
+ const speedSetpoint =
125
+ this.speedSetpoint !== undefined
126
+ ? svg`<g transform="scale(-1 1) translate(44, 176)">${setpointSvg(
127
+ 350,
128
+ this.speedSetpoint,
129
+ speedSetpointAtZero,
130
+ {
131
+ fill: cSpeed.setPointColor,
132
+ stroke: 'var(--border-silhouette-color)',
133
+ },
134
+ {
135
+ inCommand: this.state === InstrumentState.inCommand,
136
+ singleSided: true,
137
+ narrow: false,
138
+ }
139
+ )}</g>`
140
+ : nothing;
141
+
142
+ return html`
143
+ <div class="container">
144
+ <svg viewbox="-100 -200 200 400">
145
+ ${container} ${frameLeft} ${frameRight} ${thrustCenter} ${thrustTop}
146
+ ${thrusterBottom} ${speedBox} ${speedLine} ${border} ${thrustSetpoint}
147
+ ${speedSetpoint}
148
+ </svg>
149
+ </div>
150
+ `;
151
+ }
152
+
153
+ static override styles = unsafeCSS(compentStyle);
154
+ }
155
+
156
+ declare global {
157
+ interface HTMLElementTagNameMap {
158
+ 'obc-main-engine': ObcMainEngine;
159
+ }
160
+ }
@@ -12,6 +12,7 @@ const meta: Meta<typeof ObcThruster> = {
12
12
  component: 'obc-thruster',
13
13
  args: {width: 320},
14
14
  argTypes: {
15
+ width: {control: {type: 'range', min: 32, max: 1028, step: 1}},
15
16
  thrust: {control: {type: 'range', min: -100, max: 100, step: 1}},
16
17
  setpoint: {control: {type: 'range', min: -100, max: 100, step: 1}},
17
18
  state: {
@@ -1,4 +1,4 @@
1
- import {LitElement, svg, html, css} from 'lit';
1
+ import {LitElement, svg, html, css, nothing} from 'lit';
2
2
  import {customElement, property} from 'lit/decorators.js';
3
3
  import {InstrumentState} from '../types';
4
4
  import {LinearAdvice, LinearAdviceRaw, renderAdvice} from './advice';
@@ -46,6 +46,7 @@ export class ObcThruster extends LitElement {
46
46
  singleDirectionHalfSize: this.singleDirectionHalfSize,
47
47
  topPropeller: this.topPropeller,
48
48
  bottomPropeller: this.bottomPropeller,
49
+ narrow: !this.tunnel,
49
50
  })}
50
51
  </div>`;
51
52
  }
@@ -63,11 +64,11 @@ export class ObcThruster extends LitElement {
63
64
  `;
64
65
  }
65
66
 
66
- function thrusterTop(
67
+ export function thrusterTop(
67
68
  height: number,
68
69
  value: number,
69
70
  colors: {box: string; container: string},
70
- hideTicks: boolean
71
+ options: {hideTicks: boolean; hideContainer: boolean}
71
72
  ) {
72
73
  const container = svg`
73
74
  <path transform="translate(0 -2)" d="M -44 0 v -${height - 8} a 8 8 0 0 1 8 -8 h 72 a 8 8 0 0 1 8 8 V 0 Z" fill=${colors.container} stroke="var(--instrument-frame-tertiary-color)" vector-effect="non-scaling-stroke"/>
@@ -78,7 +79,7 @@ function thrusterTop(
78
79
 
79
80
  const nTicks = 2;
80
81
  const delta = height / nTicks;
81
- if (!hideTicks) {
82
+ if (!options.hideTicks) {
82
83
  for (let i = 1; i < nTicks; i++) {
83
84
  tickmarks.push(
84
85
  svg`<line x1="-24" x2="-44" y1=${-i * delta - 2} y2=${
@@ -96,54 +97,86 @@ function thrusterTop(
96
97
  const barHeight = (height * value) / 100;
97
98
  const barY = -2 - barHeight;
98
99
  const bar = svg`<rect width="40" height=${barHeight} x="-20" y=${barY} fill=${colors.box} stroke=${colors.box} vector-effect="non-scaling-stroke"/>`;
99
-
100
- return [container, track, tickmarks, bar];
100
+ if (options.hideContainer) {
101
+ return [track, tickmarks, bar];
102
+ } else {
103
+ return [container, track, tickmarks, bar];
104
+ }
101
105
  }
102
106
 
103
- function thrusterTopSingleSided(
107
+ export function thrusterTopSingleSided(
104
108
  height: number,
105
109
  value: number,
106
110
  colors: {box: string; container: string},
107
- hideTicks: boolean,
108
- advice: LinearAdviceRaw[],
109
- flipAdicePattern: boolean = false
111
+ options: {
112
+ hideTicks: boolean;
113
+ flipAdicePattern: boolean;
114
+ hideContainer: boolean;
115
+ narrow: boolean;
116
+ },
117
+ advice: LinearAdviceRaw[]
110
118
  ) {
111
- const container = svg`
119
+ const container = options.narrow
120
+ ? svg`
112
121
  <path transform="translate(0 -2)" d="M -32 0 v -${height - 8} a 8 8 0 0 1 8 -8 h 48 a 8 8 0 0 1 8 8 V 0 Z" fill=${colors.container} stroke="var(--instrument-frame-tertiary-color)" vector-effect="non-scaling-stroke"/>
122
+ `
123
+ : svg`
124
+ <path transform="translate(0 -2)" d="M -40 0 v -${height - 8} a 8 8 0 0 1 8 -8 h 56 a 8 8 0 0 1 8 8 V 0 Z" fill=${colors.container} stroke="var(--instrument-frame-tertiary-color)" vector-effect="non-scaling-stroke"/>
113
125
  `;
114
- const track = svg`
126
+ const track = options.narrow
127
+ ? svg`
115
128
  <path transform="translate(0 -2)" d="M -32 0 v -${height - 8} a 8 8 0 0 1 8 -8 h 32 V 0 Z" fill="var(--instrument-frame-secondary-color)" stroke="var(--instrument-frame-tertiary-color)" vector-effect="non-scaling-stroke"/>
129
+ `
130
+ : svg`
131
+ <path transform="translate(0 -2)" d="M -40 0 v -${height - 8} a 8 8 0 0 1 8 -8 h 40 V 0 Z" fill="var(--instrument-frame-secondary-color)" stroke="var(--instrument-frame-tertiary-color)" vector-effect="non-scaling-stroke"/>
116
132
  `;
117
133
 
118
- const tickmarks = hideTicks
134
+ const tickmarks = options.hideTicks
119
135
  ? []
120
136
  : [singleSidedTickmark(height, 50, TickmarkStyle.hinted)];
121
137
 
122
138
  const barHeight = (height * value) / 100;
139
+ const barWidth = options.narrow ? 40 : 48;
140
+ const barX = options.narrow ? -32 : -40;
123
141
  const barY = -2 - barHeight;
124
- const maskId = flipAdicePattern ? 'thrusterBarMask1' : 'thrusterBarMask2';
125
- const bar = svg`
142
+ const maskId = options.flipAdicePattern
143
+ ? 'thrusterBarMask1'
144
+ : 'thrusterBarMask2';
145
+ // The mask is used to clip the bar to the container shape
146
+ const mask = options.hideContainer
147
+ ? nothing
148
+ : svg`
126
149
  <defs>
127
150
  <mask id=${maskId}>
128
- <path transform="translate(0 -2)" d="M -32 0 v -${height - 8} a 8 8 0 0 1 8 -8 h 48 a 8 8 0 0 1 8 8 V 0 Z" fill="white" stroke="white" vector-effect="non-scaling-stroke"/>
129
- </defs>
130
- <rect mask="url(#${maskId})" width="40" height=${barHeight} x="-32" y=${barY} fill=${colors.box} stroke=${colors.box} vector-effect="non-scaling-stroke"/>`;
151
+ <path transform="translate(0 -2)" d="M ${barX} 0 v -${height - 8} a 8 8 0 0 1 8 -8 h ${barWidth} V 0 Z" fill="white" stroke="white" vector-effect="non-scaling-stroke"/>
152
+ </defs>`;
153
+ const maskAttr = options.hideContainer ? '' : `mask="url(#${maskId})"`;
154
+ const bar = svg`
155
+ ${mask}
156
+ <rect ${maskAttr} width=${barWidth} height=${barHeight} x=${barX} y=${barY} fill=${colors.box} stroke=${colors.box} vector-effect="non-scaling-stroke"/>`;
131
157
  const advicesSvg = advice.map((a) =>
132
- renderAdvice(height, a, flipAdicePattern)
158
+ renderAdvice(height, a, options.flipAdicePattern)
133
159
  );
134
-
135
- return [container, track, tickmarks, bar, advicesSvg];
160
+ const all = [tickmarks, bar, advicesSvg];
161
+ if (!options.hideContainer) {
162
+ all.splice(0, 0, [container, track]);
163
+ }
164
+ if (!options.narrow) {
165
+ return svg`<g transform="translate(4 0)">${all}</g>`;
166
+ } else {
167
+ return all;
168
+ }
136
169
  }
137
170
 
138
- function thrusterBottom(
171
+ export function thrusterBottom(
139
172
  height: number,
140
173
  value: number,
141
174
  colors: {box: string; container: string},
142
- hideTicks: boolean
175
+ options: {hideTicks: boolean; hideContainer: boolean}
143
176
  ) {
144
177
  const container = svg`
145
178
  <g transform="rotate(180)">
146
- ${thrusterTop(height, value, colors, hideTicks)}
179
+ ${thrusterTop(height, value, colors, options)}
147
180
  </g>
148
181
  `;
149
182
  return container;
@@ -153,18 +186,23 @@ function thrusterBottomSingleSided(
153
186
  height: number,
154
187
  value: number,
155
188
  colors: {box: string; container: string},
156
- hideTicks: boolean,
157
- advices: LinearAdviceRaw[]
189
+ options: {
190
+ hideTicks: boolean;
191
+ flipAdicePattern: boolean;
192
+ hideContainer: boolean;
193
+ narrow: boolean;
194
+ },
195
+ advice: LinearAdviceRaw[]
158
196
  ) {
159
197
  const container = svg`
160
198
  <g transform="rotate(180) scale(-1,1)">
161
- ${thrusterTopSingleSided(height, value, colors, hideTicks, advices, true)}
199
+ ${thrusterTopSingleSided(height, value, colors, {hideTicks: options.hideTicks, flipAdicePattern: options.flipAdicePattern, hideContainer: options.hideContainer, narrow: options.narrow}, advice)}
162
200
  </g>
163
201
  `;
164
202
  return container;
165
203
  }
166
204
 
167
- function setpointSvg(
205
+ export function setpointSvg(
168
206
  height: number,
169
207
  value: number,
170
208
  setpointAtZero: boolean,
@@ -172,12 +210,13 @@ function setpointSvg(
172
210
  options: {
173
211
  inCommand: boolean;
174
212
  singleSided: boolean;
213
+ narrow: boolean;
175
214
  }
176
215
  ) {
177
216
  const y = -(setpointAtZero
178
217
  ? 0
179
218
  : Math.sign(value) * ((height * Math.abs(value)) / 100 + 2));
180
- const extra = options.singleSided ? -12 : 0;
219
+ const extra = (options.singleSided ? -12 : 0) + (options.narrow ? 0 : 4);
181
220
  let path;
182
221
  if (options.inCommand) {
183
222
  path =
@@ -211,6 +250,27 @@ function setpointSvg(
211
250
  `;
212
251
  }
213
252
 
253
+ export function atSetpoint(
254
+ thrust: number,
255
+ setpoint: number | undefined,
256
+ options: {
257
+ autoAtSetpoint: boolean;
258
+ autoSetpointDeadband: number;
259
+ touching: boolean;
260
+ atSetpoint: boolean;
261
+ }
262
+ ): boolean {
263
+ if (options.touching) {
264
+ return false;
265
+ }
266
+
267
+ if (options.autoAtSetpoint && setpoint !== undefined) {
268
+ return Math.abs(thrust - setpoint) < options.autoSetpointDeadband;
269
+ }
270
+
271
+ return options.atSetpoint;
272
+ }
273
+
214
274
  export function thruster(
215
275
  thrust: number,
216
276
  setpoint: number | undefined,
@@ -228,92 +288,38 @@ export function thruster(
228
288
  advices: LinearAdvice[];
229
289
  topPropeller: PropellerType;
230
290
  bottomPropeller: PropellerType;
291
+ narrow: boolean;
231
292
  }
232
293
  ) {
233
- if (!options.singleSided && options.advices.length > 0) {
234
- throw new Error('Double sided thruster does not support advice');
294
+ if (options.tunnel) {
295
+ thrust = -thrust;
296
+ setpoint = setpoint === undefined ? undefined : -setpoint;
235
297
  }
236
298
 
237
- if (options.autoAtSetpoint && setpoint !== undefined) {
238
- options.atSetpoint =
239
- Math.abs(thrust - setpoint) < options.autoSetpointDeadband;
299
+ if (!options.singleSided && options.advices.length > 0) {
300
+ throw new Error('Double sided thruster does not support advice');
240
301
  }
241
302
 
242
- if (options.touching) {
243
- options.atSetpoint = false;
244
- }
303
+ options.atSetpoint = atSetpoint(thrust, setpoint, options);
245
304
 
246
- let boxColor = 'var(--instrument-enhanced-secondary-color)';
247
- let setPointColor = 'var(--instrument-enhanced-primary-color)';
248
- let arrowColor = 'var(--instrument-regular-secondary-color)';
249
- let containerBackgroundColor = 'var(--instrument-frame-primary-color)';
250
- let zeroLineColor = 'var(--instrument-enhanced-secondary-color)';
251
- let hideTicks = false;
252
- if (options.atSetpoint) {
253
- setPointColor = boxColor;
254
- }
255
- if (state === InstrumentState.active) {
256
- boxColor = 'var(--instrument-regular-secondary-color)';
257
- zeroLineColor = 'var(--instrument-regular-secondary-color)';
258
- setPointColor = 'var(--instrument-regular-primary-color)';
259
- arrowColor = 'var(--instrument-regular-secondary-color)';
260
- if (options.atSetpoint) {
261
- setPointColor = boxColor;
262
- }
263
- } else if (state === InstrumentState.loading) {
264
- boxColor = 'transparent';
265
- setPointColor = 'var(--instrument-frame-tertiary-color)';
266
- zeroLineColor = 'var(--instrument-frame-tertiary-color)';
267
- arrowColor = 'var(--instrument-regular-secondary-color)';
268
- thrust = 0;
269
- hideTicks = true;
270
- if (setpoint !== undefined) {
271
- setpoint = 0;
272
- }
273
- } else if (state === InstrumentState.off) {
274
- boxColor = 'transparent';
275
- setPointColor = 'var(--instrument-frame-tertiary-color)';
276
- arrowColor = 'var(--instrument-frame-tertiary-color)';
277
- zeroLineColor = 'var(--instrument-frame-tertiary-color)';
278
- thrust = 0;
279
- hideTicks = true;
280
- containerBackgroundColor = 'transparent';
281
- if (setpoint !== undefined) {
282
- setpoint = 0;
283
- }
284
- }
305
+ const tc = thrusterColors(options, state);
285
306
 
286
- const centerLine = options.singleSided
287
- ? svg`<rect x="-32" y="-2" width="64" height="4" stroke-width="1" fill=${zeroLineColor} stroke=${zeroLineColor} vector-effect="non-scaling-stroke"/>`
288
- : svg`
289
- <rect x="-44" y="-2" width="88" height="4" stroke-width="1" fill=${zeroLineColor} stroke=${zeroLineColor} vector-effect="non-scaling-stroke"/>
307
+ let centerLine = svg`
308
+ <rect x="-44" y="-2" width="88" height="4" stroke-width="1" fill=${tc.zeroLineColor} stroke=${tc.zeroLineColor} vector-effect="non-scaling-stroke"/>
290
309
  `;
310
+ if (options.singleSided) {
311
+ const width = options.narrow ? 64 : 72;
312
+ const x = options.narrow ? -32 : -36;
313
+ centerLine = svg`<rect x=${x} y="-2" width=${width} height="4" stroke-width="1" fill=${tc.zeroLineColor} stroke=${tc.zeroLineColor} vector-effect="non-scaling-stroke"/>`;
314
+ }
291
315
 
292
316
  const setpointAtZero =
293
317
  Math.abs(setpoint || 0) < options.setpointAtZeroDeadband;
294
318
 
295
- const advices: LinearAdviceRaw[] = options.advices.map((a) => {
296
- const triggered = thrust >= a.min && thrust <= a.max;
297
- let state: AdviceState;
298
- if (triggered) {
299
- state = AdviceState.triggered;
300
- } else if (a.hinted) {
301
- state = AdviceState.hinted;
302
- } else {
303
- state = AdviceState.regular;
304
- }
305
- return {
306
- min: a.min,
307
- max: a.max,
308
- type: a.type,
309
- state,
310
- };
311
- });
312
-
313
- const topAdvices = advices.filter((a) => a.min >= 0);
314
- const bottomAdvices = advices
315
- .filter((a) => a.max <= 0)
316
- .map((a) => ({...a, min: -a.max, max: -a.min}));
319
+ const {topAdvices, bottomAdvices} = convertThrustAdvices(
320
+ options.advices,
321
+ thrust
322
+ );
317
323
 
318
324
  const thrusterSvg = [];
319
325
  const baseheight = options.topPropeller === PropellerType.none ? 134 : 106;
@@ -323,8 +329,13 @@ export function thruster(
323
329
  thrusterTopSingleSided(
324
330
  height,
325
331
  Math.max(thrust, 0),
326
- {box: boxColor, container: containerBackgroundColor},
327
- hideTicks,
332
+ {box: tc.boxColor, container: tc.containerBackgroundColor},
333
+ {
334
+ hideTicks: tc.hideTicks,
335
+ flipAdicePattern: false,
336
+ hideContainer: false,
337
+ narrow: options.narrow,
338
+ },
328
339
  topAdvices
329
340
  )
330
341
  );
@@ -333,8 +344,13 @@ export function thruster(
333
344
  thrusterBottomSingleSided(
334
345
  height,
335
346
  Math.max(-thrust, 0),
336
- {box: boxColor, container: containerBackgroundColor},
337
- hideTicks,
347
+ {box: tc.boxColor, container: tc.containerBackgroundColor},
348
+ {
349
+ hideTicks: tc.hideTicks,
350
+ flipAdicePattern: true,
351
+ hideContainer: false,
352
+ narrow: options.narrow,
353
+ },
338
354
  bottomAdvices
339
355
  )
340
356
  );
@@ -345,8 +361,8 @@ export function thruster(
345
361
  thrusterTop(
346
362
  height,
347
363
  Math.max(thrust, 0),
348
- {box: boxColor, container: containerBackgroundColor},
349
- hideTicks
364
+ {box: tc.boxColor, container: tc.containerBackgroundColor},
365
+ {hideTicks: tc.hideTicks, hideContainer: false}
350
366
  )
351
367
  );
352
368
  if (!options.singleDirection) {
@@ -354,8 +370,8 @@ export function thruster(
354
370
  thrusterBottom(
355
371
  height,
356
372
  Math.max(-thrust, 0),
357
- {box: boxColor, container: containerBackgroundColor},
358
- hideTicks
373
+ {box: tc.boxColor, container: tc.containerBackgroundColor},
374
+ {hideTicks: tc.hideTicks, hideContainer: false}
359
375
  )
360
376
  );
361
377
  }
@@ -368,12 +384,13 @@ export function thruster(
368
384
  setpoint,
369
385
  setpointAtZero,
370
386
  {
371
- fill: setPointColor,
387
+ fill: tc.setPointColor,
372
388
  stroke: 'var(--border-silhouette-color)',
373
389
  },
374
390
  {
375
391
  inCommand: state === InstrumentState.inCommand,
376
392
  singleSided: options.singleSided,
393
+ narrow: options.narrow,
377
394
  }
378
395
  )
379
396
  );
@@ -382,7 +399,7 @@ export function thruster(
382
399
  if (options.tunnel) {
383
400
  return svg`
384
401
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="-160 -64 320 128" x="-160" y="-64">
385
- <g transform="rotate(90)">
402
+ <g transform="rotate(-90)">
386
403
  ${thrusterSvg}
387
404
  </g>
388
405
  </svg>`;
@@ -393,7 +410,7 @@ export function thruster(
393
410
  viewBox = '-80 -300 160 320';
394
411
  y = -320;
395
412
  }
396
- const top = topPropeller(height, arrowColor, options.topPropeller);
413
+ const top = topPropeller(height, tc.arrowColor, options.topPropeller);
397
414
  const bottom = bottomPropeller(
398
415
  options.singleDirectionHalfSize ? 0.5 : height,
399
416
  options.bottomPropeller
@@ -413,3 +430,78 @@ declare global {
413
430
  'obc-thruster': ObcThruster;
414
431
  }
415
432
  }
433
+
434
+ export function convertThrustAdvices(
435
+ advices: LinearAdvice[],
436
+ thrust: number
437
+ ): {topAdvices: LinearAdviceRaw[]; bottomAdvices: LinearAdviceRaw[]} {
438
+ const rawAdvices: LinearAdviceRaw[] = advices.map((a) => {
439
+ const triggered = thrust >= a.min && thrust <= a.max;
440
+ let state: AdviceState;
441
+ if (triggered) {
442
+ state = AdviceState.triggered;
443
+ } else if (a.hinted) {
444
+ state = AdviceState.hinted;
445
+ } else {
446
+ state = AdviceState.regular;
447
+ }
448
+ return {
449
+ min: a.min,
450
+ max: a.max,
451
+ type: a.type,
452
+ state,
453
+ hinted: a.hinted,
454
+ };
455
+ });
456
+
457
+ const topAdvices = rawAdvices.filter((a) => a.min >= 0);
458
+ const bottomAdvices = rawAdvices
459
+ .filter((a) => a.max <= 0)
460
+ .map((a) => ({...a, min: -a.max, max: -a.min}));
461
+ return {topAdvices, bottomAdvices};
462
+ }
463
+
464
+ export function thrusterColors(
465
+ options: {atSetpoint: boolean; touching: boolean},
466
+ state: InstrumentState
467
+ ) {
468
+ let boxColor = 'var(--instrument-enhanced-secondary-color)';
469
+ let setPointColor = 'var(--instrument-enhanced-primary-color)';
470
+ let arrowColor = 'var(--instrument-regular-secondary-color)';
471
+ let containerBackgroundColor = 'var(--instrument-frame-primary-color)';
472
+ let zeroLineColor = 'var(--instrument-enhanced-secondary-color)';
473
+ let hideTicks = false;
474
+ if (options.atSetpoint) {
475
+ setPointColor = boxColor;
476
+ }
477
+ if (state === InstrumentState.active) {
478
+ boxColor = 'var(--instrument-regular-secondary-color)';
479
+ zeroLineColor = 'var(--instrument-regular-secondary-color)';
480
+ setPointColor = 'var(--instrument-regular-primary-color)';
481
+ arrowColor = 'var(--instrument-regular-secondary-color)';
482
+ if (options.atSetpoint) {
483
+ setPointColor = boxColor;
484
+ }
485
+ } else if (state === InstrumentState.loading) {
486
+ boxColor = 'transparent';
487
+ setPointColor = 'var(--instrument-frame-tertiary-color)';
488
+ zeroLineColor = 'var(--instrument-frame-tertiary-color)';
489
+ arrowColor = 'var(--instrument-regular-secondary-color)';
490
+ hideTicks = true;
491
+ } else if (state === InstrumentState.off) {
492
+ boxColor = 'transparent';
493
+ setPointColor = 'var(--instrument-frame-tertiary-color)';
494
+ arrowColor = 'var(--instrument-frame-tertiary-color)';
495
+ zeroLineColor = 'var(--instrument-frame-tertiary-color)';
496
+ hideTicks = true;
497
+ containerBackgroundColor = 'transparent';
498
+ }
499
+ return {
500
+ zeroLineColor,
501
+ boxColor,
502
+ containerBackgroundColor,
503
+ hideTicks,
504
+ setPointColor,
505
+ arrowColor,
506
+ };
507
+ }