diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 999fbd5608d..9310786755c 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -239,12 +239,21 @@ * * R - The stack recipe we are using to get a list of properties */ /obj/item/stack/proc/build_recipe(datum/stack_recipe/R) - return list( - "res_amount" = R.res_amount, - "max_res_amount" = R.max_res_amount, - "req_amount" = R.req_amount, - "ref" = text_ref(R), - ) + var/list/data = list() + var/obj/result = R.result_type + + data["ref"] = text_ref(R) + data["req_amount"] = R.req_amount + data["res_amount"] = R.res_amount + data["max_res_amount"] = R.max_res_amount + data["icon"] = result.icon + data["icon_state"] = result.icon_state + + // DmIcon cannot paint images. So, if we have grayscale sprite, we need ready base64 image. + if(R.result_image) + data["image"] = R.result_image + + return data /** * Checks if the recipe is valid to be used diff --git a/code/game/objects/items/stacks/stack_recipe.dm b/code/game/objects/items/stacks/stack_recipe.dm index a11feee8c68..11d5315e5f6 100644 --- a/code/game/objects/items/stacks/stack_recipe.dm +++ b/code/game/objects/items/stacks/stack_recipe.dm @@ -7,6 +7,8 @@ var/title = "ERROR" /// What atom the recipe makes, typepath var/atom/result_type + /// Generated base64 image. Used only if result has color + var/result_image /// Amount of stack required to make var/req_amount = 1 /// Amount of resulting atoms made @@ -53,6 +55,15 @@ src.trait_modifier = trait_modifier src.category = src.category || category || CAT_MISC + // We create base64 image only if item have color. Otherwise use icon_ref for TGUI + var/obj/item/result = result_type + var/paint = result::color + if(!isnull(paint) && paint != COLOR_WHITE) + var/icon/result_icon = icon(result::icon, result::icon_state, SOUTH, 1) + result_icon.Scale(32, 32) + result_icon.Blend(paint, ICON_MULTIPLY) + src.result_image = "[icon2base64(result_icon)]" + /datum/stack_recipe/radial /// Optional info to be shown on the radial option for this item var/desc diff --git a/tgui/packages/tgui/interfaces/StackCrafting.tsx b/tgui/packages/tgui/interfaces/StackCrafting.tsx index 38a7cbdf24c..b45d24e861e 100644 --- a/tgui/packages/tgui/interfaces/StackCrafting.tsx +++ b/tgui/packages/tgui/interfaces/StackCrafting.tsx @@ -1,22 +1,25 @@ import { useState } from 'react'; import { - Box, Button, Collapsible, - Input, + ImageButton, NoticeBox, Section, - Table, + Stack, } from 'tgui-core/components'; import { clamp } from 'tgui-core/math'; -import { createSearch } from 'tgui-core/string'; +import { createSearch, toTitleCase } from 'tgui-core/string'; import { useBackend } from '../backend'; import { Window } from '../layouts'; +import { SearchBar } from './common/SearchBar'; type Recipe = { ref: unknown | null; req_amount: number; + icon: string; + icon_state: string; + image?: string; // for multiplier buttons res_amount: number; @@ -88,7 +91,16 @@ const filterRecipeList = ( return []; }) - .sort(([a], [b]) => (a < b ? -1 : a !== b ? 1 : 0)), + + // Sort items so that lists are on top and recipes underneath. + // Plus everything is in alphabetical order. + .sort(([aKey, aValue], [bKey, bValue]) => + isRecipeList(aValue) !== isRecipeList(bValue) + ? isRecipeList(aValue) + ? -1 + : 1 + : aKey.localeCompare(bKey), + ), ); return Object.keys(filteredList).length ? filteredList : undefined; @@ -102,23 +114,21 @@ export const StackCrafting = (_props) => { const testSearch = createSearch(searchText, (item: string) => item); const filteredRecipes = filterRecipeList(recipes, testSearch); - const height: number = clamp(94 + Object.keys(recipes).length * 26, 250, 500); + const height: number = clamp(96 + Object.keys(recipes).length * 37, 250, 500); return ( - +
- Search - setSearchText(value)} - mx={1} - /> - + setSearchText(value)} + /> } > {filteredRecipes ? ( @@ -141,10 +151,19 @@ const RecipeListBox = (props: RecipeListProps) => { const recipe = recipes[title]; if (isRecipeList(recipe)) { return ( - - - - + + ); } else { @@ -181,14 +200,19 @@ const Multipliers = (props: MultiplierProps) => { if (maxM >= multiplier) { finalResult.push( , ); } } @@ -196,14 +220,19 @@ const Multipliers = (props: MultiplierProps) => { if (multipliers.indexOf(maxM) === -1) { finalResult.push( , ); } @@ -212,44 +241,54 @@ const Multipliers = (props: MultiplierProps) => { const RecipeBox = (props: RecipeBoxProps) => { const { act, data } = useBackend(); - const { amount } = data; - const { recipe, title } = props; - - const { res_amount, max_res_amount, req_amount, ref } = recipe; + const { + res_amount, + max_res_amount, + req_amount, + ref, + icon, + icon_state, + image, + } = recipe; const resAmountLabel = res_amount > 1 ? `${res_amount}x ` : ''; const sheetSuffix = req_amount > 1 ? 's' : ''; - const buttonName = `${resAmountLabel}${title} (${req_amount} sheet${sheetSuffix})`; + const buttonName = `${resAmountLabel}${title}`; + const reqSheets = `${req_amount} sheet${sheetSuffix}`; const maxMultiplier = buildMultiplier(recipe, amount); return ( - - - - -
-
+ 1 && + maxMultiplier > 1 && ( + + ) + } + onClick={() => + act('make', { + ref: ref, + multiplier: 1, + }) + } + > + + {toTitleCase(buttonName)} + + {reqSheets} + + + ); }; diff --git a/tgui/packages/tgui/interfaces/common/SearchBar.tsx b/tgui/packages/tgui/interfaces/common/SearchBar.tsx index e6a52eb7abc..27b6f68a13a 100644 --- a/tgui/packages/tgui/interfaces/common/SearchBar.tsx +++ b/tgui/packages/tgui/interfaces/common/SearchBar.tsx @@ -37,7 +37,9 @@ export function SearchBar(props: Props) { return ( - {!noIcon && } + + {!noIcon && } +