Update Access/Airlock Controller NanoUI to TGUI (#21732)

PR for conversion of various NanoUI windows to TGUI.
**Access Controller**
<img width="758" height="252" alt="image"
src="https://github.com/user-attachments/assets/3263cc86-2e5f-4285-af39-25665a187a3c"
/>
**Airlock Controller (includes Advanced Airlock Controller)**
<img width="950" height="330" alt="image"
src="https://github.com/user-attachments/assets/1c636673-ec34-4ab2-a33d-1293fe20087b"
/>
This commit is contained in:
Batrachophreno
2026-01-30 19:28:09 +00:00
committed by GitHub
parent 0884b27691
commit 2fa2c731ea
10 changed files with 404 additions and 242 deletions
@@ -14,6 +14,8 @@
var/tag_secure = FALSE
var/tag_air_alarm
var/cycle_to_external_air = FALSE
var/has_interior_sensor
var/has_exterior_sensor
/obj/machinery/embedded_controller/radio/airlock/Initialize(mapload, given_id_tag, given_frequency, given_tag_exterior_door, given_tag_interior_door, given_tag_airpump, given_tag_chamber_sensor)
. = ..()
@@ -52,110 +54,62 @@
/obj/machinery/embedded_controller/radio/airlock/advanced_airlock_controller
name = "Advanced Airlock Controller"
/obj/machinery/embedded_controller/radio/airlock/advanced_airlock_controller/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
var/data[0]
data = list(
"chamber_pressure" = round(program.memory["chamber_sensor_pressure"]),
"external_pressure" = round(program.memory["external_sensor_pressure"]),
"internal_pressure" = round(program.memory["internal_sensor_pressure"]),
"processing" = program.memory["processing"],
"purge" = program.memory["purge"],
"secure" = program.memory["secure"]
)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "advanced_airlock_console.tmpl", name, 470, 290)
ui.set_initial_data(data)
/obj/machinery/embedded_controller/radio/airlock/advanced_airlock_controller/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "AirlockConsoleAdvanced", name, ui_x=470, ui_y=290)
ui.open()
ui.set_auto_update(1)
/obj/machinery/embedded_controller/radio/airlock/advanced_airlock_controller/ui_data(mob/user)
var/list/data = list()
/obj/machinery/embedded_controller/radio/airlock/advanced_airlock_controller/Topic(href, href_list)
if(..())
data["chamber_pressure"] = round(program.memory["chamber_sensor_pressure"])
data["has_exterior_sensor"] = has_exterior_sensor
data["external_pressure"] = round(program.memory["external_sensor_pressure"])
data["has_interior_sensor"] = has_interior_sensor
data["internal_pressure"] = round(program.memory["internal_sensor_pressure"])
data["processing"] = program.memory["processing"]
data["purge"] = program.memory["purge"]
data["secure"] = program.memory["secure"]
return data
/obj/machinery/embedded_controller/radio/airlock/advanced_airlock_controller/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
usr.set_machine(src)
src.add_fingerprint(usr)
var/clean = 0
switch(href_list["command"]) //anti-HTML-hacking checks
if("cycle_ext")
clean = 1
if("cycle_int")
clean = 1
if("force_ext")
clean = 1
if("force_int")
clean = 1
if("abort")
clean = 1
if("purge")
clean = 1
if("secure")
clean = 1
if(clean)
program.receive_user_command(href_list["command"])
return 1
if(action == "command")
var/command_name = sanitize(params["command"])
program.receive_user_command(command_name)
//Airlock controller for airlock control - most airlocks on the station use this
/obj/machinery/embedded_controller/radio/airlock/airlock_controller
name = "Airlock Controller"
tag_secure = TRUE
/obj/machinery/embedded_controller/radio/airlock/airlock_controller/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
var/data[0]
data = list(
"chamber_pressure" = round(program.memory["chamber_sensor_pressure"]),
"exterior_status" = program.memory["exterior_status"],
"interior_status" = program.memory["interior_status"],
"processing" = program.memory["processing"]
)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "simple_airlock_console.tmpl", name, 470, 290)
ui.set_initial_data(data)
/obj/machinery/embedded_controller/radio/airlock/airlock_controller/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "AirlockConsoleStandard", name, ui_x=470, ui_y=290)
ui.open()
ui.set_auto_update(1)
/obj/machinery/embedded_controller/radio/airlock/airlock_controller/ui_data(mob/user)
var/list/data = list()
/obj/machinery/embedded_controller/radio/airlock/airlock_controller/Topic(href, href_list)
if(..())
data["chamber_pressure"] = round(program.memory["chamber_sensor_pressure"])
data["processing"] = program.memory["processing"]
return data
/obj/machinery/embedded_controller/radio/airlock/airlock_controller/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
usr.set_machine(src)
src.add_fingerprint(usr)
var/clean = 0
switch(href_list["command"]) //anti-HTML-hacking checks
if("cycle_ext")
clean = 1
if("cycle_int")
clean = 1
if("force_ext")
clean = 1
if("force_int")
clean = 1
if("abort")
clean = 1
if(clean)
program.receive_user_command(href_list["command"])
return 1
if(action == "command")
var/command_name = sanitize(params["command"])
program.receive_user_command(command_name)
//Access controller for door control - used in virology and the like
/obj/machinery/embedded_controller/radio/airlock/access_controller
@@ -163,7 +117,7 @@
icon_state = "access_control_standby"
name = "Access Controller"
tag_secure = 1
tag_secure = TRUE
/obj/machinery/embedded_controller/radio/airlock/access_controller/update_icon()
@@ -175,47 +129,26 @@
else
icon_state = "access_control_off"
/obj/machinery/embedded_controller/radio/airlock/access_controller/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
var/data[0]
data = list(
"exterior_status" = program.memory["exterior_status"],
"interior_status" = program.memory["interior_status"],
"processing" = program.memory["processing"]
)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "door_access_console.tmpl", name, 330, 220)
ui.set_initial_data(data)
/obj/machinery/embedded_controller/radio/airlock/access_controller/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "AirlockConsoleAccess", name, ui_x=470, ui_y=290)
ui.open()
ui.set_auto_update(1)
/obj/machinery/embedded_controller/radio/airlock/access_controller/ui_data(mob/user)
var/list/data = list()
/obj/machinery/embedded_controller/radio/airlock/access_controller/Topic(href, href_list)
if(..())
data["exterior_status"] = round(program.memory["exterior_status"])
data["interior_status"] = round(program.memory["interior_status"])
data["processing"] = program.memory["processing"]
return data
/obj/machinery/embedded_controller/radio/airlock/access_controller/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
usr.set_machine(src)
src.add_fingerprint(usr)
var/clean = 0
switch(href_list["command"]) //anti-HTML-hacking checks
if("cycle_ext_door")
clean = 1
if("cycle_int_door")
clean = 1
if("force_ext")
if(program.memory["interior_status"]["state"] == "closed")
clean = 1
if("force_int")
if(program.memory["exterior_status"]["state"] == "closed")
clean = 1
if(clean)
program.receive_user_command(href_list["command"])
return 1
if(action == "command")
var/command_name = sanitize(params["command"])
program.receive_user_command(command_name)
@@ -3,7 +3,7 @@
name = "docking port controller"
var/datum/computer/file/embedded_program/airlock/docking/airlock_program
var/datum/computer/file/embedded_program/docking/airlock/docking_program
tag_secure = 1
tag_secure = TRUE
/obj/machinery/embedded_controller/radio/airlock/docking_port/Initialize()
. = ..()
@@ -80,27 +80,27 @@
airlock_program.receive_signal(signal, receive_method, receive_param) //pass along to subprograms
..(signal, receive_method, receive_param)
//tell the docking port to start getting ready for docking - e.g. pressurize
/// Tell the docking port to start getting ready for docking - e.g. pressurize
/datum/computer/file/embedded_program/docking/airlock/prepare_for_docking()
airlock_program.begin_dock_cycle()
//are we ready for docking?
/// Are we ready for docking?
/datum/computer/file/embedded_program/docking/airlock/ready_for_docking()
return airlock_program.done_cycling()
//we are docked, open the doors or whatever.
/// We are docked, open the doors or whatever.
/datum/computer/file/embedded_program/docking/airlock/finish_docking()
airlock_program.enable_mech_regulators()
airlock_program.open_doors()
//tell the docking port to start getting ready for undocking - e.g. close those doors.
/// Tell the docking port to start getting ready for undocking - e.g. close those doors.
/datum/computer/file/embedded_program/docking/airlock/prepare_for_undocking()
testing("[src.name] is closing doors")
airlock_program.stop_cycling()
airlock_program.close_doors()
airlock_program.disable_mech_regulators()
//are we ready for undocking?
/// Are we ready for undocking?
/datum/computer/file/embedded_program/docking/airlock/ready_for_undocking()
var/ext_closed = airlock_program.check_exterior_door_secured()
var/int_closed = airlock_program.check_interior_door_secured()
@@ -44,7 +44,7 @@
name = "docking port controller"
var/master_tag //for mapping
var/datum/computer/file/embedded_program/airlock/multi_docking/airlock_program
tag_secure = 1
tag_secure = TRUE
/obj/machinery/embedded_controller/radio/airlock/docking_port_multi/Initialize()
. = ..()
@@ -39,14 +39,35 @@
var/is_interior = locate(/obj/effect/map_effect/marker_helper/airlock/interior) in loc
var/is_exterior = locate(/obj/effect/map_effect/marker_helper/airlock/exterior) in loc
var/is_out = locate(/obj/effect/map_effect/marker_helper/airlock/out) in loc
var/found_controller = FALSE
// iterate over airlock components under this marker
// and actually set them up
for(var/thing in loc)
// set up the controller
var/obj/machinery/embedded_controller/radio/airlock/airlock_controller/controller = thing
if(istype(controller))
// Check for an advanced controller to set up.
var/obj/machinery/embedded_controller/radio/airlock/advanced_airlock_controller/advanced_controller = thing
if(istype(advanced_controller))
// common controller vars
found_controller = TRUE
advanced_controller.set_frequency(frequency)
advanced_controller.id_tag = MARKER_AIRLOCK_TAG_MASTER
advanced_controller.tag_airpump = MARKER_AIRLOCK_TAG_AIRPUMP_CHAMBER
advanced_controller.tag_chamber_sensor = MARKER_AIRLOCK_TAG_SENSOR_CHAMBER
advanced_controller.tag_exterior_sensor = MARKER_AIRLOCK_TAG_SENSOR_EXTERIOR
advanced_controller.tag_exterior_door = MARKER_AIRLOCK_TAG_DOOR_EXTERIOR
advanced_controller.tag_interior_door = MARKER_AIRLOCK_TAG_DOOR_INTERIOR
advanced_controller.cycle_to_external_air = cycle_to_external_air
advanced_controller.req_access = req_access
advanced_controller.req_one_access = req_one_access
// controller subtype specific vars
advanced_controller.program = new /datum/computer/file/embedded_program/airlock(advanced_controller)
continue
// If we didn't find an advanced controller, fall back to the basic kind
var/obj/machinery/embedded_controller/radio/airlock/airlock_controller/controller = thing
if(istype(controller) && !found_controller)
// common controller vars
found_controller = TRUE
controller.set_frequency(frequency)
controller.id_tag = MARKER_AIRLOCK_TAG_MASTER
controller.tag_airpump = MARKER_AIRLOCK_TAG_AIRPUMP_CHAMBER
@@ -61,8 +82,7 @@
controller.program = new /datum/computer/file/embedded_program/airlock(controller)
continue
// and all the other airlock components
// Now all the other airlock components
var/obj/machinery/door/airlock/door = thing
if(istype(door))
door.set_frequency(frequency)
+58
View File
@@ -0,0 +1,58 @@
################################
# Example Changelog File
#
# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
#
# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
# When it is, any changes listed below will disappear.
#
# Valid Prefixes:
# bugfix
# - (fixes bugs)
# wip
# - (work in progress)
# qol
# - (quality of life)
# soundadd
# - (adds a sound)
# sounddel
# - (removes a sound)
# rscadd
# - (adds a feature)
# rscdel
# - (removes a feature)
# imageadd
# - (adds an image or sprite)
# imagedel
# - (removes an image or sprite)
# spellcheck
# - (fixes spelling or grammar)
# experiment
# - (experimental change)
# balance
# - (balance changes)
# code_imp
# - (misc internal code change)
# refactor
# - (refactors code)
# config
# - (makes a change to the config files)
# admin
# - (makes changes to administrator tools)
# server
# - (miscellaneous changes to server)
#################################
# Your name.
author: Batrachophrenoboocosmomachia
# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
delete-after: True
# Any changes you've made. See valid prefix list above.
# INDENT WITH TWO SPACES. NOT TABS. SPACES.
# SCREW THIS UP AND IT WON'T WORK.
# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit.
# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog.
changes:
- refactor: "Rewritten interfaces for access controllers, airlock controllers, and advanced airlock controllers to use TGUI instead of NanoUI."
@@ -1,60 +0,0 @@
<div class="item" style="padding-top: 10px">
<div class="item">
<div class="itemLabel">
External Pressure:
</div>
<div class="itemContent">
{{:helper.displayBar(data.external_pressure, 0, 200, (data.external_pressure < 80 || data.external_pressure > 120) ? 'bad' : (data.external_pressure < 95 || data.external_pressure > 110) ? 'average' : 'good')}}
<div class="statusValue">
{{:data.external_pressure}} kPa
</div>
</div>
</div>
<div class="item">
<div class="itemLabel">
Chamber Pressure:
</div>
<div class="itemContent">
{{:helper.displayBar(data.chamber_pressure, 0, 200, (data.chamber_pressure < 80 || data.chamber_pressure > 120) ? 'bad' : (data.chamber_pressure < 95 || data.chamber_pressure > 110) ? 'average' : 'good')}}
<div class="statusValue">
{{:data.chamber_pressure}} kPa
</div>
</div>
</div>
<div class="item">
<div class="itemLabel">
Internal Pressure:
</div>
<div class="itemContent">
{{:helper.displayBar(data.internal_pressure, 0, 200, (data.internal_pressure < 80 || data.internal_pressure > 120) ? 'bad' : (data.internal_pressure < 95 || data.internal_pressure > 110) ? 'average' : 'good')}}
<div class="statusValue">
{{:data.internal_pressure}} kPa
</div>
</div>
</div>
</div>
<div class="item" style="padding-top: 10px">
<div class="item" style="width: auto; float: left">
<div class="itemContent">
{{:helper.link('Cycle to Exterior', 'arrowthickstop-1-w', {'command' : 'cycle_ext'}, data.processing ? 'disabled' : null)}}
{{:helper.link('Cycle to Interior', 'arrowthickstop-1-e', {'command' : 'cycle_int'}, data.processing ? 'disabled' : null)}}
</div>
<div class="itemContent" style="padding-top: 2px">
{{:helper.link('Force exterior door', 'alert', {'command' : 'force_ext'}, null, data.processing ? 'yellowButton' : null)}}
{{:helper.link('Force interior door', 'alert', {'command' : 'force_int'}, null, data.processing ? 'yellowButton' : null)}}
</div>
</div>
<div style="padding-left: 10px; width: 80px; float: left">
<div class="item">
<div class="itemContent" style="padding-bottom: 2px; width: auto">
{{:helper.link('Purge', 'refresh', {'command' : 'purge'}, data.processing ? 'disabled' : null, data.purge ? 'linkOn' : null)}}
</div>
<div class="itemContent" style="width: auto">
{{:helper.link('Secure', data.secure ? 'locked' : 'unlocked', {'command' : 'secure'}, data.processing ? 'disabled' : null, data.secure ? 'linkOn' : null)}}
</div>
</div>
</div>
<div class="item" style="padding-top: 10px">
{{:helper.link('Abort', 'cancel', {'command' : 'abort'}, data.processing ? null : 'disabled', data.processing ? 'redButton' : null)}}
</div>
</div>
-42
View File
@@ -1,42 +0,0 @@
<div class="item" style="padding-top: 10px">
<div class="item">
<div class="itemLabel" style="width: 150px">
Exterior Door Status:
</div>
<div class="statusValue">
{{if data.exterior_status.state == "closed"}}
Locked
{{else}}
Open
{{/if}}
</div>
</div>
<div class="item">
<div class="itemLabel" style="width: 150px">
Interior Door Status:
</div>
<div class="statusValue">
{{if data.interior_status.state == "closed"}}
Locked
{{else}}
Open
{{/if}}
</div>
</div>
</div>
<div class="item" style="padding-top: 10px">
<div class="item">
<div class="itemContent" style="width: 100%">
{{if data.exterior_status.state == "open"}}
{{:helper.link('Lock Exterior Door', 'alert', {'command' : 'force_ext'}, data.processing ? 'disabled' : null)}}
{{else}}
{{:helper.link('Cycle to Exterior', 'arrowthickstop-1-w', {'command' : 'cycle_ext_door'}, data.processing ? 'disabled' : null)}}
{{/if}}
{{if data.interior_status.state == "open"}}
{{:helper.link('Lock Interior Door', 'alert', {'command' : 'force_int'}, data.processing ? 'disabled' : null)}}
{{else}}
{{:helper.link('Cycle to Interior', 'arrowthickstop-1-e', {'command' : 'cycle_int_door'}, data.processing ? 'disabled' : null)}}
{{/if}}
</div>
</div>
</div>
@@ -0,0 +1,59 @@
import { useBackend } from '../backend';
import { Box, Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';
export type StatusData = {
state: string;
lock: string;
};
export type AccessAirlockConsoleData = {
exterior_status: StatusData;
interior_status: StatusData;
processing: boolean;
};
export const AirlockConsoleAccess = (props, context) => {
const { act, data } = useBackend<AccessAirlockConsoleData>(context);
return (
<Window resizable>
<Window.Content scrollable>
<Section title="Status">
<Box>
<LabeledList>
<LabeledList.Item label="Exterior Door Status">
{data.exterior_status.state === 'open' ? (
<Box>Open</Box>
) : (
<Box>Locked</Box>
)}
</LabeledList.Item>
<LabeledList.Item label="Interior Door Status">
{data.interior_status.state === 'open' ? (
<Box>Open</Box>
) : (
<Box>Locked</Box>
)}
</LabeledList.Item>
</LabeledList>
</Box>
</Section>
<Section title="Controls">
<Box>
<Button
content="Cycle to Exterior"
icon="arrow-right-from-bracket"
onClick={() => act('command', { command: 'cycle_ext' })}
/>
<Button
content="Cycle to Interior"
icon="arrow-right-to-bracket"
onClick={() => act('command', { command: 'cycle_int' })}
/>
</Box>
</Section>
</Window.Content>
</Window>
);
};
@@ -0,0 +1,127 @@
import { useBackend } from '../backend';
import { Box, Button, LabeledList, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
export type AdvancedAirlockConsoleData = {
chamber_pressure: number;
has_exterior_sensor: boolean;
external_pressure: number;
has_interior_sensor: boolean;
internal_pressure: number;
processing: boolean;
purge: boolean;
secure: boolean;
};
export const AirlockConsoleAdvanced = (props, context) => {
const { act, data } = useBackend<AdvancedAirlockConsoleData>(context);
return (
<Window resizable>
<Window.Content scrollable>
<Section title="Status">
<Box>
<LabeledList>
<LabeledList.Item label="External Pressure">
{data.has_exterior_sensor ? (
<ProgressBar
ranges={{
average: [120, Infinity],
good: [80, 120],
bad: [-Infinity, 80],
}}
value={data.external_pressure}
minValue={0}
maxValue={200}
>
{data.external_pressure} kPa
</ProgressBar>
) : (
<Box>Error - No Sensor Detected</Box>
)}
</LabeledList.Item>
<LabeledList.Item label="Chamber Pressure">
<ProgressBar
ranges={{
average: [120, Infinity],
good: [80, 120],
bad: [-Infinity, 80],
}}
value={data.chamber_pressure}
minValue={0}
maxValue={200}
>
{data.chamber_pressure} kPa
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item label="Internal Pressure">
{data.has_interior_sensor ? (
<ProgressBar
ranges={{
average: [120, Infinity],
good: [80, 120],
bad: [-Infinity, 80],
}}
value={data.internal_pressure}
minValue={0}
maxValue={200}
>
{data.internal_pressure} kPa
</ProgressBar>
) : (
<Box>Error - No Sensor Detected</Box>
)}
</LabeledList.Item>
</LabeledList>
</Box>
</Section>
<Section title="Controls">
<Box>
<Button
content="Cycle to Exterior"
icon="arrow-right-from-bracket"
onClick={() => act('command', { command: 'cycle_ext' })}
/>
<Button
content="Cycle to Interior"
icon="arrow-right-to-bracket"
onClick={() => act('command', { command: 'cycle_int' })}
/>
</Box>
<Box>
<Button
content="Force Exterior Door"
icon="circle-exclamation"
color="yellow"
onClick={() => act('command', { command: 'force_ext' })}
/>
<Button
content="Force Interior Door"
icon="circle-exclamation"
color="yellow"
onClick={() => act('command', { command: 'force_int' })}
/>
</Box>
<Box>
<Button
content="Purge"
icon="refresh"
tooltip="Pumps all air out of the airlock before refilling it; removed air will be pumped in the same direction as the airlock is cycled, if multiple outflow targets exist."
disabled={!data.processing}
color={!data.processing ? null : 'red'}
onClick={() => act('command', { command: 'purge' })}
/>
<Button
content="Secure"
icon={data.secure ? 'locked' : 'unlocked'}
tooltip="Bolts airlock doors during cycles. Default enabled; use extreme caution if disabling."
disabled={!data.processing}
color={!data.secure ? 'green' : 'red'}
onClick={() => act('command', { command: 'secure' })}
/>
</Box>
</Section>
</Window.Content>
</Window>
);
};
@@ -0,0 +1,67 @@
import { useBackend } from '../backend';
import { Box, Button, LabeledList, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
export type StandardAirlockConsoleData = {
chamber_pressure: number;
processing: boolean;
};
export const AirlockConsoleStandard = (props, context) => {
const { act, data } = useBackend<StandardAirlockConsoleData>(context);
return (
<Window resizable>
<Window.Content scrollable>
<Section title="Status">
<Box>
<LabeledList>
<LabeledList.Item label="Chamber Pressure">
<ProgressBar
ranges={{
average: [120, Infinity],
good: [80, 120],
bad: [-Infinity, 80],
}}
value={data.chamber_pressure}
minValue={0}
maxValue={200}
>
{data.chamber_pressure} kPa
</ProgressBar>
</LabeledList.Item>
</LabeledList>
</Box>
</Section>
<Section title="Controls">
<Box>
<Button
content="Cycle to Exterior"
icon="arrow-right-from-bracket"
onClick={() => act('command', { command: 'cycle_ext' })}
/>
<Button
content="Cycle to Interior"
icon="arrow-right-to-bracket"
onClick={() => act('command', { command: 'cycle_int' })}
/>
</Box>
<Box>
<Button
content="Force Exterior Door"
icon="circle-exclamation"
color="yellow"
onClick={() => act('command', { command: 'force_ext' })}
/>
<Button
content="Force Interior Door"
icon="circle-exclamation"
color="yellow"
onClick={() => act('command', { command: 'force_int' })}
/>
</Box>
</Section>
</Window.Content>
</Window>
);
};