mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-18 18:44:48 +01:00
Port DmIcon & Image components from TG (#26623)
* Port DmIcon & Image components from TG * Documentation * Uh oh * I hate it * I will bang you
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -36,4 +36,4 @@ SUBSYSTEM_DEF(assets)
|
||||
if(initial(asset_to_load._abstract))
|
||||
continue
|
||||
|
||||
get_asset_datum(type)
|
||||
load_asset_datum(type)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
+2
-1
@@ -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"
|
||||
|
||||
@@ -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".
|
||||
|
||||
Vendored
+6
-1
@@ -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<string, string>;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<string, string> | undefined;
|
||||
|
||||
export class DmIcon extends Component<Props, { iconRef: string }> {
|
||||
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 <Image fixErrors src={query} {...rest} />;
|
||||
}
|
||||
}
|
||||
@@ -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<Props> {
|
||||
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 <div onError={this.handleError} {...computedProps} />;
|
||||
}
|
||||
|
||||
return <img onError={this.handleError} src={src} {...computedProps} />;
|
||||
}
|
||||
}
|
||||
@@ -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' }}
|
||||
>
|
||||
<div className={classes(['image'])}>
|
||||
{(base64 || imageSrc) && !asset ? (
|
||||
<img
|
||||
{base64 || asset || imageSrc ? (
|
||||
<Image
|
||||
className={classes((!base64 && !imageSrc && asset) || [])}
|
||||
src={base64 ? `data:image/jpeg;base64,${base64}` : imageSrc}
|
||||
height={`${imageSize}px`}
|
||||
width={`${imageSize}px`}
|
||||
/>
|
||||
) : asset ? (
|
||||
/* Not a <img> cause assets made some shit with it on Byond 516 */
|
||||
<div className={classes(asset)} />
|
||||
) : dmIcon && dmIconState ? (
|
||||
<DmIcon
|
||||
icon={dmIcon}
|
||||
icon_state={dmIconState}
|
||||
fallback={dmFallback ? dmFallback : getFallback('spinner', true)}
|
||||
height={`${imageSize}px`}
|
||||
width={`${imageSize}px`}
|
||||
/>
|
||||
) : (
|
||||
getFallback('question', false)
|
||||
)}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* An equivalent to `fetch`, except will automatically retry.
|
||||
*/
|
||||
export const fetchRetry = (url: string, options?: RequestInit, retryTimer: number = 1000): Promise<Response> => {
|
||||
return fetch(url, options).catch(() => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
fetchRetry(url, options, retryTimer).then(resolve);
|
||||
}, retryTimer);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -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));
|
||||
};
|
||||
@@ -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 (
|
||||
|
||||
@@ -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) => {
|
||||
</Stack.Item>
|
||||
)}
|
||||
<Stack.Item>
|
||||
<Button
|
||||
fluid
|
||||
iconRight
|
||||
icon="ticket"
|
||||
disabled={!tickets}
|
||||
content={<>Tickets: {<b>{tickets}</b>}</>}
|
||||
onClick={() => act('eject')}
|
||||
/>
|
||||
<Button fluid iconRight icon="ticket" disabled={!tickets} onClick={() => act('eject')}>
|
||||
Tickets: <b>{tickets}</b>
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
@@ -72,8 +67,9 @@ export const PrizeCounter = (props, context) => {
|
||||
<ImageButton
|
||||
fluid
|
||||
key={prize.name}
|
||||
asset={['prize_counter64x64', prize.imageID]}
|
||||
title={prize.name}
|
||||
dmIcon={prize.icon}
|
||||
dmIconState={prize.icon_state}
|
||||
buttonsAlt
|
||||
buttons={
|
||||
<Button
|
||||
|
||||
@@ -200,7 +200,7 @@ const RecipeBox = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { amount } = data;
|
||||
const { title, recipe } = props;
|
||||
const { result_amount, required_amount, max_result_amount, uid, image } = recipe;
|
||||
const { result_amount, required_amount, max_result_amount, uid, icon, icon_state, image } = recipe;
|
||||
|
||||
const resAmountLabel = result_amount > 1 ? `${result_amount}x ` : '';
|
||||
const sheetSuffix = required_amount > 1 ? 's' : '';
|
||||
@@ -212,7 +212,9 @@ const RecipeBox = (props, context) => {
|
||||
return (
|
||||
<ImageButton
|
||||
fluid
|
||||
base64={image}
|
||||
base64={image} /* Use base64 image if we have it. DmIcon cannot paint grayscale images yet */
|
||||
dmIcon={icon}
|
||||
dmIconState={icon_state}
|
||||
imageSize={32}
|
||||
disabled={!max_possible_multiplier}
|
||||
tooltip={tooltipContent}
|
||||
|
||||
@@ -125,11 +125,6 @@ $bg-map: colors.$bg-map !default;
|
||||
line-height: 0;
|
||||
padding: 0.25em;
|
||||
border-radius: 0.33em;
|
||||
|
||||
* {
|
||||
image-rendering: pixelated;
|
||||
-ms-interpolation-mode: nearest-neighbor; // Remove with 516
|
||||
}
|
||||
}
|
||||
|
||||
.buttonsContainer {
|
||||
|
||||
+111
-111
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+76
-76
File diff suppressed because one or more lines are too long
@@ -369,6 +369,9 @@
|
||||
Byond.loadCss = function (url, sync) {
|
||||
loadAsset({ url: url, sync: sync, type: 'css' });
|
||||
};
|
||||
|
||||
// Icon cache
|
||||
Byond.iconRefMap = {};
|
||||
})();
|
||||
|
||||
// Error handling
|
||||
|
||||
Reference in New Issue
Block a user