Update the autolathe UI, make it less laggy (#16521)

This commit is contained in:
ShadowLarkens
2024-10-26 14:19:12 +02:00
committed by GitHub
parent 9e53689ec7
commit 4ebf8c4303
3 changed files with 194 additions and 130 deletions
+4 -1
View File
@@ -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),
+1 -1
View File
@@ -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
+189 -128
View File
@@ -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<string, number>;
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<Data>();
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 (
<Window width={550} height={700}>
<Window.Content scrollable>
<Section title="Materials">
<Materials disableEject />
</Section>
<Section
title="Recipes"
buttons={
<Dropdown
autoScroll={false}
width="190px"
options={categories}
selected={categories[category]}
onSelected={(val) => setCategory(categories.indexOf(val))}
/>
}
>
<Input
fluid
placeholder="Search for..."
value={searchText}
onInput={(e, v: string) => setSearchText(v)}
mb={1}
/>
{recipesToShow.map((recipe) => (
<Flex justify="space-between" align="center" key={recipe.ref}>
<Flex.Item>
<Button
color={(recipe.hidden && 'red') || null}
icon="hammer"
iconSpin={busy === recipe.name}
disabled={!canBeMade(recipe, materials, 1)}
onClick={() => act('make', { make: recipe.ref })}
>
{toTitleCase(recipe.name)}
</Button>
{(!recipe.is_stack && (
<Box as="span">
<Button
color={(recipe.hidden && 'red') || null}
disabled={!canBeMade(recipe, materials, 5)}
onClick={() =>
act('make', { make: recipe.ref, multiplier: 5 })
}
>
x5
</Button>
<Button
color={(recipe.hidden && 'red') || null}
disabled={!canBeMade(recipe, materials, 10)}
onClick={() =>
act('make', { make: recipe.ref, multiplier: 10 })
}
>
x10
</Button>
</Box>
)) ||
null}
</Flex.Item>
<Flex.Item>
{(recipe.requirements &&
Object.keys(recipe.requirements)
.map(
(mat) =>
toTitleCase(mat) + ': ' + recipe.requirements[mat],
)
.join(', ')) || <Box>No resources required.</Box>}
</Flex.Item>
</Flex>
))}
</Section>
<Window width={670} height={600}>
<Window.Content>
<Stack vertical fill>
<Stack.Item>
<Section title="Materials">
<Materials />
</Section>
</Stack.Item>
<Stack.Item grow>
<Designs />
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};
const Designs = (props) => {
const { act, data } = useBackend<Data>();
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 (
<Stack fill>
<Stack.Item width="240px">
<Section title="Categories" fill>
<Tabs vertical backgroundColor="rgba(0,0,0,0)">
{Object.entries(categories).map(([category, amount]) => (
<Tabs.Tab
key={category}
rightSlot={`(${amount})`}
selected={selectedCategory === category}
onClick={() => setSelectedCategory(category)}
>
{category}
</Tabs.Tab>
))}
</Tabs>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section title={selectedCategory} fill>
<Stack>
<Stack.Item>
<Icon name="magnifying-glass" />
</Stack.Item>
<Stack.Item grow>
<Input
fluid
placeholder="Search all designs..."
value={searchText}
onChange={(e, val) => setSearchText(val)}
/>
</Stack.Item>
</Stack>
<Section fill height="94%" mt={1} scrollable>
<VirtualList>
{recipes.map((recipe) => (
<Recipe
key={recipe.ref}
recipe={recipe}
materials={materials}
busy={data.busy}
/>
))}
</VirtualList>
</Section>
</Section>
</Stack.Item>
</Stack>
);
};
const canBeMade = (
required: Record<string, number>,
available: Record<string, number>,
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<string, number>;
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 (
<Box>
<Button
width="80%"
icon="hammer"
iconSpin={busy === recipe.name}
disabled={!canBeMadeCb(recipe.requirements, materials)}
onClick={() => act('make', { make: recipe.ref, multiplier: 1 })}
tooltip={requirementString}
>
{toTitleCase(recipe.name)}
</Button>
<Button
disabled={!canBeMadeCb(recipe.requirements, materials, 5)}
onClick={() => act('make', { make: recipe.ref, multiplier: 5 })}
>
x5
</Button>
<Button
disabled={!canBeMadeCb(recipe.requirements, materials, 10)}
onClick={() => act('make', { make: recipe.ref, multiplier: 10 })}
>
x10
</Button>
</Box>
);
};