tgui-core 1.0.0

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 (152) hide show
  1. package/.editorconfig +10 -0
  2. package/.eslintrc.cjs +78 -0
  3. package/.gitattributes +4 -0
  4. package/.prettierrc.yml +1 -0
  5. package/.vscode/extensions.json +6 -0
  6. package/.vscode/settings.json +5 -0
  7. package/README.md +1 -0
  8. package/global.d.ts +173 -0
  9. package/package.json +25 -0
  10. package/src/assets.ts +43 -0
  11. package/src/backend.ts +369 -0
  12. package/src/common/collections.ts +349 -0
  13. package/src/common/color.ts +94 -0
  14. package/src/common/events.ts +45 -0
  15. package/src/common/exhaustive.ts +19 -0
  16. package/src/common/fp.ts +38 -0
  17. package/src/common/keycodes.ts +86 -0
  18. package/src/common/keys.ts +39 -0
  19. package/src/common/math.ts +98 -0
  20. package/src/common/perf.ts +72 -0
  21. package/src/common/random.ts +32 -0
  22. package/src/common/react.ts +65 -0
  23. package/src/common/redux.ts +196 -0
  24. package/src/common/storage.js +196 -0
  25. package/src/common/string.ts +173 -0
  26. package/src/common/timer.ts +68 -0
  27. package/src/common/type-utils.ts +41 -0
  28. package/src/common/types.ts +9 -0
  29. package/src/common/uuid.ts +24 -0
  30. package/src/common/vector.ts +51 -0
  31. package/src/components/AnimatedNumber.tsx +185 -0
  32. package/src/components/Autofocus.tsx +23 -0
  33. package/src/components/Blink.jsx +69 -0
  34. package/src/components/BlockQuote.tsx +15 -0
  35. package/src/components/BodyZoneSelector.tsx +149 -0
  36. package/src/components/Box.tsx +255 -0
  37. package/src/components/Button.tsx +415 -0
  38. package/src/components/ByondUi.jsx +121 -0
  39. package/src/components/Chart.tsx +160 -0
  40. package/src/components/Collapsible.tsx +45 -0
  41. package/src/components/ColorBox.tsx +30 -0
  42. package/src/components/Dialog.tsx +85 -0
  43. package/src/components/Dimmer.tsx +19 -0
  44. package/src/components/Divider.tsx +26 -0
  45. package/src/components/DmIcon.tsx +72 -0
  46. package/src/components/DraggableControl.jsx +282 -0
  47. package/src/components/Dropdown.tsx +246 -0
  48. package/src/components/FakeTerminal.jsx +52 -0
  49. package/src/components/FitText.tsx +99 -0
  50. package/src/components/Flex.tsx +105 -0
  51. package/src/components/Grid.tsx +44 -0
  52. package/src/components/Icon.tsx +91 -0
  53. package/src/components/Image.tsx +63 -0
  54. package/src/components/InfinitePlane.jsx +192 -0
  55. package/src/components/Input.tsx +181 -0
  56. package/src/components/KeyListener.tsx +40 -0
  57. package/src/components/Knob.tsx +185 -0
  58. package/src/components/LabeledControls.tsx +50 -0
  59. package/src/components/LabeledList.tsx +130 -0
  60. package/src/components/MenuBar.tsx +238 -0
  61. package/src/components/Modal.tsx +25 -0
  62. package/src/components/NoticeBox.tsx +48 -0
  63. package/src/components/NumberInput.tsx +328 -0
  64. package/src/components/Popper.tsx +100 -0
  65. package/src/components/ProgressBar.tsx +79 -0
  66. package/src/components/RestrictedInput.jsx +301 -0
  67. package/src/components/RoundGauge.tsx +189 -0
  68. package/src/components/Section.tsx +125 -0
  69. package/src/components/Slider.tsx +173 -0
  70. package/src/components/Stack.tsx +101 -0
  71. package/src/components/StyleableSection.tsx +30 -0
  72. package/src/components/Table.tsx +90 -0
  73. package/src/components/Tabs.tsx +90 -0
  74. package/src/components/TextArea.tsx +198 -0
  75. package/src/components/TimeDisplay.jsx +64 -0
  76. package/src/components/Tooltip.tsx +147 -0
  77. package/src/components/TrackOutsideClicks.tsx +35 -0
  78. package/src/components/VirtualList.tsx +69 -0
  79. package/src/constants.ts +355 -0
  80. package/src/debug/KitchenSink.jsx +56 -0
  81. package/src/debug/actions.js +11 -0
  82. package/src/debug/hooks.js +10 -0
  83. package/src/debug/index.ts +10 -0
  84. package/src/debug/middleware.js +86 -0
  85. package/src/debug/reducer.js +22 -0
  86. package/src/debug/selectors.js +7 -0
  87. package/src/drag.ts +280 -0
  88. package/src/events.ts +237 -0
  89. package/src/focus.ts +25 -0
  90. package/src/format.ts +173 -0
  91. package/src/hotkeys.ts +212 -0
  92. package/src/http.ts +16 -0
  93. package/src/layouts/Layout.tsx +75 -0
  94. package/src/layouts/NtosWindow.tsx +162 -0
  95. package/src/layouts/Pane.tsx +56 -0
  96. package/src/layouts/Window.tsx +227 -0
  97. package/src/renderer.ts +50 -0
  98. package/styles/base.scss +32 -0
  99. package/styles/colors.scss +92 -0
  100. package/styles/components/BlockQuote.scss +20 -0
  101. package/styles/components/Button.scss +175 -0
  102. package/styles/components/ColorBox.scss +12 -0
  103. package/styles/components/Dialog.scss +105 -0
  104. package/styles/components/Dimmer.scss +22 -0
  105. package/styles/components/Divider.scss +27 -0
  106. package/styles/components/Dropdown.scss +72 -0
  107. package/styles/components/Flex.scss +31 -0
  108. package/styles/components/Icon.scss +25 -0
  109. package/styles/components/Input.scss +68 -0
  110. package/styles/components/Knob.scss +131 -0
  111. package/styles/components/LabeledList.scss +49 -0
  112. package/styles/components/MenuBar.scss +75 -0
  113. package/styles/components/Modal.scss +14 -0
  114. package/styles/components/NoticeBox.scss +65 -0
  115. package/styles/components/NumberInput.scss +76 -0
  116. package/styles/components/ProgressBar.scss +63 -0
  117. package/styles/components/RoundGauge.scss +88 -0
  118. package/styles/components/Section.scss +143 -0
  119. package/styles/components/Slider.scss +54 -0
  120. package/styles/components/Stack.scss +59 -0
  121. package/styles/components/Table.scss +44 -0
  122. package/styles/components/Tabs.scss +144 -0
  123. package/styles/components/TextArea.scss +84 -0
  124. package/styles/components/Tooltip.scss +24 -0
  125. package/styles/functions.scss +79 -0
  126. package/styles/layouts/Layout.scss +57 -0
  127. package/styles/layouts/NtosHeader.scss +20 -0
  128. package/styles/layouts/NtosWindow.scss +26 -0
  129. package/styles/layouts/TitleBar.scss +111 -0
  130. package/styles/layouts/Window.scss +103 -0
  131. package/styles/main.scss +97 -0
  132. package/styles/reset.scss +68 -0
  133. package/styles/themes/abductor.scss +68 -0
  134. package/styles/themes/admin.scss +38 -0
  135. package/styles/themes/cardtable.scss +57 -0
  136. package/styles/themes/hackerman.scss +70 -0
  137. package/styles/themes/malfunction.scss +67 -0
  138. package/styles/themes/neutral.scss +50 -0
  139. package/styles/themes/ntOS95.scss +166 -0
  140. package/styles/themes/ntos.scss +44 -0
  141. package/styles/themes/ntos_cat.scss +148 -0
  142. package/styles/themes/ntos_darkmode.scss +44 -0
  143. package/styles/themes/ntos_lightmode.scss +67 -0
  144. package/styles/themes/ntos_spooky.scss +69 -0
  145. package/styles/themes/ntos_synth.scss +99 -0
  146. package/styles/themes/ntos_terminal.scss +112 -0
  147. package/styles/themes/paper.scss +184 -0
  148. package/styles/themes/retro.scss +72 -0
  149. package/styles/themes/spookyconsole.scss +73 -0
  150. package/styles/themes/syndicate.scss +67 -0
  151. package/styles/themes/wizard.scss +68 -0
  152. package/tsconfig.json +34 -0
@@ -0,0 +1,64 @@
1
+ import { Component } from 'react';
2
+
3
+ import { formatTime } from '../format';
4
+
5
+ // AnimatedNumber Copypaste
6
+ const isSafeNumber = (value) => {
7
+ return (
8
+ typeof value === 'number' && Number.isFinite(value) && !Number.isNaN(value)
9
+ );
10
+ };
11
+
12
+ export class TimeDisplay extends Component {
13
+ constructor(props) {
14
+ super(props);
15
+ this.timer = null;
16
+ this.last_seen_value = undefined;
17
+ this.state = {
18
+ value: 0,
19
+ };
20
+ // Set initial state with value provided in props
21
+ if (isSafeNumber(props.value)) {
22
+ this.state.value = Number(props.value);
23
+ this.last_seen_value = Number(props.value);
24
+ }
25
+ }
26
+
27
+ componentDidUpdate() {
28
+ if (this.props.auto !== undefined) {
29
+ clearInterval(this.timer);
30
+ this.timer = setInterval(() => this.tick(), 1000); // every 1 s
31
+ }
32
+ }
33
+
34
+ tick() {
35
+ let current = Number(this.state.value);
36
+ if (this.props.value !== this.last_seen_value) {
37
+ this.last_seen_value = this.props.value;
38
+ current = this.props.value;
39
+ }
40
+ const mod = this.props.auto === 'up' ? 10 : -10; // Time down by default.
41
+ const value = Math.max(0, current + mod); // one sec tick
42
+ this.setState({ value });
43
+ }
44
+
45
+ componentDidMount() {
46
+ if (this.props.auto !== undefined) {
47
+ this.timer = setInterval(() => this.tick(), 1000); // every 1 s
48
+ }
49
+ }
50
+
51
+ componentWillUnmount() {
52
+ clearInterval(this.timer);
53
+ }
54
+
55
+ render() {
56
+ const val = this.state.value;
57
+ // Directly display weird stuff
58
+ if (!isSafeNumber(val)) {
59
+ return this.state.value || null;
60
+ }
61
+
62
+ return formatTime(val);
63
+ }
64
+ }
@@ -0,0 +1,147 @@
1
+ /* eslint-disable react/no-deprecated */
2
+ // TODO: Rewrite as an FC, remove this lint disable
3
+ import { createPopper, Placement, VirtualElement } from '@popperjs/core';
4
+ import { Component, ReactNode } from 'react';
5
+ import { findDOMNode, render } from 'react-dom';
6
+
7
+ type TooltipProps = {
8
+ children?: ReactNode;
9
+ content: ReactNode;
10
+ position?: Placement;
11
+ };
12
+
13
+ type TooltipState = {
14
+ hovered: boolean;
15
+ };
16
+
17
+ const DEFAULT_OPTIONS = {
18
+ modifiers: [
19
+ {
20
+ name: 'eventListeners',
21
+ enabled: false,
22
+ },
23
+ ],
24
+ };
25
+
26
+ const NULL_RECT: DOMRect = {
27
+ width: 0,
28
+ height: 0,
29
+ top: 0,
30
+ right: 0,
31
+ bottom: 0,
32
+ left: 0,
33
+ x: 0,
34
+ y: 0,
35
+ toJSON: () => null,
36
+ };
37
+
38
+ export class Tooltip extends Component<TooltipProps, TooltipState> {
39
+ // Mounting poppers is really laggy because popper.js is very slow.
40
+ // Thus, instead of using the Popper component, Tooltip creates ONE popper
41
+ // and stores every tooltip inside that.
42
+ // This means you can never have two tooltips at once, for instance.
43
+ static renderedTooltip: HTMLDivElement | undefined;
44
+ static singletonPopper: ReturnType<typeof createPopper> | undefined;
45
+ static currentHoveredElement: Element | undefined;
46
+ static virtualElement: VirtualElement = {
47
+ getBoundingClientRect: () =>
48
+ Tooltip.currentHoveredElement?.getBoundingClientRect() ?? NULL_RECT,
49
+ };
50
+
51
+ getDOMNode() {
52
+ // HACK: We don't want to create a wrapper, as it could break the layout
53
+ // of consumers, so we use findDOMNode.
54
+ // This is usually bad as refs are usually better, but refs did
55
+ // not work in this case, as they weren't propagating correctly.
56
+ // A previous attempt was made as a render prop that passed an ID,
57
+ // but this made consuming use too unwieldly.
58
+ // Because this component is written in TypeScript, we will know
59
+ // immediately if this internal variable is removed.
60
+ //
61
+ // eslint-disable-next-line react/no-find-dom-node
62
+ return findDOMNode(this) as Element;
63
+ }
64
+
65
+ componentDidMount() {
66
+ const domNode = this.getDOMNode();
67
+
68
+ if (!domNode) {
69
+ return;
70
+ }
71
+
72
+ domNode.addEventListener('mouseenter', () => {
73
+ let renderedTooltip = Tooltip.renderedTooltip;
74
+ if (renderedTooltip === undefined) {
75
+ renderedTooltip = document.createElement('div');
76
+ renderedTooltip.className = 'Tooltip';
77
+ document.body.appendChild(renderedTooltip);
78
+ Tooltip.renderedTooltip = renderedTooltip;
79
+ }
80
+
81
+ Tooltip.currentHoveredElement = domNode;
82
+
83
+ renderedTooltip.style.opacity = '1';
84
+
85
+ this.renderPopperContent();
86
+ });
87
+
88
+ domNode.addEventListener('mouseleave', () => {
89
+ this.fadeOut();
90
+ });
91
+ }
92
+
93
+ fadeOut() {
94
+ if (Tooltip.currentHoveredElement !== this.getDOMNode()) {
95
+ return;
96
+ }
97
+
98
+ Tooltip.currentHoveredElement = undefined;
99
+ Tooltip.renderedTooltip!.style.opacity = '0';
100
+ }
101
+
102
+ renderPopperContent() {
103
+ const renderedTooltip = Tooltip.renderedTooltip;
104
+ if (!renderedTooltip) {
105
+ return;
106
+ }
107
+
108
+ render(<span>{this.props.content}</span>, renderedTooltip, () => {
109
+ let singletonPopper = Tooltip.singletonPopper;
110
+ if (singletonPopper === undefined) {
111
+ singletonPopper = createPopper(
112
+ Tooltip.virtualElement,
113
+ renderedTooltip!,
114
+ {
115
+ ...DEFAULT_OPTIONS,
116
+ placement: this.props.position || 'auto',
117
+ }
118
+ );
119
+
120
+ Tooltip.singletonPopper = singletonPopper;
121
+ } else {
122
+ singletonPopper.setOptions({
123
+ ...DEFAULT_OPTIONS,
124
+ placement: this.props.position || 'auto',
125
+ });
126
+
127
+ singletonPopper.update();
128
+ }
129
+ });
130
+ }
131
+
132
+ componentDidUpdate() {
133
+ if (Tooltip.currentHoveredElement !== this.getDOMNode()) {
134
+ return;
135
+ }
136
+
137
+ this.renderPopperContent();
138
+ }
139
+
140
+ componentWillUnmount() {
141
+ this.fadeOut();
142
+ }
143
+
144
+ render() {
145
+ return this.props.children;
146
+ }
147
+ }
@@ -0,0 +1,35 @@
1
+ import { Component, createRef, PropsWithChildren } from 'react';
2
+
3
+ type Props = {
4
+ onOutsideClick: () => void;
5
+ } & PropsWithChildren;
6
+
7
+ export class TrackOutsideClicks extends Component<Props> {
8
+ ref = createRef<HTMLDivElement>();
9
+
10
+ constructor(props) {
11
+ super(props);
12
+
13
+ this.handleOutsideClick = this.handleOutsideClick.bind(this);
14
+
15
+ document.addEventListener('click', this.handleOutsideClick);
16
+ }
17
+
18
+ componentWillUnmount() {
19
+ document.removeEventListener('click', this.handleOutsideClick);
20
+ }
21
+
22
+ handleOutsideClick(event: MouseEvent) {
23
+ if (!(event.target instanceof Node)) {
24
+ return;
25
+ }
26
+
27
+ if (this.ref.current && !this.ref.current.contains(event.target)) {
28
+ this.props.onOutsideClick();
29
+ }
30
+ }
31
+
32
+ render() {
33
+ return <div ref={this.ref}>{this.props.children}</div>;
34
+ }
35
+ }
@@ -0,0 +1,69 @@
1
+ import {
2
+ PropsWithChildren,
3
+ useCallback,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
8
+
9
+ /**
10
+ * A vertical list that renders items to fill space up to the extents of the
11
+ * current window, and then defers rendering of other items until they come
12
+ * into view.
13
+ */
14
+ export const VirtualList = (props: PropsWithChildren) => {
15
+ const { children } = props;
16
+ const containerRef = useRef(null as HTMLDivElement | null);
17
+ const [visibleElements, setVisibleElements] = useState(1);
18
+ const [padding, setPadding] = useState(0);
19
+
20
+ const adjustExtents = useCallback(() => {
21
+ const { current } = containerRef;
22
+
23
+ if (
24
+ !children ||
25
+ !Array.isArray(children) ||
26
+ !current ||
27
+ visibleElements >= children.length
28
+ ) {
29
+ return;
30
+ }
31
+
32
+ const unusedArea =
33
+ document.body.offsetHeight - current.getBoundingClientRect().bottom;
34
+
35
+ const averageItemHeight = Math.ceil(current.offsetHeight / visibleElements);
36
+
37
+ if (unusedArea > 0) {
38
+ const newVisibleElements = Math.min(
39
+ children.length,
40
+ visibleElements +
41
+ Math.max(1, Math.ceil(unusedArea / averageItemHeight)),
42
+ );
43
+
44
+ setVisibleElements(newVisibleElements);
45
+
46
+ setPadding((children.length - newVisibleElements) * averageItemHeight);
47
+ }
48
+ }, [containerRef, visibleElements, children]);
49
+
50
+ useEffect(() => {
51
+ adjustExtents();
52
+
53
+ const interval = setInterval(adjustExtents, 100);
54
+
55
+ return () => clearInterval(interval);
56
+ }, [adjustExtents]);
57
+
58
+ return (
59
+ <div className={'VirtualList'}>
60
+ <div className={'VirtualList__Container'} ref={containerRef}>
61
+ {Array.isArray(children) ? children.slice(0, visibleElements) : null}
62
+ </div>
63
+ <div
64
+ className={'VirtualList__Padding'}
65
+ style={{ paddingBottom: `${padding}px` }}
66
+ />
67
+ </div>
68
+ );
69
+ };
@@ -0,0 +1,355 @@
1
+ /**
2
+ * @file
3
+ * @copyright 2020 Aleksej Komarov
4
+ * @license MIT
5
+ */
6
+
7
+ type Gas = {
8
+ id: string;
9
+ path: string;
10
+ name: string;
11
+ label: string;
12
+ color: string;
13
+ };
14
+
15
+ // UI states, which are mirrored from the BYOND code.
16
+ export const UI_INTERACTIVE = 2;
17
+ export const UI_UPDATE = 1;
18
+ export const UI_DISABLED = 0;
19
+ export const UI_CLOSE = -1;
20
+
21
+ // All game related colors are stored here
22
+ export const COLORS = {
23
+ // Department colors
24
+ department: {
25
+ captain: '#c06616',
26
+ security: '#e74c3c',
27
+ medbay: '#3498db',
28
+ science: '#9b59b6',
29
+ engineering: '#f1c40f',
30
+ cargo: '#f39c12',
31
+ service: '#7cc46a',
32
+ centcom: '#00c100',
33
+ other: '#c38312',
34
+ },
35
+ // Damage type colors
36
+ damageType: {
37
+ oxy: '#3498db',
38
+ toxin: '#2ecc71',
39
+ burn: '#e67e22',
40
+ brute: '#e74c3c',
41
+ },
42
+ // reagent / chemistry related colours
43
+ reagent: {
44
+ acidicbuffer: '#fbc314',
45
+ basicbuffer: '#3853a4',
46
+ },
47
+ } as const;
48
+
49
+ // Colors defined in CSS
50
+ export const CSS_COLORS = [
51
+ 'average',
52
+ 'bad',
53
+ 'black',
54
+ 'blue',
55
+ 'brown',
56
+ 'good',
57
+ 'green',
58
+ 'grey',
59
+ 'label',
60
+ 'olive',
61
+ 'orange',
62
+ 'pink',
63
+ 'purple',
64
+ 'red',
65
+ 'teal',
66
+ 'transparent',
67
+ 'violet',
68
+ 'white',
69
+ 'yellow',
70
+ ] as const;
71
+
72
+ export type CssColor = (typeof CSS_COLORS)[number];
73
+
74
+ /* IF YOU CHANGE THIS KEEP IT IN SYNC WITH CHAT CSS */
75
+ export const RADIO_CHANNELS = [
76
+ {
77
+ name: 'Syndicate',
78
+ freq: 1213,
79
+ color: '#8f4a4b',
80
+ },
81
+ {
82
+ name: 'Red Team',
83
+ freq: 1215,
84
+ color: '#ff4444',
85
+ },
86
+ {
87
+ name: 'Blue Team',
88
+ freq: 1217,
89
+ color: '#3434fd',
90
+ },
91
+ {
92
+ name: 'Green Team',
93
+ freq: 1219,
94
+ color: '#34fd34',
95
+ },
96
+ {
97
+ name: 'Yellow Team',
98
+ freq: 1221,
99
+ color: '#fdfd34',
100
+ },
101
+ {
102
+ name: 'CentCom',
103
+ freq: 1337,
104
+ color: '#2681a5',
105
+ },
106
+ {
107
+ name: 'Supply',
108
+ freq: 1347,
109
+ color: '#b88646',
110
+ },
111
+ {
112
+ name: 'Service',
113
+ freq: 1349,
114
+ color: '#6ca729',
115
+ },
116
+ {
117
+ name: 'Science',
118
+ freq: 1351,
119
+ color: '#c68cfa',
120
+ },
121
+ {
122
+ name: 'Command',
123
+ freq: 1353,
124
+ color: '#fcdf03',
125
+ },
126
+ {
127
+ name: 'Medical',
128
+ freq: 1355,
129
+ color: '#57b8f0',
130
+ },
131
+ {
132
+ name: 'Engineering',
133
+ freq: 1357,
134
+ color: '#f37746',
135
+ },
136
+ {
137
+ name: 'Security',
138
+ freq: 1359,
139
+ color: '#dd3535',
140
+ },
141
+ {
142
+ name: 'AI Private',
143
+ freq: 1447,
144
+ color: '#d65d95',
145
+ },
146
+ {
147
+ name: 'Common',
148
+ freq: 1459,
149
+ color: '#1ecc43',
150
+ },
151
+ ] as const;
152
+
153
+ const GASES = [
154
+ {
155
+ id: 'o2',
156
+ path: '/datum/gas/oxygen',
157
+ name: 'Oxygen',
158
+ label: 'O₂',
159
+ color: 'blue',
160
+ },
161
+ {
162
+ id: 'n2',
163
+ path: '/datum/gas/nitrogen',
164
+ name: 'Nitrogen',
165
+ label: 'N₂',
166
+ color: 'yellow',
167
+ },
168
+ {
169
+ id: 'co2',
170
+ path: '/datum/gas/carbon_dioxide',
171
+ name: 'Carbon Dioxide',
172
+ label: 'CO₂',
173
+ color: 'grey',
174
+ },
175
+ {
176
+ id: 'plasma',
177
+ path: '/datum/gas/plasma',
178
+ name: 'Plasma',
179
+ label: 'Plasma',
180
+ color: 'pink',
181
+ },
182
+ {
183
+ id: 'water_vapor',
184
+ path: '/datum/gas/water_vapor',
185
+ name: 'Water Vapor',
186
+ label: 'H₂O',
187
+ color: 'lightsteelblue',
188
+ },
189
+ {
190
+ id: 'hypernoblium',
191
+ path: '/datum/gas/hypernoblium',
192
+ name: 'Hyper-noblium',
193
+ label: 'Hyper-nob',
194
+ color: 'teal',
195
+ },
196
+ {
197
+ id: 'n2o',
198
+ path: '/datum/gas/nitrous_oxide',
199
+ name: 'Nitrous Oxide',
200
+ label: 'N₂O',
201
+ color: 'bisque',
202
+ },
203
+ {
204
+ id: 'no2',
205
+ path: '/datum/gas/nitrium',
206
+ name: 'Nitrium',
207
+ label: 'Nitrium',
208
+ color: 'brown',
209
+ },
210
+ {
211
+ id: 'tritium',
212
+ path: '/datum/gas/tritium',
213
+ name: 'Tritium',
214
+ label: 'Tritium',
215
+ color: 'limegreen',
216
+ },
217
+ {
218
+ id: 'bz',
219
+ path: '/datum/gas/bz',
220
+ name: 'BZ',
221
+ label: 'BZ',
222
+ color: 'mediumpurple',
223
+ },
224
+ {
225
+ id: 'pluoxium',
226
+ path: '/datum/gas/pluoxium',
227
+ name: 'Pluoxium',
228
+ label: 'Pluoxium',
229
+ color: 'mediumslateblue',
230
+ },
231
+ {
232
+ id: 'miasma',
233
+ path: '/datum/gas/miasma',
234
+ name: 'Miasma',
235
+ label: 'Miasma',
236
+ color: 'olive',
237
+ },
238
+ {
239
+ id: 'freon',
240
+ path: '/datum/gas/freon',
241
+ name: 'Freon',
242
+ label: 'Freon',
243
+ color: 'paleturquoise',
244
+ },
245
+ {
246
+ id: 'hydrogen',
247
+ path: '/datum/gas/hydrogen',
248
+ name: 'Hydrogen',
249
+ label: 'H₂',
250
+ color: 'white',
251
+ },
252
+ {
253
+ id: 'healium',
254
+ path: '/datum/gas/healium',
255
+ name: 'Healium',
256
+ label: 'Healium',
257
+ color: 'salmon',
258
+ },
259
+ {
260
+ id: 'proto_nitrate',
261
+ path: '/datum/gas/proto_nitrate',
262
+ name: 'Proto Nitrate',
263
+ label: 'Proto-Nitrate',
264
+ color: 'greenyellow',
265
+ },
266
+ {
267
+ id: 'zauker',
268
+ path: '/datum/gas/zauker',
269
+ name: 'Zauker',
270
+ label: 'Zauker',
271
+ color: 'darkgreen',
272
+ },
273
+ {
274
+ id: 'halon',
275
+ path: '/datum/gas/halon',
276
+ name: 'Halon',
277
+ label: 'Halon',
278
+ color: 'purple',
279
+ },
280
+ {
281
+ id: 'helium',
282
+ path: '/datum/gas/helium',
283
+ name: 'Helium',
284
+ label: 'He',
285
+ color: 'aliceblue',
286
+ },
287
+ {
288
+ id: 'antinoblium',
289
+ path: '/datum/gas/antinoblium',
290
+ name: 'Antinoblium',
291
+ label: 'Anti-Noblium',
292
+ color: 'maroon',
293
+ },
294
+ {
295
+ id: 'nitrium',
296
+ path: '/datum/gas/nitrium',
297
+ name: 'Nitrium',
298
+ label: 'Nitrium',
299
+ color: 'brown',
300
+ },
301
+ ] as const;
302
+
303
+ // Returns gas label based on gasId
304
+ export const getGasLabel = (gasId: string, fallbackValue?: string) => {
305
+ if (!gasId) return fallbackValue || 'None';
306
+
307
+ const gasSearchString = gasId.toLowerCase();
308
+
309
+ for (let idx = 0; idx < GASES.length; idx++) {
310
+ if (GASES[idx].id === gasSearchString) {
311
+ return GASES[idx].label;
312
+ }
313
+ }
314
+
315
+ return fallbackValue || 'None';
316
+ };
317
+
318
+ // Returns gas color based on gasId
319
+ export const getGasColor = (gasId: string) => {
320
+ if (!gasId) return 'black';
321
+
322
+ const gasSearchString = gasId.toLowerCase();
323
+
324
+ for (let idx = 0; idx < GASES.length; idx++) {
325
+ if (GASES[idx].id === gasSearchString) {
326
+ return GASES[idx].color;
327
+ }
328
+ }
329
+
330
+ return 'black';
331
+ };
332
+
333
+ // Returns gas object based on gasId
334
+ export const getGasFromId = (gasId: string): Gas | undefined => {
335
+ if (!gasId) return;
336
+
337
+ const gasSearchString = gasId.toLowerCase();
338
+
339
+ for (let idx = 0; idx < GASES.length; idx++) {
340
+ if (GASES[idx].id === gasSearchString) {
341
+ return GASES[idx];
342
+ }
343
+ }
344
+ };
345
+
346
+ // Returns gas object based on gasPath
347
+ export const getGasFromPath = (gasPath: string): Gas | undefined => {
348
+ if (!gasPath) return;
349
+
350
+ for (let idx = 0; idx < GASES.length; idx++) {
351
+ if (GASES[idx].path === gasPath) {
352
+ return GASES[idx];
353
+ }
354
+ }
355
+ };