From d773f4db2ff5dce051510dae6674e34edf5d27bf Mon Sep 17 00:00:00 2001 From: Aylong <69762909+AyIong@users.noreply.github.com> Date: Thu, 30 Jan 2025 18:00:12 +0200 Subject: [PATCH] Supply Consoles restyle & order content preview (#89198) ## About The Pull Request In a nutshell, everything will be visible in the `Demo` section. But if you want some details... - Filling of static_data was divided into procs, for better understanding and possibility to use the same actions in two order consoles - Added an image of the first item in crate at each order (almost) - Added the ability to see what is inside an order, the information is not always accurate due to the nature of some orders such as random hats - Slightly remaked layout, making everything look more cohesive and eliminating the empty space at the bottom of the screen - When buying anonymously, the original price does not disappear, but is crossed out, and the new price is written below it ## Demo
Screenshots Supply Console | Before | After | | - | - | | ![image](https://github.com/user-attachments/assets/6067fc76-de5a-4d32-929a-239074514767) | ![image](https://github.com/user-attachments/assets/81b3ab2a-f6d6-4f7a-8fff-0d5f459bcef9) | Express Console | Before | After | | - | - | | ![image](https://github.com/user-attachments/assets/934554e5-ec42-422f-b6ce-f66a137bd280) | ![image](https://github.com/user-attachments/assets/0d00c364-eece-4aa4-8f7d-f40ab7b9bb27) | Other | Content preview | Express login | | - | - | | ![image](https://github.com/user-attachments/assets/62b3374e-5f46-4113-9da9-f481aa25739c) | ![image](https://github.com/user-attachments/assets/8468f191-a5da-4de4-86f1-e0aca10256b3) |
Video https://github.com/user-attachments/assets/c7e16206-39f5-413c-9eea-c0284350c4a3
## Why It's Good For The Game More convenience plus a bit prettier. The ability to view the contents of an order, even if some madman didn't describe its contents in the description (those who did this are really crazy) Section with tabs scrolls on both 515 and 516 And little cleaner data receiving code ## Changelog :cl: qol: Supply console got little redesign, and ability to see what can be inside a order. code: Supply console and express supply console, now use a unified method of acquiring data /:cl: --------- Co-authored-by: Ivory Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com> --- code/modules/cargo/expressconsole.dm | 33 +- code/modules/cargo/orderconsole.dm | 92 +++- .../tgui/interfaces/Cargo/CargoCart.tsx | 52 ++- .../tgui/interfaces/Cargo/CargoCatalog.tsx | 398 +++++++++++------- .../tgui/interfaces/Cargo/CargoRequests.tsx | 17 +- .../tgui/interfaces/Cargo/CargoStatus.tsx | 2 +- tgui/packages/tgui/interfaces/Cargo/index.tsx | 102 +++-- tgui/packages/tgui/interfaces/Cargo/types.ts | 10 + .../packages/tgui/interfaces/CargoExpress.tsx | 102 +++-- 9 files changed, 494 insertions(+), 314 deletions(-) diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm index 4070301bfbe..86a4d4f3a4f 100644 --- a/code/modules/cargo/expressconsole.dm +++ b/code/modules/cargo/expressconsole.dm @@ -92,26 +92,17 @@ /obj/machinery/computer/cargo/express/proc/packin_up(forced = FALSE) // oh shit, I'm sorry meme_pack_data = list() // sorry for what? - if (!forced && !SSshuttle.initialized) // Subsystem is still sleeping, add ourselves to its buffer and abort - SSshuttle.express_consoles += src - return - for(var/pack in SSshuttle.supply_packs) // our quartermaster taught us not to be ashamed of our supply packs - var/datum/supply_pack/P = SSshuttle.supply_packs[pack] // specially since they're such a good price and all - if(!meme_pack_data[P.group]) // yeah, I see that, your quartermaster gave you good advice - meme_pack_data[P.group] = list( // it gets cheaper when I return it - "name" = P.group, // mmhm - "packs" = list() // sometimes, I return it so much, I rip the manifest - ) // see, my quartermaster taught me a few things too - if((P.hidden) || (P.special)) // like, how not to rip the manifest - continue// by using someone else's crate - if(P.contraband && !contraband) // will you show me? - continue // i'd be right happy to - meme_pack_data[P.group]["packs"] += list(list( - "name" = P.name, - "cost" = P.get_cost() * get_discount(), - "id" = pack, - "desc" = P.desc || P.name // If there is a description, use it. Otherwise use the pack's name. - )) + if(!forced && !SSshuttle.initialized) // our quartermaster taught us not to be ashamed of our supply packs + SSshuttle.express_consoles += src // specially since they're such a good price and all + return // yeah, I see that, your quartermaster gave you good advice + // it gets cheaper when I return it + for(var/pack_id in SSshuttle.supply_packs) // mmhm + var/datum/supply_pack/pack = SSshuttle.supply_packs[pack_id] // sometimes, I return it so much, I rip the manifest + if(!meme_pack_data[pack.group]) // see, my quartermaster taught me a few things too + meme_pack_data[pack.group] = list( // like, how not to rip the manifest + "name" = pack.group, // by using someone else's crate + "packs" = get_packs_data(pack.group, express = TRUE), // will you show me? + ) // i'd be right happy to /obj/machinery/computer/cargo/express/ui_data(mob/user) var/canBeacon = beacon && (isturf(beacon.loc) || ismob(beacon.loc))//is the beacon in a valid location? @@ -146,7 +137,7 @@ data["supplies"] = meme_pack_data return data -/obj/machinery/computer/cargo/express/proc/get_discount() +/obj/machinery/computer/cargo/express/get_discount() return (obj_flags & EMAGGED) ? EXPRESS_EMAG_DISCOUNT : 1 /obj/machinery/computer/cargo/express/ui_act(action, params, datum/tgui/ui) diff --git a/code/modules/cargo/orderconsole.dm b/code/modules/cargo/orderconsole.dm index 87a085707c0..409dc197e43 100644 --- a/code/modules/cargo/orderconsole.dm +++ b/code/modules/cargo/orderconsole.dm @@ -95,9 +95,9 @@ var/list/data = list() data["department"] = "Cargo" // Hardcoded here, for customization in budgetordering.dm AKA NT IRN data["location"] = SSshuttle.supply.getStatusText() - var/datum/bank_account/D = SSeconomy.get_dep_account(cargo_account) - if(D) - data["points"] = D.account_balance + var/datum/bank_account/bank = SSeconomy.get_dep_account(cargo_account) + if(bank) + data["points"] = bank.account_balance data["grocery"] = SSshuttle.chef_groceries.len data["away"] = SSshuttle.supply.getDockedId() == docking_away data["self_paid"] = self_paid @@ -162,26 +162,78 @@ var/list/data = list() data["max_order"] = CARGO_MAX_ORDER data["supplies"] = list() - for(var/pack in SSshuttle.supply_packs) - var/datum/supply_pack/P = SSshuttle.supply_packs[pack] - if(!data["supplies"][P.group]) - data["supplies"][P.group] = list( - "name" = P.group, - "packs" = list() + + for(var/pack_id in SSshuttle.supply_packs) + var/datum/supply_pack/pack = SSshuttle.supply_packs[pack_id] + if(!data["supplies"][pack.group]) + data["supplies"][pack.group] = list( + "name" = pack.group, + "packs" = get_packs_data(pack.group), ) - if((P.hidden && !(obj_flags & EMAGGED)) || (P.contraband && !contraband) || (P.special && !P.special_enabled) || P.drop_pod_only) - continue - data["supplies"][P.group]["packs"] += list(list( - "name" = P.name, - "cost" = P.get_cost(), - "id" = pack, - "desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name. - "goody" = P.goody, - "access" = P.access, - "contraband" = P.contraband, - )) + return data +/** + * returns a list of supply packs for a certain group + * * group - the group of packs to return + * * express - if this is an express console + */ +/obj/machinery/computer/cargo/proc/get_packs_data(group, express = FALSE) + var/list/packs = list() + for(var/pack_id in SSshuttle.supply_packs) + var/datum/supply_pack/pack = SSshuttle.supply_packs[pack_id] + if(pack.group != group) + continue + + // Express console packs check + if(express && (pack.hidden || pack.special)) + continue + + if(!express && ((pack.hidden && !(obj_flags & EMAGGED)) || (pack.special && !pack.special_enabled) || pack.drop_pod_only)) + continue + + if(pack.contraband && !contraband) + continue + + var/obj/item/first_item = length(pack.contains) > 0 ? pack.contains[1] : null + packs += list(list( + "name" = pack.name, + "cost" = pack.get_cost() * get_discount(), + "id" = pack_id, + "desc" = pack.desc || pack.name, // If there is a description, use it. Otherwise use the pack's name. + "first_item_icon" = first_item?.icon, + "first_item_icon_state" = first_item?.icon_state, + "goody" = pack.goody, + "access" = pack.access, + "contraband" = pack.contraband, + "contains" = get_pack_contains(pack), + )) + + return packs + +/** + * returns a list of the contents of a supply pack + * * pack - the pack to get the contents of + */ +/obj/machinery/computer/cargo/proc/get_pack_contains(datum/supply_pack/pack) + var/list/contains = list() + for(var/obj/item/item as anything in pack.contains) + contains += list(list( + "name" = item.name, + "icon" = item.greyscale_config ? null : item.icon, + "icon_state" = item.greyscale_config ? null : item.icon_state, + "amount" = pack.contains[item] + )) + + return contains + +/** + * returns the discount multiplier applied to all supply packs, + * the discount is calculated as follows: pack_cost * get_discount() + */ +/obj/machinery/computer/cargo/proc/get_discount() + return 1 + /** * adds an supply pack to the checkout cart * * user - the mobe doing this order diff --git a/tgui/packages/tgui/interfaces/Cargo/CargoCart.tsx b/tgui/packages/tgui/interfaces/Cargo/CargoCart.tsx index 41c05acdb05..af3fc11981e 100644 --- a/tgui/packages/tgui/interfaces/Cargo/CargoCart.tsx +++ b/tgui/packages/tgui/interfaces/Cargo/CargoCart.tsx @@ -11,7 +11,6 @@ import { import { formatMoney } from 'tgui-core/format'; import { useBackend } from '../../backend'; -import { CargoCartButtons } from './CargoButtons'; import { CargoData } from './types'; export function CargoCart(props) { @@ -23,16 +22,18 @@ export function CargoCart(props) { return ( -
}> +
{cart.length > 0 && !!can_send && ( - -
+ +
- {!sendable && } + {!sendable && ( + + )} - - ) - } - > + <> + {showContents && ( + + )} - - + +
+ +
- - + +
+ +
-
+ ); } @@ -101,143 +93,245 @@ type CatalogTabsProps = { setSearchText: (text: string) => void; }; -function CatalogTabs(props: CatalogTabsProps) { +function CatalogTabs(props: CatalogTabsProps & Props) { + const { act, data } = useBackend(); const { activeSupplyName, categories, searchText, setActiveSupplyName, setSearchText, + express, } = props; + const { self_paid } = data; const sorted = sortBy(categories, (supply) => supply.name); return ( - - - - - - - - { - if (value === searchText) { - return; - } + + + { + if (value === searchText) { + return; + } - if (value.length) { - // Start showing results - setActiveSupplyName('search_results'); - } else if (activeSupplyName === 'search_results') { - // return to normal category - setActiveSupplyName(sorted[0]?.name); - } - setSearchText(value); - }} - /> - - - - - {sorted.map((supply) => ( - { - setActiveSupplyName(supply.name); - setSearchText(''); + if (value.length) { + // Start showing results + setActiveSupplyName('search_results'); + } else if (activeSupplyName === 'search_results') { + // return to normal category + setActiveSupplyName(sorted[0]?.name); + } + setSearchText(value); }} - > -
- {supply.name} - {supply.packs.length} -
-
- ))} -
+ /> +
+ + + + + {sorted.map((supply) => ( + { + setActiveSupplyName(supply.name); + setSearchText(''); + }} + > + + {supply.name} + {supply.packs.length} + + + ))} + + + + {!express && ( + + )} + + ); } type CatalogListProps = { packs: SupplyCategory['packs']; + openContents: Dispatch>; }; function CatalogList(props: CatalogListProps) { const { act, data } = useBackend(); const { amount_by_name = {}, max_order, self_paid, app_cost } = data; - const { packs = [] } = props; + const { packs = [], openContents } = props; return ( -
- - {packs.map((pack) => { - let color = ''; - const digits = Math.floor(Math.log10(pack.cost) + 1); - if (self_paid) { - color = 'caution'; - } else if (digits >= 5 && digits <= 6) { - color = 'yellow'; - } else if (digits > 6) { - color = 'bad'; - } + <> + {packs.map((pack) => { + let color = ''; + const digits = Math.floor(Math.log10(pack.cost) + 1); + if (self_paid) { + color = 'yellow'; + } else if (digits >= 5 && digits <= 6) { + color = 'orange'; + } else if (digits > 6) { + color = 'bad'; + } - return ( - - {pack.name} - - {!!pack.small_item && ( - - - - )} - - - {!!pack.access && ( - - - - )} - {!!pack.contraband && ( - - - - )} - - - - - - ); - })} -
-
+ const privateBuy = (self_paid && !pack.goody) || app_cost; + const tooltipIcon = (content: string, icon: string, color: string) => ( + + + + + + ); + + return ( + = max_order} + buttonsAlt={ + - ) - } - > +
{requests.length === 0 && No Requests} {requests.length > 0 && ( diff --git a/tgui/packages/tgui/interfaces/Cargo/CargoStatus.tsx b/tgui/packages/tgui/interfaces/Cargo/CargoStatus.tsx index 649f4979af3..c1fbbb835f1 100644 --- a/tgui/packages/tgui/interfaces/Cargo/CargoStatus.tsx +++ b/tgui/packages/tgui/interfaces/Cargo/CargoStatus.tsx @@ -30,7 +30,7 @@ export function CargoStatus(props) {
+ formatMoney(value)} diff --git a/tgui/packages/tgui/interfaces/Cargo/index.tsx b/tgui/packages/tgui/interfaces/Cargo/index.tsx index 22fb5305b6d..94d90dd16f1 100644 --- a/tgui/packages/tgui/interfaces/Cargo/index.tsx +++ b/tgui/packages/tgui/interfaces/Cargo/index.tsx @@ -1,7 +1,9 @@ -import { Stack, Tabs } from 'tgui-core/components'; +import { Button, Section, Stack, Tabs } from 'tgui-core/components'; +import { toTitleCase } from 'tgui-core/string'; import { useBackend, useSharedState } from '../../backend'; import { Window } from '../../layouts'; +import { CargoCartButtons } from './CargoButtons'; import { CargoCart } from './CargoCart'; import { CargoCatalog } from './CargoCatalog'; import { CargoHelp } from './CargoHelp'; @@ -11,7 +13,7 @@ import { CargoData } from './types'; enum TAB { Catalog = 'catalog', - Requests = 'requests', + Requests = 'active requests', Cart = 'cart', Help = 'help', } @@ -27,10 +29,8 @@ export function Cargo(props) { } export function CargoContent(props) { - const { data } = useBackend(); - + const { act, data } = useBackend(); const { cart = [], requests = [], requestonly } = data; - const [tab, setTab] = useSharedState('cargotab', TAB.Catalog); let amount = 0; @@ -44,44 +44,66 @@ export function CargoContent(props) { - - setTab(TAB.Catalog)} - > - Catalog - - 0 && 'yellow'} - selected={tab === TAB.Requests} - onClick={() => setTab(TAB.Requests)} - > - Requests ({requests.length}) - - {!requestonly && ( +
- 0 && 'yellow'} - selected={tab === TAB.Cart} - onClick={() => setTab(TAB.Cart)} - > - Checkout ({amount}) - - setTab(TAB.Help)} - > - Help - + {tab === TAB.Requests && !requestonly && ( + + )} + {(tab === TAB.Catalog || tab === TAB.Cart) && ( + + )} - )} - + } + > + + setTab(TAB.Catalog)} + > + Catalog + + 0 && 'yellow' + } + selected={tab === TAB.Requests} + onClick={() => setTab(TAB.Requests)} + > + Requests ({requests.length}) + + {!requestonly && ( + <> + 0 && 'yellow'} + selected={tab === TAB.Cart} + onClick={() => setTab(TAB.Cart)} + > + Checkout ({amount}) + + setTab(TAB.Help)} + > + Help + + + )} + +
- + {tab === TAB.Catalog && } {tab === TAB.Requests && } {tab === TAB.Cart && } diff --git a/tgui/packages/tgui/interfaces/Cargo/types.ts b/tgui/packages/tgui/interfaces/Cargo/types.ts index f374117cbdf..1761fdf2a1e 100644 --- a/tgui/packages/tgui/interfaces/Cargo/types.ts +++ b/tgui/packages/tgui/interfaces/Cargo/types.ts @@ -31,11 +31,21 @@ export type Supply = { access: BooleanLike; cost: number; desc: string; + first_item_icon: string | null; + first_item_icon_state: string | null; goody: BooleanLike; id: string; name: string; small_item: BooleanLike; contraband: BooleanLike; + contains: SupplyItem[]; +}; + +type SupplyItem = { + name: string; + icon: string | null; + icon_state: string | null; + amount: number; }; type CartEntry = { diff --git a/tgui/packages/tgui/interfaces/CargoExpress.tsx b/tgui/packages/tgui/interfaces/CargoExpress.tsx index 4d911e0f25f..be2360a059f 100644 --- a/tgui/packages/tgui/interfaces/CargoExpress.tsx +++ b/tgui/packages/tgui/interfaces/CargoExpress.tsx @@ -2,15 +2,17 @@ import { AnimatedNumber, Box, Button, + Icon, LabeledList, + NoticeBox, Section, + Stack, } from 'tgui-core/components'; import { BooleanLike } from 'tgui-core/react'; import { useBackend } from '../backend'; import { Window } from '../layouts'; import { CargoCatalog } from './Cargo/CargoCatalog'; -import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; type Data = { locked: BooleanLike; @@ -18,21 +20,41 @@ type Data = { using_beacon: BooleanLike; beaconzone: string; beaconName: string; + beaconError: BooleanLike; canBuyBeacon: BooleanLike; hasBeacon: BooleanLike; + canBeacon: BooleanLike; printMsg: string; message: string; }; export function CargoExpress(props) { const { data } = useBackend(); - const { locked } = data; + const { beaconError, canBeacon, message, locked } = data; return ( - - - {!locked && } + + {locked ? ( +
+ + + +
+ {`Swipe a Cargo Technician-level ID card to unlock this interface.`} +
+
+
+ ) : ( + + + {message} + + + + + + )}
); @@ -42,7 +64,6 @@ function CargoExpressContent(props) { const { act, data } = useBackend(); const { hasBeacon, - message, points, using_beacon, beaconzone, @@ -52,36 +73,43 @@ function CargoExpressContent(props) { } = data; return ( - <> -
- - {' credits'} - - } - > - - - - - - - {message} - -
- - + + +
+ + {' credits'} + + } + > + + + + + + + +
+
+ + + +
); }