From 4ebf8c430343b58a9ec2e7e68cbc4cc919b44707 Mon Sep 17 00:00:00 2001 From: ShadowLarkens Date: Sat, 26 Oct 2024 05:19:12 -0700 Subject: [PATCH] Update the autolathe UI, make it less laggy (#16521) --- code/datums/components/material_container.dm | 5 +- code/game/machinery/autolathe.dm | 2 +- tgui/packages/tgui/interfaces/Autolathe.tsx | 317 +++++++++++-------- 3 files changed, 194 insertions(+), 130 deletions(-) diff --git a/code/datums/components/material_container.dm b/code/datums/components/material_container.dm index 4305a236b36..bbd2b7fd128 100644 --- a/code/datums/components/material_container.dm +++ b/code/datums/components/material_container.dm @@ -471,12 +471,15 @@ return materials[mat] /// List format is list(list(name = ..., amount = ..., ref = ..., etc.), list(...)) -/datum/component/material_container/tgui_data(mob/user) +/datum/component/material_container/tgui_data(mob/user, skip_empty = FALSE) var/list/data = list() for(var/datum/material/material as anything in materials) var/amount = materials[material] + if(amount == 0 && skip_empty) + continue + data += list(list( "name" = material.name, "ref" = REF(material), diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index efa21c3b1f3..752f9305acb 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -90,7 +90,7 @@ var/list/data = ..() data["busy"] = busy var/datum/component/material_container/materials = GetComponent(/datum/component/material_container) - data["materials"] = materials.tgui_data() + data["materials"] = materials.tgui_data(user, TRUE) data["mat_efficiency"] = mat_efficiency return data diff --git a/tgui/packages/tgui/interfaces/Autolathe.tsx b/tgui/packages/tgui/interfaces/Autolathe.tsx index f52ba149a60..bfb355f030c 100644 --- a/tgui/packages/tgui/interfaces/Autolathe.tsx +++ b/tgui/packages/tgui/interfaces/Autolathe.tsx @@ -1,148 +1,209 @@ -import { filter, sortBy } from 'common/collections'; -import { flow } from 'common/fp'; -import { BooleanLike } from 'common/react'; -import { createSearch, toTitleCase } from 'common/string'; +import { toTitleCase } from 'common/string'; +import { useCallback, useMemo } from 'react'; +import { Window } from 'tgui/layouts'; +import { + Box, + Button, + Icon, + Input, + Section, + Stack, + Tabs, + VirtualList, +} from 'tgui-core/components'; +import { BooleanLike } from 'tgui-core/react'; import { useBackend, useSharedState } from '../backend'; -import { Box, Button, Dropdown, Flex, Input, Section } from '../components'; -import { Window } from '../layouts'; -import { mat } from './common/CommonTypes'; +import { formatSiUnit } from '../format'; import { Materials } from './ExosuitFabricator/Material'; -const canBeMade = (recipe, materials, mult: number = 1) => { - if (recipe.requirements === null) { - return true; - } - - let recipeRequiredMaterials: string[] = Object.keys(recipe.requirements); - - for (let mat_id of recipeRequiredMaterials) { - let material = materials.find((val: mat) => val.name === mat_id); - if (!material) { - continue; // yes, if we cannot find the material, we just ignore it :V - } - if (material.amount < recipe.requirements[mat_id] * mult) { - return false; - } - } - - return true; -}; -type Data = { - recipes: recipe[]; - categories: string[]; - busy: string; - materials: mat[]; +type MaterialData = { + name: string; + ref: string; + amount: number; + sheets: number; + removable: BooleanLike; }; -type recipe = { +type RecipeData = { category: string; name: string; ref: string; requirements: Record; hidden: BooleanLike; - coeff_applies: BooleanLike; + coeff_applies: number; is_stack: BooleanLike; }; +type Data = { + busy: string; + materials: MaterialData[]; + mat_efficiency: number; + recipes: RecipeData[]; +}; + export const Autolathe = (props) => { - const { act, data } = useBackend(); - - const { recipes, busy, materials, categories } = data; - - const [category, setCategory] = useSharedState('category', 0); - const [searchText, setSearchText] = useSharedState('search_text', ''); - - const testSearch = createSearch(searchText, (recipe: recipe) => recipe.name); - - const recipesToShow: recipe[] = flow([ - (recipes: recipe[]) => - filter(recipes, (recipe) => recipe.category === categories[category]), - (recipes: recipe[]) => { - if (!searchText) { - return recipes; - } else { - return filter(recipes, testSearch); - } - }, - (recipes: recipe[]) => - sortBy(recipes, (recipe) => recipe.name.toLowerCase()), - ])(recipes); - return ( - - -
- -
-
setCategory(categories.indexOf(val))} - /> - } - > - setSearchText(v)} - mb={1} - /> - {recipesToShow.map((recipe) => ( - - - - {(!recipe.is_stack && ( - - - - - )) || - null} - - - {(recipe.requirements && - Object.keys(recipe.requirements) - .map( - (mat) => - toTitleCase(mat) + ': ' + recipe.requirements[mat], - ) - .join(', ')) || No resources required.} - - - ))} -
+ + + + +
+ +
+
+ + + +
); }; + +const Designs = (props) => { + const { act, data } = useBackend(); + + const [selectedCategory, setSelectedCategory] = useSharedState( + 'selected_category', + 'No Category Selected', + ); + const [searchText, setSearchText] = useSharedState('search_text', ''); + + const materials = useMemo(() => { + let materials = {}; + for (const material of data.materials) { + materials[material.name] = material.amount; + } + return materials; + }, [data.materials]); + + const categories = {}; + + for (const recipe of data.recipes) { + if (categories[recipe.category]) { + categories[recipe.category] += 1; + } else { + categories[recipe.category] = 1; + } + } + + let recipes: RecipeData[]; + + if (searchText.length > 0) { + recipes = data.recipes.filter( + (recipe) => + recipe.category !== 'All' && + recipe.name.toLowerCase().includes(searchText.toLowerCase()), + ); + } else { + recipes = data.recipes.filter( + (recipe) => recipe.category === selectedCategory, + ); + } + + return ( + + +
+ + {Object.entries(categories).map(([category, amount]) => ( + setSelectedCategory(category)} + > + {category} + + ))} + +
+
+ +
+ + + + + + setSearchText(val)} + /> + + +
+ + {recipes.map((recipe) => ( + + ))} + +
+
+
+
+ ); +}; + +const canBeMade = ( + required: Record, + available: Record, + multiplier: number = 1, +): boolean => { + for (let [id, amt] of Object.entries(required)) { + if ((available[id] || 0) < amt * multiplier) { + return false; + } + } + return true; +}; + +const Recipe = (props: { + recipe: RecipeData; + materials: Record; + busy: string; +}) => { + const { act } = useBackend(); + const { recipe, materials, busy } = props; + + const canBeMadeCb = useCallback(canBeMade, [recipe, materials]); + + const requirementString = Object.entries(recipe.requirements) + .map(([name, number]) => `${toTitleCase(name)}: ${formatSiUnit(number, 0)}`) + .join(', '); + + return ( + + + + + + ); +};