New Ore Silo UI (#80975)

![UI](https://github.com/tgstation/tgstation/assets/137328283/8a6f3645-32db-4700-8884-985cf7b3dd40)

## About The Pull Request

Made a new UI for Ore Silo, tweaked `MaterialAccessBar` component a bit
to properly display pop-ups over “wrapped” elements and actually wrap.

## Why It's Good For The Game

Ore Silo is one of the last machines that use browser on TG, we're
almost there!

Also, it helps @JohnFulpWillard to bring
https://hackmd.io/XLt5MoRvRxuhFbwtk4VAUA HackMD document to a closure.

## Changelog
🆑
refactor: Refactored Ore Silo Ui.
/🆑
This commit is contained in:
Interception&?
2024-01-17 12:42:24 +03:00
committed by GitHub
parent 380e2661c2
commit 9c2df71dc2
4 changed files with 274 additions and 93 deletions
@@ -74,7 +74,6 @@ handles linking back and forth.
if (silo)
silo.ore_connected_machines -= src
silo.holds -= src
silo.updateUsrDialog()
silo = null
mat_container = null
return ..()
@@ -170,7 +169,6 @@ handles linking back and forth.
if (silo)
silo.ore_connected_machines -= src
silo.holds -= src
silo.updateUsrDialog()
else if (mat_container)
//transfer all mats to silo. whatever cannot be transfered is dumped out as sheets
if(mat_container.total_amount())
@@ -183,7 +181,6 @@ handles linking back and forth.
qdel(mat_container)
silo = new_silo
silo.ore_connected_machines += src
silo.updateUsrDialog()
mat_container = new_container
if(!(mat_container_flags & MATCONTAINER_NO_INSERT))
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, TYPE_PROC_REF(/datum/component/remote_materials, SiloAttackBy))
+65 -86
View File
@@ -8,7 +8,7 @@ GLOBAL_LIST_EMPTY(silo_access_logs)
icon_state = "silo"
density = TRUE
circuit = /obj/item/circuitboard/machine/ore_silo
interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN|INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_OPEN_SILICON|INTERACT_MACHINE_SET_MACHINE
interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN|INTERACT_MACHINE_ALLOW_SILICON|INTERACT_MACHINE_OPEN_SILICON
/// The machine UI's page of logs showing ore history.
var/log_page = 1
@@ -77,93 +77,79 @@ GLOBAL_LIST_EMPTY(silo_access_logs)
/obj/machinery/ore_silo/crowbar_act(mob/living/user, obj/item/tool)
return default_deconstruction_crowbar(tool)
/obj/machinery/ore_silo/ui_interact(mob/user)
user.set_machine(src)
var/datum/browser/popup = new(user, "ore_silo", null, 600, 550)
popup.set_content(generate_ui())
popup.open()
/obj/machinery/ore_silo/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/spritesheet/sheetmaterials)
)
/obj/machinery/ore_silo/proc/generate_ui()
var/list/ui = list("<head><title>Ore Silo</title></head><body><div class='statusDisplay'><h2>Stored Material:</h2>")
var/any = FALSE
for(var/M in materials.materials)
var/datum/material/mat = M
var/amount = materials.materials[M]
var/sheets = round(amount) / SHEET_MATERIAL_AMOUNT
var/ref = REF(M)
if (sheets)
if (sheets >= 1)
ui += "<a href='?src=[REF(src)];ejectsheet=[ref];eject_amt=1'>Eject</a>"
else
ui += "<span class='linkOff'>Eject</span>"
if (sheets >= 20)
ui += "<a href='?src=[REF(src)];ejectsheet=[ref];eject_amt=20'>20x</a>"
else
ui += "<span class='linkOff'>20x</span>"
ui += "<b>[mat.name]</b>: [sheets] sheets<br>"
any = TRUE
if(!any)
ui += "Nothing!"
/obj/machinery/ore_silo/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "OreSilo")
ui.open()
ui += "</div><div class='statusDisplay'><h2>Connected Machines:</h2>"
for(var/datum/component/remote_materials/mats as anything in ore_connected_machines)
var/atom/parent = mats.parent
ui += "<a href='?src=[REF(src)];remove=[REF(mats)]'>Remove</a>"
ui += "<a href='?src=[REF(src)];hold=[REF(mats)]'>[holds[mats] ? "Allow" : "Hold"]</a>"
ui += " <b>[parent.name]</b> in [get_area_name(parent, TRUE)]<br>"
if(!ore_connected_machines.len)
ui += "Nothing!"
/obj/machinery/ore_silo/ui_static_data(mob/user)
return materials.ui_static_data()
ui += "</div><div class='statusDisplay'><h2>Access Logs:</h2>"
var/list/logs = GLOB.silo_access_logs[REF(src)]
var/len = LAZYLEN(logs)
var/num_pages = 1 + round((len - 1) / 30)
var/page = clamp(log_page, 1, num_pages)
if(num_pages > 1)
for(var/i in 1 to num_pages)
if(i == page)
ui += "<span class='linkOff'>[i]</span>"
else
ui += "<a href='?src=[REF(src)];page=[i]'>[i]</a>"
/obj/machinery/ore_silo/ui_data(mob/user)
var/list/data = list(
"materials" = materials.ui_data()
)
ui += "<ol>"
any = FALSE
for(var/i in (page - 1) * 30 + 1 to min(page * 30, len))
var/datum/ore_silo_log/entry = logs[i]
ui += "<li value=[len + 1 - i]>[entry.formatted]</li>"
any = TRUE
if (!any)
ui += "<li>Nothing!</li>"
var/list/connected_data
for(var/datum/component/remote_materials/remote as anything in ore_connected_machines)
var/atom/parent = remote.parent
var/icon/parent_icon = icon(initial(parent.icon), initial(parent.icon_state), frame = 1)
var/list/remote_data = list(
"ref" = REF(remote),
"icon" = icon2base64(parent_icon),
"name" = parent.name,
"onHold" = holds[remote] ? TRUE : FALSE,
"location" = get_area_name(parent, TRUE)
)
LAZYADD(connected_data, list(remote_data))
LAZYSET(data, "machines", connected_data)
ui += "</ol></div>"
return ui.Join()
var/list/logs_data
for(var/datum/ore_silo_log/entry as anything in GLOB.silo_access_logs[REF(src)])
var/list/log_data = list(
"rawMaterials" = entry.get_raw_materials(""),
"machineName" = entry.machine_name,
"areaName" = entry.area_name,
"action" = entry.action,
"amount" = entry.amount,
"time" = entry.timestamp,
"noun" = entry.noun
)
LAZYADD(logs_data, list(log_data))
LAZYSET(data, "logs", logs_data)
/obj/machinery/ore_silo/Topic(href, href_list)
if(..())
return data
/obj/machinery/ore_silo/ui_act(action, list/params)
. = ..()
if(.)
return
add_fingerprint(usr)
usr.set_machine(src)
if(href_list["remove"])
var/datum/component/remote_materials/mats = locate(href_list["remove"]) in ore_connected_machines
if (mats)
mats.disconnect_from(src)
updateUsrDialog()
switch(action)
if("remove")
var/datum/component/remote_materials/remote = locate(params["ref"]) in ore_connected_machines
remote?.disconnect_from(src)
return TRUE
if("hold")
var/datum/component/remote_materials/remote = locate(params["ref"]) in ore_connected_machines
remote?.toggle_holding()
return TRUE
if("eject")
var/datum/material/ejecting = locate(params["ref"])
var/amount = text2num(params["amount"])
if(!isnum(amount) || !istype(ejecting))
return TRUE
materials.retrieve_sheets(amount, ejecting, drop_location())
return TRUE
else if(href_list["hold"])
var/datum/component/remote_materials/mats = locate(href_list["hold"]) in ore_connected_machines
mats.toggle_holding()
updateUsrDialog()
return TRUE
else if(href_list["ejectsheet"])
var/datum/material/eject_sheet = locate(href_list["ejectsheet"])
var/amount = text2num(href_list["eject_amt"])
materials.retrieve_sheets(amount, eject_sheet, drop_location())
return TRUE
else if(href_list["page"])
log_page = text2num(href_list["page"]) || 1
updateUsrDialog()
return TRUE
/obj/machinery/ore_silo/multitool_act(mob/living/user, obj/item/multitool/I)
. = ..()
@@ -192,7 +178,6 @@ GLOBAL_LIST_EMPTY(silo_access_logs)
else if(!logs[1].merge(entry))
logs.Insert(1, entry)
updateUsrDialog()
flick("silo_active", src)
/obj/machinery/ore_silo/examine(mob/user)
@@ -219,7 +204,6 @@ GLOBAL_LIST_EMPTY(silo_access_logs)
amount = _amount
noun = _noun
materials = mats.Copy()
format()
var/list/data = list(
"machine_name" = machine_name,
"area_name" = AREACOORD(M),
@@ -245,13 +229,8 @@ GLOBAL_LIST_EMPTY(silo_access_logs)
amount += other.amount
for(var/each in other.materials)
materials[each] += other.materials[each]
format()
return TRUE
/datum/ore_silo_log/proc/format()
name = "[machine_name]: [action] [amount]x [noun]"
formatted = "([timestamp]) <b>[machine_name]</b> in [area_name]<br>[action] [abs(amount)]x [noun]<br> [get_raw_materials("")]"
/datum/ore_silo_log/proc/get_raw_materials(separator)
var/list/msg = list()
for(var/key in materials)
@@ -2,7 +2,7 @@ import { sortBy } from 'common/collections';
import { classes } from 'common/react';
import { useState } from 'react';
import { AnimatedNumber, Button, Flex, Stack } from '../../components';
import { AnimatedNumber, Button, Flex } from '../../components';
import { formatSiUnit } from '../../format';
import { MaterialIcon } from './MaterialIcon';
import { Material } from './Types';
@@ -57,7 +57,7 @@ export const MaterialAccessBar = (props: MaterialAccessBarProps) => {
<Flex wrap>
{sortBy((m: Material) => MATERIAL_RARITY[m.name])(availableMaterials).map(
(material) => (
<Flex.Item key={material.name} grow={1}>
<Flex.Item grow basis={4.5} key={material.name}>
<MaterialCounter
material={material}
SHEET_MATERIAL_AMOUNT={SHEET_MATERIAL_AMOUNT}
@@ -95,7 +95,7 @@ const MaterialCounter = (props: MaterialCounterProps) => {
sheets < 1 && 'MaterialDock--disabled',
])}
>
<Stack vertical direction="column-reverse">
<Flex direction="column-reverse">
<Flex
direction="column"
textAlign="center"
@@ -135,7 +135,7 @@ const MaterialCounter = (props: MaterialCounterProps) => {
</Flex>
</div>
)}
</Stack>
</Flex>
</div>
);
};
+205
View File
@@ -0,0 +1,205 @@
import { classes } from 'common/react';
import { capitalize } from 'common/string';
import { useState } from 'react';
import { useBackend } from '../backend';
import {
Box,
Icon,
Image,
LabeledList,
NoticeBox,
Section,
Stack,
Tabs,
Tooltip,
} from '../components';
import { Window } from '../layouts';
import { MaterialAccessBar } from './Fabrication/MaterialAccessBar';
import { Material } from './Fabrication/Types';
type Machine = {
ref: string;
name: string;
icon: string;
onHold: boolean;
location: string;
};
type Log = {
rawMaterials: string;
machineName: string;
areaName: string;
action: string;
amount: number;
time: string;
noun: string;
};
type OreSiloData = {
SHEET_MATERIAL_AMOUNT: number;
materials: Material[];
machines: Machine[];
logs: Log[];
};
export const OreSilo = (props: any, context: any) => {
const { act, data } = useBackend<OreSiloData>();
const { SHEET_MATERIAL_AMOUNT, machines, logs } = data;
const [currentTab, setCurrentTab] = useState(0);
return (
<Window title="Ore Silo" width={380} height={600}>
<Window.Content>
<Stack vertical fill>
<Stack.Item>
<Tabs fluid>
<Tabs.Tab
icon="plug"
selected={currentTab === 0}
onClick={() => setCurrentTab(0)}
>
Connections
</Tabs.Tab>
<Tabs.Tab
icon="book-bookmark"
selected={currentTab === 1}
onClick={() => setCurrentTab(1)}
>
Logs
</Tabs.Tab>
</Tabs>
</Stack.Item>
<Stack.Item grow>
{currentTab === 0 ? (
!!machines && machines.length > 0 ? (
<Section fill scrollable>
{machines.map((machine, index) => (
<MachineDisplay
key={machine.name}
machine={machine}
onPause={(machine) => act('hold', { ref: machine.ref })}
onRemove={(machine) =>
act('remove', { ref: machine.ref })
}
/>
))}
</Section>
) : (
<NoticeBox>No machines connected!</NoticeBox>
)
) : null}
{currentTab === 1 ? (
!!logs && logs.length > 0 ? (
<Box pr={1} height="100%" overflowY="scroll">
{logs.map((log, index) => (
<LogEntry key={index} log={log} />
))}
</Box>
) : (
<NoticeBox>No log entries currently present!</NoticeBox>
)
) : null}
</Stack.Item>
<Stack.Item>
<Section fill>
<MaterialAccessBar
availableMaterials={data.materials}
SHEET_MATERIAL_AMOUNT={SHEET_MATERIAL_AMOUNT}
onEjectRequested={(material, amount) =>
act('eject', { ref: material.ref, amount })
}
/>
</Section>
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};
type MachineProps = {
machine: Machine;
onPause: (machine: Machine) => void;
onRemove: (machine: Machine) => void;
};
const MachineDisplay = (props: MachineProps) => {
const { machine, onPause, onRemove } = props;
return (
<Box className="FabricatorRecipe">
<Box className="FabricatorRecipe__Title">
<Box className="FabricatorRecipe__Icon">
<Image
width={'32px'}
height={'32px'}
src={`data:image/jpeg;base64,${machine.icon}`}
/>
</Box>
<Box className="FabricatorRecipe__Label">{machine.name}</Box>
</Box>
<Tooltip
content={
machine.onHold
? `Resume ${machine.name} usage.`
: `Put ${machine.name} on hold.`
}
>
<Box
className={classes([
'FabricatorRecipe__Button',
'FabricatorRecipe__Button--icon',
])}
onClick={(_) => {
onPause(machine);
}}
>
<Icon name={machine.onHold ? 'circle-play' : 'circle-pause'} />
</Box>
</Tooltip>
<Tooltip content={`Disconnect ${machine.name}.`}>
<Box
className={classes([
'FabricatorRecipe__Button',
'FabricatorRecipe__Button--icon',
])}
onClick={(_) => {
onRemove(machine);
}}
>
<Icon name="trash-can" />
</Box>
</Tooltip>
</Box>
);
};
type LogProps = {
log: Log;
};
const LogEntry = (props: LogProps) => {
const { log } = props;
return (
<Section
title={`${capitalize(log.action)}: x${Math.abs(log.amount)} ${log.noun}`}
>
<LabeledList>
<LabeledList.Item label="Time">{log.time}</LabeledList.Item>
<LabeledList.Item label="Machine">
{capitalize(log.machineName)}
</LabeledList.Item>
<LabeledList.Item label="Location">{log.areaName}</LabeledList.Item>
<LabeledList.Item
label="Materials"
color={log.amount > 0 ? 'good' : 'bad'}
>
{log.rawMaterials}
</LabeledList.Item>
</LabeledList>
</Section>
);
};