From a6aba1706a45b2ceb51ddb77033ecdeddafa6e19 Mon Sep 17 00:00:00 2001
From: Jeremiah <42397676+jlsnow301@users.noreply.github.com>
Date: Sun, 30 Jun 2024 19:21:51 -0700
Subject: [PATCH] tgui: Power monitor refactor, tgui-core bump (#84486)
## About The Pull Request
Refactors the power monitor UI to prevent layout shift and losing
headers as you scroll.
Also bumps tgui-core version to latest, since there's been some changes
there (Style's requests)

## Why It's Good For The Game
Neater UI for power monitor
## Changelog
:cl:
fix: Fixed some UI bugs in the power monitor screen.
/:cl:
---
.../tgui/interfaces/NtosPowerMonitor.tsx | 2 +-
.../packages/tgui/interfaces/PowerMonitor.jsx | 230 -------------
.../packages/tgui/interfaces/PowerMonitor.tsx | 318 ++++++++++++++++++
tgui/packages/tgui/package.json | 2 +-
tgui/yarn.lock | 10 +-
5 files changed, 325 insertions(+), 237 deletions(-)
delete mode 100644 tgui/packages/tgui/interfaces/PowerMonitor.jsx
create mode 100644 tgui/packages/tgui/interfaces/PowerMonitor.tsx
diff --git a/tgui/packages/tgui/interfaces/NtosPowerMonitor.tsx b/tgui/packages/tgui/interfaces/NtosPowerMonitor.tsx
index 295c41c3c72..bb7f7d1d527 100644
--- a/tgui/packages/tgui/interfaces/NtosPowerMonitor.tsx
+++ b/tgui/packages/tgui/interfaces/NtosPowerMonitor.tsx
@@ -4,7 +4,7 @@ import { PowerMonitorContent } from './PowerMonitor';
export const NtosPowerMonitor = (props) => {
return (
-
+
diff --git a/tgui/packages/tgui/interfaces/PowerMonitor.jsx b/tgui/packages/tgui/interfaces/PowerMonitor.jsx
deleted file mode 100644
index 039e700f68c..00000000000
--- a/tgui/packages/tgui/interfaces/PowerMonitor.jsx
+++ /dev/null
@@ -1,230 +0,0 @@
-import { map, sortBy } from 'common/collections';
-import { flow } from 'common/fp';
-import { toFixed } from 'common/math';
-import { useState } from 'react';
-
-import { useBackend } from '../backend';
-import {
- Box,
- Button,
- Chart,
- ColorBox,
- Dimmer,
- Flex,
- Icon,
- LabeledList,
- ProgressBar,
- Section,
- Stack,
- Table,
-} from '../components';
-import { Window } from '../layouts';
-
-const PEAK_DRAW = 500000;
-
-export const powerRank = (str) => {
- const unit = String(str.split(' ')[1]).toLowerCase();
- return ['w', 'kw', 'mw', 'gw'].indexOf(unit);
-};
-
-export const PowerMonitor = () => {
- return (
-
-
-
-
-
- );
-};
-
-export const PowerMonitorContent = (props) => {
- const { data } = useBackend();
- const { history = { supply: [], demand: [] } } = data;
- const [sortByField, setSortByField] = useState(null);
- const supply = history.supply[history.supply.length - 1] || 0;
- const demand = history.demand[history.demand.length - 1] || 0;
- const supplyData = history.supply.map((value, i) => [i, value]);
- const demandData = history.demand.map((value, i) => [i, value]);
- const maxValue = Math.max(PEAK_DRAW, ...history.supply, ...history.demand);
- // Process area data
- const areas = flow([
- (areas) =>
- map(areas, (area, i) => ({
- ...area,
- // Generate a unique id
- id: area.name + i,
- })),
- sortByField === 'name' && ((areas) => sortBy(areas, (area) => area.name)),
- sortByField === 'charge' &&
- ((areas) => sortBy(areas, (area) => -area.charge)),
- sortByField === 'draw' &&
- ((areas) =>
- sortBy(
- areas,
- (area) => -powerRank(area.load),
- (area) => -parseFloat(area.load),
- )),
- ])(data.areas);
- return (
- <>
- {areas.length === 0 && (
-
-
-
-
-
-
- No APCs found!
-
-
-
- )}
-
-
-
-
-
-
- {toFixed(supply / 1000) + ' kW'}
-
-
-
-
- {toFixed(demand / 1000) + ' kW'}
-
-
-
-
-
-
-
-
-
-
-
-
- Sort by:
-
- setSortByField(sortByField !== 'name' && 'name')}
- />
- setSortByField(sortByField !== 'charge' && 'charge')}
- />
- setSortByField(sortByField !== 'draw' && 'draw')}
- />
-
-
-
- Area
- Charge
- Draw
-
- Eqp
-
-
- Lgt
-
-
- Env
-
-
- {areas.map((area, i) => (
-
- | {area.name} |
-
-
- |
-
- {area.load}
- |
-
-
- |
-
-
- |
-
-
- |
-
- ))}
-
-
- >
- );
-};
-
-export const AreaCharge = (props) => {
- const { charging, charge } = props;
- return (
- <>
- 50 ? 'battery-half' : 'battery-quarter')) ||
- (charging === 1 && 'bolt') ||
- (charging === 2 && 'battery-full')
- }
- color={
- (charging === 0 && (charge > 50 ? 'yellow' : 'red')) ||
- (charging === 1 && 'yellow') ||
- (charging === 2 && 'green')
- }
- />
-
- {toFixed(charge) + '%'}
-
- >
- );
-};
-
-const AreaStatusColorBox = (props) => {
- const { status } = props;
- const power = Boolean(status & 2);
- const mode = Boolean(status & 1);
- const tooltipText = (power ? 'On' : 'Off') + ` [${mode ? 'auto' : 'manual'}]`;
- return (
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/PowerMonitor.tsx b/tgui/packages/tgui/interfaces/PowerMonitor.tsx
new file mode 100644
index 00000000000..b311ae15ede
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PowerMonitor.tsx
@@ -0,0 +1,318 @@
+import { useState } from 'react';
+import {
+ Box,
+ Button,
+ Chart,
+ ColorBox,
+ Flex,
+ Icon,
+ LabeledList,
+ ProgressBar,
+ Section,
+ Stack,
+ Table,
+} from 'tgui-core/components';
+import { toFixed } from 'tgui-core/math';
+
+import { useBackend } from '../backend';
+import { Tooltip } from '../components';
+import { Window } from '../layouts';
+import { LoadingScreen } from './common/LoadingToolbox';
+
+type Data = {
+ areas: Area[];
+ history: PowerHistory;
+};
+
+type Area = {
+ charge: number;
+ charging: number;
+ env: number;
+ eqp: number;
+ lgt: number;
+ load: string;
+ name: string;
+};
+
+type PowerHistory = {
+ demand: number[];
+ supply: number[];
+};
+
+export function powerRank(str: string): number {
+ const unit = String(str.split(' ')[1]).toLowerCase();
+ return ['w', 'kw', 'mw', 'gw'].indexOf(unit);
+}
+
+// Oh no not another sorting algorithm
+function powerSort(a: Area, b: Area): number {
+ const sortedByRank = powerRank(a.load) - powerRank(b.load);
+ const sortedByLoad = parseFloat(a.load) - parseFloat(b.load);
+
+ if (sortedByRank !== 0) {
+ return sortedByRank;
+ }
+ return sortedByLoad;
+}
+
+function nameSort(a: Area, b: Area): number {
+ if (a.name < b.name) {
+ return -1;
+ }
+ if (a.name > b.name) {
+ return 1;
+ }
+ return 0;
+}
+
+export function PowerMonitor() {
+ return (
+
+
+
+
+
+ );
+}
+
+const PEAK_DRAW = 500000;
+
+export function PowerMonitorContent(props) {
+ const { data } = useBackend();
+ const { history } = data;
+
+ if (!history) {
+ return <>Loading...>;
+ }
+
+ const supply = history.supply[history.supply.length - 1] || 0;
+ const demand = history.demand[history.demand.length - 1] || 0;
+
+ const supplyData = history.supply.map((value, i) => [i, value]);
+ const demandData = history.demand.map((value, i) => [i, value]);
+
+ const maxValue = Math.max(PEAK_DRAW, ...history.supply, ...history.demand);
+
+ if (supplyData.length === 0 || demandData.length === 0) {
+ return ;
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ {toFixed(supply / 1000) + ' kW'}
+
+
+
+
+ {toFixed(demand / 1000) + ' kW'}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function StationAreas(props) {
+ const { data } = useBackend();
+
+ const [sortByField, setSortByField] = useState('');
+
+ const areas = data.areas
+ .map((area, i) => ({
+ ...area,
+ // Generate a unique id
+ id: area.name + i,
+ }))
+ .sort((a, b) => {
+ if (sortByField === 'name') {
+ return nameSort(a, b);
+ }
+ if (sortByField === 'charge') {
+ return a.charge - b.charge;
+ }
+ if (sortByField === 'draw') {
+ return powerSort(b, a);
+ }
+ return 0;
+ });
+
+ return (
+
+
+
+
+ Sort by:
+
+ setSortByField(sortByField !== 'name' ? 'name' : '')}
+ >
+ Name
+
+
+ setSortByField(sortByField !== 'charge' ? 'charge' : '')
+ }
+ >
+ Charge
+
+ setSortByField(sortByField !== 'draw' ? 'draw' : '')}
+ >
+ Draw
+
+
+
+
+
+
+
+
+ Area
+ Charge
+
+ Draw
+
+
+ Eqp
+
+
+ Lgt
+
+
+ Env
+
+
+ {areas.map((area) => (
+
+ | {area.name} |
+
+
+ |
+
+ {area.load}
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+ ))}
+
+
+
+
+ );
+}
+
+type AreaChargeProps = {
+ charge: number;
+ charging: number;
+};
+
+const NOT_CHARGING = 0;
+const CHARGING = 1;
+const CHARGED = 2;
+
+export function AreaCharge(props: AreaChargeProps) {
+ const { charging, charge } = props;
+
+ let name: string;
+ if (charging === NOT_CHARGING) {
+ name = charge > 50 ? 'battery-half' : 'battery-quarter';
+ } else if (charging === CHARGING) {
+ name = 'bolt';
+ } else {
+ name = 'battery-full';
+ }
+
+ return (
+ <>
+ 50 ? 'yellow' : 'red')) ||
+ (charging === CHARGING && 'yellow') ||
+ (charging === CHARGED && 'green')
+ }
+ />
+
+ {toFixed(charge) + '%'}
+
+ >
+ );
+}
+
+type AreaStatusColorBoxProps = {
+ status: number;
+};
+
+function AreaStatusColorBox(props: AreaStatusColorBoxProps) {
+ const { status } = props;
+
+ const power = Boolean(status & 2);
+ const mode = Boolean(status & 1);
+ const tooltipText = (power ? 'On' : 'Off') + ` [${mode ? 'auto' : 'manual'}]`;
+
+ return (
+
+
+
+ );
+}
diff --git a/tgui/packages/tgui/package.json b/tgui/packages/tgui/package.json
index a9ab77fac45..ffbb15ba176 100644
--- a/tgui/packages/tgui/package.json
+++ b/tgui/packages/tgui/package.json
@@ -16,7 +16,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-popper": "^2.3.0",
- "tgui-core": "^1.0.18",
+ "tgui-core": "^1.1.2",
"tgui-dev-server": "workspace:*",
"tgui-polyfill": "workspace:*"
}
diff --git a/tgui/yarn.lock b/tgui/yarn.lock
index 5421adc8057..88b94e8cda5 100644
--- a/tgui/yarn.lock
+++ b/tgui/yarn.lock
@@ -8281,13 +8281,13 @@ __metadata:
languageName: unknown
linkType: soft
-"tgui-core@npm:^1.0.18":
- version: 1.0.18
- resolution: "tgui-core@npm:1.0.18"
+"tgui-core@npm:^1.1.2":
+ version: 1.1.2
+ resolution: "tgui-core@npm:1.1.2"
peerDependencies:
react: ^18.2.0
react-dom: ^18.2.0
- checksum: 10c0/f5fa9078649ba0aa052116cd3ecbbb3a38113e9c32e6875c233d074f38db6abdb4dbfc3fb846d3cd125d4572eaf999988a082c1650b94ae429ace90e56deaca9
+ checksum: 10c0/05edc2215daf63bdb126f57aa74e17875784dbe1fadf19f1218dabb17dfffcd63b3c6a1678eb8a5b10b8d325755c1e124f1298350be20af913e9cd0d5a749ec9
languageName: node
linkType: hard
@@ -8399,7 +8399,7 @@ __metadata:
react: "npm:^18.2.0"
react-dom: "npm:^18.2.0"
react-popper: "npm:^2.3.0"
- tgui-core: "npm:^1.0.18"
+ tgui-core: "npm:^1.1.2"
tgui-dev-server: "workspace:*"
tgui-polyfill: "workspace:*"
languageName: unknown