From 9d968e6ba956af5f09db6c2d85d5b1ededc3583a Mon Sep 17 00:00:00 2001 From: Aylong <69762909+AyIong@users.noreply.github.com> Date: Tue, 24 Sep 2024 00:04:37 +0300 Subject: [PATCH] Port DmIcon & Image components from TG (#26623) * Port DmIcon & Image components from TG * Documentation * Uh oh * I hate it * I will bang you --- code/__DEFINES/subsystems.dm | 1 + .../subsystem/non_firing/SSassets.dm | 2 +- .../subsystem/non_firing/SSearly_assets.dm | 18 ++ code/game/objects/items/stacks/stack.dm | 8 +- .../game/objects/items/stacks/stack_recipe.dm | 13 +- code/modules/arcade/prize_counter.dm | 9 +- code/modules/asset_cache/asset_list.dm | 44 +++- .../asset_cache/assets/asset_icon_ref_map.dm | 28 +++ .../asset_cache/assets/asset_prize_counter.dm | 13 - code/modules/tgui/tgui_datum.dm | 2 + paradise.dme | 3 +- tgui/docs/component-reference.md | 4 + tgui/global.d.ts | 7 +- tgui/packages/tgui/components/DmIcon.tsx | 88 +++++++ tgui/packages/tgui/components/Image.tsx | 64 +++++ tgui/packages/tgui/components/ImageButton.tsx | 27 ++- tgui/packages/tgui/components/index.js | 2 + tgui/packages/tgui/http.ts | 12 + tgui/packages/tgui/icons.ts | 14 ++ tgui/packages/tgui/index.js | 3 + .../packages/tgui/interfaces/PrizeCounter.tsx | 18 +- tgui/packages/tgui/interfaces/StackCraft.js | 6 +- .../tgui/styles/components/ImageButton.scss | 5 - tgui/public/tgui-panel.bundle.js | 222 +++++++++--------- tgui/public/tgui.bundle.css | 2 +- tgui/public/tgui.bundle.js | 152 ++++++------ tgui/public/tgui.html | 3 + 27 files changed, 526 insertions(+), 244 deletions(-) create mode 100644 code/controllers/subsystem/non_firing/SSearly_assets.dm create mode 100644 code/modules/asset_cache/assets/asset_icon_ref_map.dm delete mode 100644 code/modules/asset_cache/assets/asset_prize_counter.dm create mode 100644 tgui/packages/tgui/components/DmIcon.tsx create mode 100644 tgui/packages/tgui/components/Image.tsx create mode 100644 tgui/packages/tgui/http.ts create mode 100644 tgui/packages/tgui/icons.ts diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index a818ccba950..61856621d61 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -64,6 +64,7 @@ #define INIT_ORDER_JOBS 11 #define INIT_ORDER_TICKER 10 #define INIT_ORDER_MAPPING 9 +#define INIT_ORDER_EARLY_ASSETS 8 #define INIT_ORDER_ATOMS 7 #define INIT_ORDER_MACHINES 5 #define INIT_ORDER_HOLIDAY 4 diff --git a/code/controllers/subsystem/non_firing/SSassets.dm b/code/controllers/subsystem/non_firing/SSassets.dm index 83bd6b5b9b5..7b8e7c84332 100644 --- a/code/controllers/subsystem/non_firing/SSassets.dm +++ b/code/controllers/subsystem/non_firing/SSassets.dm @@ -36,4 +36,4 @@ SUBSYSTEM_DEF(assets) if(initial(asset_to_load._abstract)) continue - get_asset_datum(type) + load_asset_datum(type) diff --git a/code/controllers/subsystem/non_firing/SSearly_assets.dm b/code/controllers/subsystem/non_firing/SSearly_assets.dm new file mode 100644 index 00000000000..81811357f2e --- /dev/null +++ b/code/controllers/subsystem/non_firing/SSearly_assets.dm @@ -0,0 +1,18 @@ +/// Initializes any assets that need to be loaded ASAP. +SUBSYSTEM_DEF(early_assets) + name = "Early Assets" + init_order = INIT_ORDER_EARLY_ASSETS + flags = SS_NO_FIRE + +/datum/controller/subsystem/early_assets/Initialize() + for(var/datum/asset/asset_type as anything in subtypesof(/datum/asset)) + if(asset_type::_abstract == asset_type) + continue + + if(!asset_type::early) + continue + + if(!load_asset_datum(asset_type)) + stack_trace("Could not initialize early asset [asset_type]!") + + CHECK_TICK diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index fbecc032514..b9e03124fdf 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -271,12 +271,18 @@ /obj/item/stack/proc/build_recipe_data(datum/stack_recipe/recipe) var/list/data = list() + var/obj/result = recipe.result_type data["uid"] = recipe.UID() data["required_amount"] = recipe.req_amount data["result_amount"] = recipe.res_amount data["max_result_amount"] = recipe.max_res_amount - data["image"] = recipe.image + 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(recipe.result_image) + data["image"] = recipe.result_image return data diff --git a/code/game/objects/items/stacks/stack_recipe.dm b/code/game/objects/items/stacks/stack_recipe.dm index a389993f0a1..0cabc52e536 100644 --- a/code/game/objects/items/stacks/stack_recipe.dm +++ b/code/game/objects/items/stacks/stack_recipe.dm @@ -4,10 +4,10 @@ /datum/stack_recipe /// Visible title of recipe var/title = "ERROR" - /// Cached recipe result base64 image - var/image /// Resulting typepath of crafted atom var/result_type + /// Generated base64 image. Used only if result has color + var/result_image /// Required stack amount to make var/req_amount = 1 /// Amount of atoms made @@ -52,15 +52,14 @@ src.window_checks = window_checks src.cult_structure = cult_structure + // We create base64 image only if item have color. Otherwise use icon_ref for TGUI var/obj/item/result = result_type - var/icon/result_icon = icon(result::icon, result::icon_state, SOUTH, 1) var/paint = result::color - - result_icon.Scale(32, 32) 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.image = "[icon2base64(result_icon)]" + src.result_image = "[icon2base64(result_icon)]" /// Returns TRUE if the recipe can be built, otherwise returns FALSE. This proc is only meant as a series of tests to check if construction is possible; the actual creation of the resulting atom should be handled in do_build() /datum/stack_recipe/proc/try_build(mob/user, obj/item/stack/material, multiplier) diff --git a/code/modules/arcade/prize_counter.dm b/code/modules/arcade/prize_counter.dm index fbd8352201d..21708983075 100644 --- a/code/modules/arcade/prize_counter.dm +++ b/code/modules/arcade/prize_counter.dm @@ -30,11 +30,6 @@ ui.set_autoupdate(FALSE) ui.open() -/obj/machinery/prize_counter/ui_assets(mob/user) - return list( - get_asset_datum(/datum/asset/spritesheet/prize_counter) - ) - /obj/machinery/prize_counter/ui_data(mob/user) var/list/data = list() data["tickets"] = tickets @@ -45,12 +40,14 @@ var/list/prizes = list() for(var/datum/prize_item/prize in GLOB.global_prizes.prizes) + var/obj/prize_item = prize.typepath prizes += list(list( "name" = initial(prize.name), "desc" = initial(prize.desc), "cost" = prize.cost, + "icon" = prize_item.icon, + "icon_state" = prize_item.icon_state, "itemID" = GLOB.global_prizes.prizes.Find(prize), - "imageID" = replacetext(replacetext("[prize.typepath]", "/obj/item/", ""), "/", "-"), )) static_data["prizes"] = prizes diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm index 1eb088800c6..e5cd8d966f0 100644 --- a/code/modules/asset_cache/asset_list.dm +++ b/code/modules/asset_cache/asset_list.dm @@ -5,18 +5,31 @@ GLOBAL_LIST_EMPTY(asset_datums) //get an assetdatum or make a new one -/proc/get_asset_datum(type) +//does NOT ensure it's filled, if you want that use get_asset_datum() +/proc/load_asset_datum(type) return GLOB.asset_datums[type] || new type() +/proc/get_asset_datum(type) + var/datum/asset/loaded_asset = GLOB.asset_datums[type] || new type() + return loaded_asset.ensure_ready() + /datum/asset var/_abstract = /datum/asset var/cached_serialized_url_mappings var/cached_serialized_url_mappings_transport_type + /// Whether or not this asset should be loaded in the "early assets" SS + var/early = FALSE + /datum/asset/New() GLOB.asset_datums[type] = src register() +/// Stub that allows us to react to something trying to get us +/// Not useful here, more handy for sprite sheets +/datum/asset/proc/ensure_ready() + return src + /datum/asset/proc/get_url_mappings() return list() @@ -26,7 +39,6 @@ GLOBAL_LIST_EMPTY(asset_datums) /datum/asset/proc/send(client) return - /// If you don't need anything complicated. /datum/asset/simple _abstract = /datum/asset/simple @@ -68,7 +80,7 @@ GLOBAL_LIST_EMPTY(asset_datums) /datum/asset/group/register() for(var/type in children) - get_asset_datum(type) + load_asset_datum(type) /datum/asset/group/send(client/C) for(var/type in children) @@ -370,6 +382,32 @@ GLOBAL_LIST_EMPTY(asset_datums) assets = sorted_assets ..() +/// A subtype to generate a JSON file from a list +/datum/asset/json + _abstract = /datum/asset/json + /// The filename, will be suffixed with ".json" + var/name + +/datum/asset/json/send(client) + return SSassets.transport.send_assets(client, "[name].json") + +/datum/asset/json/get_url_mappings() + return list( + "[name].json" = SSassets.transport.get_asset_url("[name].json"), + ) + +/datum/asset/json/register() + var/filename = "data/[name].json" + fdel(filename) + text2file(json_encode(generate()), filename) + SSassets.transport.register_asset("[name].json", fcopy_rsc(filename)) + fdel(filename) + +/// Returns the data that will be JSON encoded +/datum/asset/json/proc/generate() + SHOULD_CALL_PARENT(FALSE) + CRASH("generate() not implemented for [type]!") + /* * Get a html string that will load a html asset. * Needed because byond doesn't allow you to browse() to a url. diff --git a/code/modules/asset_cache/assets/asset_icon_ref_map.dm b/code/modules/asset_cache/assets/asset_icon_ref_map.dm new file mode 100644 index 00000000000..2f7f8463099 --- /dev/null +++ b/code/modules/asset_cache/assets/asset_icon_ref_map.dm @@ -0,0 +1,28 @@ +/// Maps icon names to ref values +/datum/asset/json/icon_ref_map + name = "icon_ref_map" + early = TRUE + +/datum/asset/json/icon_ref_map/generate() + var/list/data = list() //"icons/obj/drinks.dmi" => "[0xc000020]" + + //var/start = "0xc000000" + var/value = 0 + + while(TRUE) + value += 1 + var/ref = "\[0xc[num2text(value,6,16)]\]" + var/mystery_meat = locate(ref) + + if(isicon(mystery_meat)) + if(!isfile(mystery_meat)) // Ignore the runtime icons for now + continue + var/path = get_icon_dmi_path(mystery_meat) //Try to get the icon path + if(path) + data[path] = ref + else if(mystery_meat) + continue; //Some other non-icon resource, ogg/json/whatever + else //Out of resources end this, could also try to end this earlier as soon as runtime generated icons appear but eh + break; + + return data diff --git a/code/modules/asset_cache/assets/asset_prize_counter.dm b/code/modules/asset_cache/assets/asset_prize_counter.dm deleted file mode 100644 index d7f340033cf..00000000000 --- a/code/modules/asset_cache/assets/asset_prize_counter.dm +++ /dev/null @@ -1,13 +0,0 @@ -/datum/asset/spritesheet/prize_counter - name = "prize_counter" - -/datum/asset/spritesheet/prize_counter/create_spritesheets() - for(var/datum/prize_item/prize in GLOB.global_prizes.prizes) - var/obj/item/prize_item = prize.typepath - var/prize_icon = icon(icon = initial(prize_item.icon), icon_state = initial(prize_item.icon_state)) - var/imgid = replacetext(replacetext("[prize_item]", "/obj/item/", ""), "/", "-") - Insert(imgid, prize_icon) - -/datum/asset/spritesheet/prize_counter/ModifyInserted(icon/pre_asset) - pre_asset.Scale(64, 64) - return pre_asset diff --git a/code/modules/tgui/tgui_datum.dm b/code/modules/tgui/tgui_datum.dm index 1efece27cad..f9d7b4f97bb 100644 --- a/code/modules/tgui/tgui_datum.dm +++ b/code/modules/tgui/tgui_datum.dm @@ -103,6 +103,8 @@ /datum/tgui/proc/send_assets() var/flushqueue = window.send_asset(get_asset_datum( /datum/asset/simple/namespaced/fontawesome)) + flushqueue |= window.send_asset(get_asset_datum( + /datum/asset/json/icon_ref_map)) for(var/datum/asset/asset in src_object.ui_assets(user)) flushqueue |= window.send_asset(asset) if(flushqueue) diff --git a/paradise.dme b/paradise.dme index 21dbbac0512..96f265eb3df 100644 --- a/paradise.dme +++ b/paradise.dme @@ -343,6 +343,7 @@ #include "code\controllers\subsystem\non_firing\SSassets.dm" #include "code\controllers\subsystem\non_firing\SSatoms.dm" #include "code\controllers\subsystem\non_firing\SSchangelog.dm" +#include "code\controllers\subsystem\non_firing\SSearly_assets.dm" #include "code\controllers\subsystem\non_firing\SSholiday.dm" #include "code\controllers\subsystem\non_firing\SSlate_mapping.dm" #include "code\controllers\subsystem\non_firing\SSmapping.dm" @@ -1569,13 +1570,13 @@ #include "code\modules\asset_cache\assets\asset_cloning.dm" #include "code\modules\asset_cache\assets\asset_common.dm" #include "code\modules\asset_cache\assets\asset_emoji.dm" +#include "code\modules\asset_cache\assets\asset_icon_ref_map.dm" #include "code\modules\asset_cache\assets\asset_jquery.dm" #include "code\modules\asset_cache\assets\asset_materials.dm" #include "code\modules\asset_cache\assets\asset_nanomap.dm" #include "code\modules\asset_cache\assets\asset_orbit_icons.dm" #include "code\modules\asset_cache\assets\asset_panels.dm" #include "code\modules\asset_cache\assets\asset_paper.dm" -#include "code\modules\asset_cache\assets\asset_prize_counter.dm" #include "code\modules\asset_cache\assets\asset_rpd.dm" #include "code\modules\asset_cache\assets\asset_safe.dm" #include "code\modules\asset_cache\assets\asset_strip.dm" diff --git a/tgui/docs/component-reference.md b/tgui/docs/component-reference.md index 3865c5c1724..1960210abb4 100644 --- a/tgui/docs/component-reference.md +++ b/tgui/docs/component-reference.md @@ -570,6 +570,10 @@ A Robust button is specifically for sticking a picture in it. - `disabled: boolean` - Makes button disabled and dark red if true. Also disables onClick & onRightClick. - `selected: boolean` - Makes button selected and green if true. +- `dmFallback: any` - Optional. Adds a "stub" when loading DmIcon. +- `dmIcon: string` - Parameter `icon` of component `DmIcon`. +- `dmIconState: string` - Parameter `icon_state` of component `DmIcon`. + For proper work of `DmIcon` it is necessary that both parameters are filled in! - `fluid: boolean` - Changes the layout of the button, making it fill the entire horizontally available space. Allows the use of `title` - `imageSize: number` - Parameter responsible for the size of the image, component and standard "stubs". diff --git a/tgui/global.d.ts b/tgui/global.d.ts index a78e06440b1..7161334c2a2 100644 --- a/tgui/global.d.ts +++ b/tgui/global.d.ts @@ -26,7 +26,7 @@ namespace JSX { marquee: any; blink: any; } - Element + Element; } type TguiMessage = { @@ -169,6 +169,11 @@ type ByondType = { * Loads a script into the document. */ loadJs(url: string): void; + + /** + * Maps icons to their ref + */ + iconRefMap: Record; }; /** diff --git a/tgui/packages/tgui/components/DmIcon.tsx b/tgui/packages/tgui/components/DmIcon.tsx new file mode 100644 index 00000000000..954cbc37568 --- /dev/null +++ b/tgui/packages/tgui/components/DmIcon.tsx @@ -0,0 +1,88 @@ +import { Component, InfernoNode } from 'inferno'; +import { resolveAsset } from '../assets'; +import { fetchRetry } from '../http'; +import { BoxProps } from './Box'; +import { Image } from './Image'; + +enum Direction { + NORTH = 1, + SOUTH = 2, + EAST = 4, + WEST = 8, + NORTHEAST = NORTH | EAST, + NORTHWEST = NORTH | WEST, + SOUTHEAST = SOUTH | EAST, + SOUTHWEST = SOUTH | WEST, +} + +type Props = { + /** Required: The path of the icon */ + icon: string; + /** Required: The state of the icon */ + icon_state: string; +} & Partial<{ + /** Facing direction. See direction enum. Default is South */ + direction: Direction; + /** Fallback icon. */ + fallback: InfernoNode; + /** Frame number. Default is 1 */ + frame: number; + /** Movement state. Default is false */ + movement: any; +}> & + BoxProps; + +let refMap: Record | undefined; + +export class DmIcon extends Component { + constructor(props: Props) { + super(props); + this.state = { + iconRef: '', + }; + } + + async fetchRefMap() { + const response = await fetchRetry(resolveAsset('icon_ref_map.json')); + const data = await response.json(); + refMap = data; + this.setState({ iconRef: data[this.props.icon] }); + } + + componentDidMount() { + if (!refMap) { + this.fetchRefMap(); + } else { + this.setState({ iconRef: refMap[this.props.icon] }); + } + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.icon !== this.props.icon) { + if (refMap) { + this.setState({ iconRef: refMap[this.props.icon] }); + } else { + this.fetchRefMap(); + } + } + } + + render() { + const { + className, + direction = Direction.SOUTH, + fallback, + frame = 1, + icon_state, + movement = false, + ...rest + } = this.props; + const { iconRef } = this.state; + + const query = `${iconRef}?state=${icon_state}&dir=${direction}&movement=${!!movement}&frame=${frame}`; + + if (!iconRef) return fallback || null; + + return ; + } +} diff --git a/tgui/packages/tgui/components/Image.tsx b/tgui/packages/tgui/components/Image.tsx new file mode 100644 index 00000000000..33e3b063f69 --- /dev/null +++ b/tgui/packages/tgui/components/Image.tsx @@ -0,0 +1,64 @@ +import { Component } from 'inferno'; +import { BoxProps, computeBoxProps } from './Box'; + +type Props = Partial<{ + /** True is default, this fixes an ie thing */ + fixBlur: boolean; + /** False by default. Good if you're fetching images on UIs that do not auto update. This will attempt to fix the 'x' icon 5 times. */ + fixErrors: boolean; + /** Fill is default. */ + objectFit: 'contain' | 'cover'; +}> & + IconUnion & + BoxProps; + +// at least one of these is required +type IconUnion = + | { + className?: string; + src: string; + } + | { + className: string; + src?: string; + }; + +const maxAttempts = 5; + +/** Image component. Use this instead of Box as="img". */ +export class Image extends Component { + attempts: number = 0; + + handleError = (event) => { + const { fixErrors, src } = this.props; + if (fixErrors && this.attempts < maxAttempts) { + const imgElement = event.currentTarget; + + setTimeout(() => { + imgElement.src = `${src}?attempt=${this.attempts}`; + this.attempts++; + }, 1000); + } + }; + + render() { + const { fixBlur = true, fixErrors = false, objectFit = 'fill', src, ...rest } = this.props; + + /* Remove -ms-interpolation-mode with Byond 516. -webkit-optimize-contrast is better than pixelated */ + const computedProps = computeBoxProps({ + style: { + '-ms-interpolation-mode': `${fixBlur ? 'nearest-neighbor' : 'auto'}`, + 'image-rendering': `${fixBlur ? '-webkit-optimize-contrast' : 'auto'}`, + 'object-fit': `${objectFit}`, + }, + ...rest, + }); + + /* Use div instead img if used asset, cause img with class leaves white border on 516 */ + if (computedProps.className) { + return
; + } + + return ; + } +} diff --git a/tgui/packages/tgui/components/ImageButton.tsx b/tgui/packages/tgui/components/ImageButton.tsx index 4b1b439e102..f7b41229d30 100644 --- a/tgui/packages/tgui/components/ImageButton.tsx +++ b/tgui/packages/tgui/components/ImageButton.tsx @@ -10,6 +10,8 @@ import { InfernoNode } from 'inferno'; import { BooleanLike, classes } from 'common/react'; import { BoxProps, computeBoxProps } from './Box'; import { Icon } from './Icon'; +import { Image } from './Image'; +import { DmIcon } from './DmIcon'; import { Stack } from './Stack'; import { Tooltip } from './Tooltip'; @@ -35,6 +37,12 @@ type Props = Partial<{ color: string; /** Makes button disabled and dark red if true. Also disables onClick. */ disabled: BooleanLike; + /** Optional. Adds a "stub" when loading DmIcon. */ + dmFallback: InfernoNode; + /** Parameter `icon` of component `DmIcon`. */ + dmIcon: string | null; + /** Parameter `icon_state` of component `DmIcon`. */ + dmIconState: string | null; /** * Changes the layout of the button, making it fill the entire horizontally available space. * Allows the use of `title` @@ -69,6 +77,9 @@ export const ImageButton = (props: Props) => { className, color, disabled, + dmFallback, + dmIcon, + dmIconState, fluid, imageSize = 64, imageSrc, @@ -116,15 +127,21 @@ export const ImageButton = (props: Props) => { style={{ width: !fluid ? `calc(${imageSize}px + 0.5em + 2px)` : 'auto' }} >
- {(base64 || imageSrc) && !asset ? ( - - ) : asset ? ( - /* Not a cause assets made some shit with it on Byond 516 */ -
+ ) : dmIcon && dmIconState ? ( + ) : ( getFallback('question', false) )} diff --git a/tgui/packages/tgui/components/index.js b/tgui/packages/tgui/components/index.js index 8876792dc99..1db4cb5c898 100644 --- a/tgui/packages/tgui/components/index.js +++ b/tgui/packages/tgui/components/index.js @@ -21,6 +21,8 @@ export { DraggableControl } from './DraggableControl'; export { Dropdown } from './Dropdown'; export { Flex } from './Flex'; export { Grid } from './Grid'; +export { Image } from './Image'; +export { DmIcon } from './DmIcon'; export { Icon } from './Icon'; export { ImageButton } from './ImageButton'; export { Input } from './Input'; diff --git a/tgui/packages/tgui/http.ts b/tgui/packages/tgui/http.ts new file mode 100644 index 00000000000..8c18cf0c093 --- /dev/null +++ b/tgui/packages/tgui/http.ts @@ -0,0 +1,12 @@ +/** + * An equivalent to `fetch`, except will automatically retry. + */ +export const fetchRetry = (url: string, options?: RequestInit, retryTimer: number = 1000): Promise => { + return fetch(url, options).catch(() => { + return new Promise((resolve) => { + setTimeout(() => { + fetchRetry(url, options, retryTimer).then(resolve); + }, retryTimer); + }); + }); +}; diff --git a/tgui/packages/tgui/icons.ts b/tgui/packages/tgui/icons.ts new file mode 100644 index 00000000000..fb53a610f17 --- /dev/null +++ b/tgui/packages/tgui/icons.ts @@ -0,0 +1,14 @@ +import { resolveAsset } from './assets'; +import { fetchRetry } from './http'; +import { logger } from './logging'; + +export const loadIconRefMap = function () { + if (Object.keys(Byond.iconRefMap).length > 0) { + return; + } + + fetchRetry(resolveAsset('icon_ref_map.json')) + .then((res) => res.json()) + .then((data) => (Byond.iconRefMap = data)) + .catch((error) => logger.log(error)); +}; diff --git a/tgui/packages/tgui/index.js b/tgui/packages/tgui/index.js index d6194e81554..b0c4bb0caf9 100644 --- a/tgui/packages/tgui/index.js +++ b/tgui/packages/tgui/index.js @@ -25,6 +25,7 @@ import './styles/themes/noticeboard.scss'; import { perf } from 'common/perf'; import { setupHotReloading } from 'tgui-dev-server/link/client.cjs'; import { setupHotKeys } from './hotkeys'; +import { loadIconRefMap } from './icons'; import { captureExternalLinks } from './links'; import { createRenderer } from './renderer'; import { configureStore, StoreProvider } from './store'; @@ -36,6 +37,8 @@ perf.mark('init'); const store = configureStore(); const renderApp = createRenderer(() => { + loadIconRefMap(); + const { getRoutedComponent } = require('./routes'); const Component = getRoutedComponent(store); return ( diff --git a/tgui/packages/tgui/interfaces/PrizeCounter.tsx b/tgui/packages/tgui/interfaces/PrizeCounter.tsx index 5cad855dcb7..c0af6d20f11 100644 --- a/tgui/packages/tgui/interfaces/PrizeCounter.tsx +++ b/tgui/packages/tgui/interfaces/PrizeCounter.tsx @@ -1,4 +1,3 @@ -import { classes } from 'common/react'; import { useBackend, useLocalState } from '../backend'; import { Button, Section, Stack, ImageButton, Input, Icon } from '../components'; import { Window } from '../layouts'; @@ -6,9 +5,10 @@ import { Window } from '../layouts'; type Prize = { name: string; desc: string; + icon: string; + icon_state: string; cost: number; itemID: number; - imageID: string; }; type PrizeData = { @@ -45,14 +45,9 @@ export const PrizeCounter = (props, context) => { )} -