From 67ccfec0bfec37e2e29116db9ea7de0274fa76c1 Mon Sep 17 00:00:00 2001 From: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Date: Thu, 15 May 2025 17:40:49 -0700 Subject: [PATCH] RPD ui refactor (#91018) ## About The Pull Request Fixes a possible bluescreen in the RPD. Issue was caused when the backend didn't send the static data that the UI relied upon to populate pipe layers (perhaps due to throttling) Reworks the UI a bit so that the content section is scrollable instead of the entire window. This prevents the top content from disappearing if you're scrolling available pipes. Adds some extra typing where it was type: any ![image](https://github.com/user-attachments/assets/d8e4e0b6-1798-4c76-bf94-10a50f916b3b) ## Why It's Good For The Game Fixes #90992 ## Changelog :cl: fix: Fixed an uncommon bluescreen in the rapid pipe dispenser UI. /:cl: --- .../tgui/interfaces/RapidPipeDispenser.tsx | 266 +++++++++--------- 1 file changed, 129 insertions(+), 137 deletions(-) diff --git a/tgui/packages/tgui/interfaces/RapidPipeDispenser.tsx b/tgui/packages/tgui/interfaces/RapidPipeDispenser.tsx index bee64ef659d..339e85f03c7 100644 --- a/tgui/packages/tgui/interfaces/RapidPipeDispenser.tsx +++ b/tgui/packages/tgui/interfaces/RapidPipeDispenser.tsx @@ -3,31 +3,32 @@ import { Box, Button, ColorBox, + ImageButton, LabeledList, Section, Stack, - Table, + StyleableSection, Tabs, } from 'tgui-core/components'; -import { BooleanLike, classes } from 'tgui-core/react'; +import { BooleanLike } from 'tgui-core/react'; import { capitalizeAll } from 'tgui-core/string'; import { useBackend } from '../backend'; import { Window } from '../layouts'; -const ROOT_CATEGORIES = ['Atmospherics', 'Disposals', 'Transit Tubes']; +const ROOT_CATEGORIES = ['Atmospherics', 'Disposals', 'Transit Tubes'] as const; export const ICON_BY_CATEGORY_NAME = { Atmospherics: 'wrench', - Disposals: 'trash-alt', - 'Transit Tubes': 'bus', - Pipes: 'grip-lines', Binary: 'arrows-left-right', - 'Disposal Pipes': 'grip-lines', Devices: 'microchip', + 'Disposal Pipes': 'grip-lines', + Disposals: 'trash-alt', 'Heat Exchange': 'thermometer-half', + Pipes: 'grip-lines', 'Station Equipment': 'microchip', -}; + 'Transit Tubes': 'bus', +} as const; const TOOLS = [ { @@ -46,69 +47,55 @@ const TOOLS = [ name: 'Reprogram', bitmask: 8, }, -]; +] as const; type DirectionsAllowed = { + east: BooleanLike; north: BooleanLike; south: BooleanLike; - east: BooleanLike; west: BooleanLike; }; -type Colors = { - green: string; - blue: string; - red: string; - orange: string; - cyan: string; - dark: string; - yellow: string; - brown: string; - pink: string; - purple: string; - violet: string; - omni: string; -}; - type Category = { cat_name: string; recipes: Recipe[]; }; type Recipe = { - pipe_name: string; pipe_index: number; + pipe_name: string; previews: Preview[]; }; type Preview = { - selected: BooleanLike; - dir: string; dir_name: string; - icon_state: string; + dir: string; flipped: BooleanLike; + icon_state: string; + selected: BooleanLike; }; type Data = { - // Static - paint_colors: Colors; - max_pipe_layers: number; // Dynamic - category: number; - pipe_layers: number; - multi_layer: BooleanLike; categories: Category[]; - selected_recipe: string; - selected_color: string; - selected_category: string; - mode: number; + category: number; init_directions: DirectionsAllowed; + mode: number; + multi_layer: BooleanLike; + pipe_layers: number; + selected_category: string; + selected_color: string; + selected_recipe: string; + // Static + max_pipe_layers: number; + paint_colors: Record; }; -export const ColorItem = (props) => { +export function ColorItem(props) { const { act, data } = useBackend(); - const { selected_color, paint_colors } = data; + const { selected_color, paint_colors = {} } = data; const colorNames = Object.keys(paint_colors); + return ( {colorNames.map((colorName) => ( @@ -134,11 +121,12 @@ export const ColorItem = (props) => { ); -}; +} -const ModeItem = (props) => { +function ModeItem(props) { const { act, data } = useBackend(); const { mode } = data; + return ( {TOOLS.map((tool) => ( @@ -156,11 +144,12 @@ const ModeItem = (props) => { ))} ); -}; +} -const CategoryItem = (props) => { +function CategoryItem(props) { const { act, data } = useBackend(); const { category: rootCategoryIndex } = data; + return ( {ROOT_CATEGORIES.map((categoryName, i) => ( @@ -176,11 +165,12 @@ const CategoryItem = (props) => { ))} ); -}; +} -const SelectionSection = (props) => { +function SelectionSection(props) { const { data } = useBackend(); const { category: rootCategoryIndex } = data; + return (
@@ -191,34 +181,33 @@ const SelectionSection = (props) => {
); -}; +} -const LayerSelect = (props) => { +function layerToBitmask(layer: number) { + return 1 << layer; +} + +function LayerSelect(props) { const { act, data } = useBackend(); - const { pipe_layers, multi_layer, max_pipe_layers } = data; - const layer_to_bitmask = (layer: number) => { - return 1 << layer; - }; + const { pipe_layers, multi_layer, max_pipe_layers = 1 } = data; return ( - {Array(max_pipe_layers) - .keys() - .map((layer) => ( - - act('pipe_layers', { pipe_layers: layer_to_bitmask(layer) }) - } - > - {layer + 1} - - ))} + {Array.from({ length: max_pipe_layers }).map((_, layer) => ( + + act('pipe_layers', { pipe_layers: layerToBitmask(layer) }) + } + > + {layer + 1} + + ))} { ); +} + +type RecipeRowProps = { + recipe: Recipe; + shownCategory: Category; }; -const PreviewSelect = (props) => { +function RecipeRow(props: RecipeRowProps) { const { act } = useBackend(); - return ( - - {props.previews.map((preview) => ( - - ))} - - ); -}; + const { recipe, shownCategory } = props; -const PipeTypeSection = (props) => { + return ( + + + {recipe.previews.map((preview) => ( + + { + act('pipe_type', { + pipe_type: recipe.pipe_index, + category: shownCategory.cat_name, + }); + act('setdir', { + dir: preview.dir, + flipped: preview.flipped, + }); + }} + selected={preview.selected} + tooltip={preview.dir_name} + tooltipPosition="bottom" + /> + + ))} + + + ); +} + +function PipeTypeSection(props) { const { data } = useBackend(); const { categories = [], selected_category } = data; const [categoryName, setCategoryName] = useState(selected_category); + const shownCategory = categories.find((category) => category.cat_name === categoryName) || categories[0]; return ( -
- + + {categories.map((category, i) => ( { ))} - +
{shownCategory?.recipes.map((recipe) => ( - - - - - - - {recipe.pipe_name} - - + ))} -
-
+ + ); -}; +} -export const SmartPipeBlockSection = (props) => { +export function SmartPipeBlockSection(props) { const { act, data } = useBackend(); const { init_directions = [] } = data; + return (
@@ -395,14 +386,15 @@ export const SmartPipeBlockSection = (props) => {
); -}; +} -export const RapidPipeDispenser = (props) => { +export function RapidPipeDispenser(props) { const { data } = useBackend(); const { category: rootCategoryIndex } = data; + return ( - + @@ -423,4 +415,4 @@ export const RapidPipeDispenser = (props) => { ); -}; +}