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