NanoUI Conversions - DNA Analyzer & Engines Control (#22326)

changes:
  - refactor: "Migrates DNA Analyzer NanoUI to TGUI."
  - refactor: "Migrates Engines Control NanoUI to TGUI."
- qol: "DNA Analyzer reports now provide the date and time the scan was
produced."
- bugfix: "Ship computers no longer function after their power is cut."
  
Before/After images:
<img width="540" height="683" alt="dna_analyzer"
src="https://github.com/user-attachments/assets/b1199467-74e5-4b08-a0d6-3a052142baa0"
/>
<img width="776" height="696" alt="engines_control"
src="https://github.com/user-attachments/assets/cea796cf-34f5-43dd-897f-f5435ff7f120"
/>
This commit is contained in:
Batrachophreno
2026-04-27 18:49:22 +00:00
committed by GitHub
parent ffdb6edfdb
commit 34ebbcd67a
12 changed files with 543 additions and 199 deletions
@@ -4,85 +4,111 @@
desc = "A high tech machine that is designed to read DNA samples properly."
icon = 'icons/obj/forensics.dmi'
icon_state = "dnaopen"
anchored = 1
density = 1
anchored = TRUE
density = TRUE
var/obj/item/forensics/swab/bloodsamp = null
var/closed = 0
var/scanning = 0
var/closed = FALSE
var/scanning = FALSE
var/scanner_progress = 0
var/scanner_rate = 2.50
var/last_process_worldtime = 0
var/report_num = 0
/obj/machinery/dnaforensics/attackby(obj/item/attacking_item, mob/user)
if(bloodsamp)
to_chat(user, SPAN_WARNING("There is already a sample in the machine."))
return
if(closed)
to_chat(user, SPAN_WARNING("Open the cover before inserting the sample."))
return
var/obj/item/forensics/swab/swab = attacking_item
if(istype(swab) && swab.is_used())
user.unEquip(attacking_item)
src.bloodsamp = swab
swab.forceMove(src)
to_chat(user, SPAN_NOTICE("You insert \the [attacking_item] into \the [src]."))
else
to_chat(user, SPAN_WARNING("\The [src] only accepts used swabs."))
return
// General intended use (w swabs)
if(istype(swab))
if(swab.is_used())
// Already a sample loaded
if(bloodsamp)
to_chat(user, SPAN_WARNING("There is already a sample in the machine."))
return FALSE
// Open the lid, dummy
if(closed)
to_chat(user, SPAN_WARNING("Open the cover before inserting the sample."))
return FALSE
// Insert the sample
else
user.unEquip(attacking_item)
src.bloodsamp = swab
swab.forceMove(src)
to_chat(user, SPAN_NOTICE("You insert \the [attacking_item] into \the [src]."))
return TRUE
// Used swabs only
else
to_chat(user, SPAN_WARNING("\The [src] only accepts used swabs."))
return FALSE
/obj/machinery/dnaforensics/ui_interact(mob/user, ui_key = "main",var/datum/nanoui/ui = null)
if(stat & (NOPOWER)) return
if(user.stat || user.restrained()) return
else if(attacking_item.tool_behaviour == TOOL_WRENCH)
attacking_item.play_tool_sound(get_turf(src), 100)
balloon_alert(user, "[anchored ? "secure" : "unsecure"]")
user.visible_message("[user.name] [anchored ? "secures" : "unsecures"] the bolts holding [src.name] to the floor.", \
"You [anchored ? "secure" : "unsecure"] the bolts holding [src] to the floor.", \
"You hear a ratchet")
anchored = !anchored
return TRUE
return ..()
/obj/machinery/dnaforensics/ui_interact(mob/user, datum/tgui/ui)
if(stat & NOPOWER)
return
if(use_check_and_message(user))
return
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "DnaForensics", "QuikScan DNA Analyzer")
ui.open()
ui.set_autoupdate(TRUE)
/obj/machinery/dnaforensics/ui_data(mob/user)
var/list/data = list()
data["scan_progress"] = round(scanner_progress)
data["scanning"] = scanning
data["bloodsamp"] = (bloodsamp ? bloodsamp.name : "")
data["bloodsamp_desc"] = (bloodsamp ? (bloodsamp.desc ? bloodsamp.desc : "No information on record.") : "")
data["lidstate"] = closed
data["lid_closed"] = closed
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data)
if (!ui)
ui = new(user, src, ui_key, "dnaforensics.tmpl", "QuikScan DNA Analyzer", 540, 326)
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
return data
/obj/machinery/dnaforensics/Topic(href, href_list)
/obj/machinery/dnaforensics/ui_act(action, params)
. = ..()
if(.)
return
if(..()) return 1
switch(action)
if("scanItem")
if(scanning)
scanning = FALSE
return TRUE
if(stat & (NOPOWER))
return 0 // don't update UIs attached to this object
if(href_list["scanItem"])
if(scanning)
scanning = 0
else
if(bloodsamp)
if(closed == 1)
scanner_progress = 0
scanning = 1
to_chat(usr, SPAN_NOTICE("Scan initiated."))
update_icon()
else
to_chat(usr, SPAN_NOTICE("Please close sample lid before initiating scan."))
else
if(!bloodsamp)
to_chat(usr, SPAN_WARNING("Insert an item to scan."))
return TRUE
if(href_list["ejectItem"])
if(bloodsamp)
bloodsamp.forceMove(src.loc)
bloodsamp = null
if(closed)
scanner_progress = 0
scanning = TRUE
to_chat(usr, SPAN_NOTICE("Scan initiated."))
update_icon()
return TRUE
if(href_list["toggleLid"])
toggle_lid()
to_chat(usr, SPAN_NOTICE("Please close sample lid before initiating scan."))
return TRUE
return 1
if("ejectItem")
if(bloodsamp && !scanning)
bloodsamp.forceMove(src.loc)
bloodsamp = null
return TRUE
return FALSE
if("toggleLid")
toggle_lid()
return TRUE
return FALSE
/obj/machinery/dnaforensics/process()
if(scanning)
@@ -108,6 +134,7 @@
P.stamped = list(/obj/item/stamp)
P.overlays = list("paper_stamped")
//dna data itself
var/datetime = "[worlddate2text()] [worldtime2text()]"
var/data = "No scan information available."
if(bloodsamp.dna != null)
data = "Spectometric analysis on provided sample has determined the presence of [length(bloodsamp.dna)] string\s of DNA.<br><br>"
@@ -117,7 +144,7 @@
data += "<b>DNA:</b> [blood]<br><br>"
else
data += "No DNA found.<br>"
info = "<b><font size=\"4\">[src] analysis report #[report_num]</font></b><HR>"
info = "<b><font size=\"4\">[src] forensic report #[report_num]</font></b><br><i>Scanned [datetime]</i><HR>"
info += "<b>Scanned item:</b> [bloodsamp.name]<br><br>" + data
P.set_content_unsafe(pname, info)
print(P, user = usr)
@@ -138,7 +165,7 @@
set name = "Toggle Lid"
set src in oview(1)
if(usr.stat || !isliving(usr))
if(use_check_and_message(usr))
return
if(scanning)
@@ -10,7 +10,7 @@
var/display_state = "status"
/obj/machinery/computer/ship/engines/cockpit
density = 0
density = FALSE
icon = 'icons/obj/cockpit_console.dmi'
icon_state = "right"
icon_screen = "engine"
@@ -28,91 +28,116 @@
can_pass_under = FALSE
light_power_on = 1
/obj/machinery/computer/ship/engines/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
/obj/machinery/computer/ship/engines/ui_interact(mob/user, datum/tgui/ui)
if(!connected)
display_reconnect_dialog(user, "ship control systems")
return
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "EnginesControl", "[connected.get_real_name()] Engines Control")
ui.open()
/obj/machinery/computer/ship/engines/ui_data(mob/user)
var/list/data = list()
if(!connected)
return
var/data[0]
data["state"] = display_state
data["global_state"] = connected.engines_state
data["global_limit"] = round(connected.thrust_limit*100)
var/total_thrust = 0
data["global_state"] = !!connected.engines_state
data["global_limit"] = round(connected.thrust_limit * 100)
var/total_thrust = 0
var/list/enginfo = list()
var/list/enginfo[0]
for(var/datum/ship_engine/E in connected.engines)
var/list/rdata[0]
rdata["eng_type"] = E.name
rdata["eng_on"] = E.is_on()
rdata["eng_thrust"] = E.get_thrust()
rdata["eng_thrust_limiter"] = round(E.get_thrust_limit()*100)
rdata["eng_status"] = E.get_status()
rdata["eng_reference"] = "[REF(E)]"
var/list/edata = list()
edata["eng_type"] = E.name
edata["eng_on"] = !!E.is_on()
edata["eng_thrust"] = E.get_thrust() * 10
edata["eng_thrust_limiter"] = round(E.get_thrust_limit() * 100)
edata["eng_status"] = E.get_status()
edata["eng_reference"] = "[REF(E)]"
total_thrust += E.get_thrust()
enginfo.Add(list(rdata))
enginfo += list(edata)
data["engines_info"] = enginfo
data["total_thrust"] = total_thrust
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "engines_control.tmpl", "[connected.get_real_name()] Engines Control", 390, 530)
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
return data
/obj/machinery/computer/ship/engines/Topic(href, href_list)
if(..())
return TOPIC_HANDLED
/obj/machinery/computer/ship/engines/ui_act(action, params)
. = ..()
if(.)
return
if(href_list["state"])
display_state = href_list["state"]
return TOPIC_REFRESH
if(!connected)
return FALSE
if(href_list["global_toggle"])
connected.engines_state = !connected.engines_state
for(var/datum/ship_engine/E in connected.engines)
if(connected.engines_state == !E.is_on())
E.toggle()
return TOPIC_REFRESH
if(use_check_and_message(usr))
return FALSE
if(href_list["set_global_limit"])
var/newlim = tgui_input_number(usr, "Input the new thrust limit.", "Thrust Limit", connected.thrust_limit*100, 100, 0)
if(!CanInteract(usr, GLOB.physical_state))
return TOPIC_NOACTION
connected.thrust_limit = clamp(newlim/100, 0, 1)
for(var/datum/ship_engine/E in connected.engines)
E.set_thrust_limit(connected.thrust_limit)
return TOPIC_REFRESH
switch(action)
if("set_state")
var/new_state = params["state"]
if(new_state in list("status", "engines"))
display_state = new_state
return TRUE
return FALSE
if(href_list["global_limit"])
connected.thrust_limit = clamp(connected.thrust_limit + text2num(href_list["global_limit"]), 0, 1)
for(var/datum/ship_engine/E in connected.engines)
E.set_thrust_limit(connected.thrust_limit)
return TOPIC_REFRESH
if("global_toggle")
connected.engines_state = !connected.engines_state
for(var/datum/ship_engine/E in connected.engines)
if(connected.engines_state == !E.is_on())
E.toggle()
return TRUE
if(href_list["engine"])
if(href_list["set_limit"])
var/datum/ship_engine/E = locate(href_list["engine"])
var/newlim = tgui_input_number(usr, "Input the new thrust limit.", "Thrust Limit", E.get_thrust_limit(), 100, 0)
if(!CanInteract(usr, GLOB.physical_state))
return
var/limit = clamp(newlim/100, 0, 1)
if(istype(E))
E.set_thrust_limit(limit)
return TOPIC_REFRESH
if(href_list["limit"])
var/datum/ship_engine/E = locate(href_list["engine"])
var/limit = clamp(E.get_thrust_limit() + text2num(href_list["limit"]), 0, 1)
if(istype(E))
E.set_thrust_limit(limit)
return TOPIC_REFRESH
if("set_global_limit")
var/newlim = tgui_input_number(usr, "Input the new thrust limit.", "Thrust Limit", connected.thrust_limit * 100, 100, 0)
if(isnull(newlim))
return FALSE
if(href_list["toggle"])
var/datum/ship_engine/E = locate(href_list["engine"])
if(istype(E))
E.toggle()
return TOPIC_REFRESH
return TOPIC_REFRESH
return TOPIC_NOACTION
connected.thrust_limit = clamp(newlim / 100, 0, 1)
for(var/datum/ship_engine/E in connected.engines)
E.set_thrust_limit(connected.thrust_limit)
return TRUE
if("global_limit_delta")
var/delta = text2num(params["delta"])
connected.thrust_limit = round(clamp(connected.thrust_limit + delta, 0, 1),0.1)
for(var/datum/ship_engine/E in connected.engines)
E.set_thrust_limit(connected.thrust_limit)
return TRUE
if("engine_set_limit")
var/datum/ship_engine/E = locate(params["engine"])
if(!istype(E))
return FALSE
var/newlim = tgui_input_number(usr, "Input the new thrust limit.", "Thrust Limit", E.get_thrust_limit() * 100, 100, 0)
if(isnull(newlim))
return FALSE
var/limit = clamp(newlim / 100, 0, 1)
E.set_thrust_limit(limit)
return TRUE
if("engine_limit_delta")
var/datum/ship_engine/E = locate(params["engine"])
if(!istype(E))
return FALSE
var/delta = text2num(params["delta"])
var/limit = clamp(E.get_thrust_limit() + delta, 0, 1)
E.set_thrust_limit(limit)
return TRUE
if("engine_toggle")
var/datum/ship_engine/E = locate(params["engine"])
if(!istype(E))
return FALSE
E.toggle()
return TRUE
return FALSE
+3 -1
View File
@@ -339,8 +339,10 @@
light_power_on = 1
/obj/machinery/computer/ship/navigation/attack_hand(mob/user)
if(stat & (NOPOWER|BROKEN))
return FALSE
if(use_check_and_message(user))
return
return FALSE
if(!emagged && !allowed(user))
to_chat(user, SPAN_WARNING("Access denied."))
return FALSE
+3 -1
View File
@@ -65,12 +65,14 @@ somewhere on that shuttle. Subtypes of these can be then used to perform ship ov
return ..()
/obj/machinery/computer/ship/attack_hand(mob/user)
if(stat & (NOPOWER|BROKEN))
return FALSE
// Snowflake case for checking player characters for a Pilot Spacecraft Skill.
// Only player characters will have the component. Which will both always be present on them, and will only enable its own return logic if it exists.
// NPCs, Ghostroles, and Offship Antags that don't generate skills are unaffected by this check by intentional design so that we don't have to account for them.
if (user.GetComponent(PILOT_SPACECRAFT_SKILL_COMPONENT)?.skill_level == SKILL_LEVEL_UNFAMILIAR)
to_chat(user, SPAN_WARNING("There's just so many buttons... You have no idea where to even begin."))
return
return FALSE
if(use_check_and_message(user))
return
+4 -4
View File
@@ -14,13 +14,13 @@ GLOBAL_LIST_INIT_TYPED(ship_engines, /datum/ship_engine, list())
/datum/ship_engine/proc/can_burn()
return 0
//Tries to fire the engine with power modifier (0..1). Returns thrust.
//Power modifier is intended to be used for maneuvers or the like,
//that should use less/more fuel/electricity than an actual burn.
/// Tries to fire the engine with power modifier (0..1). Returns thrust.
/// Power modifier is intended to be used for maneuvers or the like,
/// that should use less/more fuel/electricity than an actual burn.
/datum/ship_engine/proc/burn(var/power_modifier = 1)
return 0
//Returns status string for this engine
/// Returns status string for this engine
/datum/ship_engine/proc/get_status()
return "All systems nominal"
@@ -158,20 +158,33 @@
/obj/machinery/atmospherics/unary/engine/proc/get_status()
. = list()
.+= "Location: [get_area(src)]."
if(stat & NOPOWER)
.+= "<span class='average'>Insufficient power to operate.</span>"
if(!check_fuel())
.+= "<span class='average'>Insufficient fuel for a burn.</span>"
if(stat & BROKEN)
.+= "<span class='average'>Inoperable engine configuration.</span>"
if(blockage)
.+= "<span class='average'>Obstruction of airflow detected.</span>"
.+= "Propellant total mass: [round(air_contents.get_mass(),0.01)] kg."
.+= "Propellant used per burn: [round(air_contents.get_mass() * volume_per_burn * thrust_limit / air_contents.volume,0.01)] kg."
.+= "Propellant pressure: [round(XGM_PRESSURE(air_contents)/1000,0.1)] MPa."
. = jointext(.,"<br>")
. += list(list(
"text" = "Location: [get_area(src)].",
"severity" = "info"
))
if(stat & NOPOWER)
. += list(list("text" = "Insufficient power to operate.", "severity" = "bad"))
if(!check_fuel())
. += list(list("text" = "Insufficient fuel for a burn.", "severity" = "bad"))
if(stat & BROKEN)
. += list(list("text" = "Inoperable engine configuration.", "severity" = "bad"))
if(blockage)
. += list(list("text" = "Obstruction of airflow detected.", "severity" = "bad"))
. += list(list(
"text" = "Propellant total mass: [round(air_contents.get_mass(), 0.01)] kg.",
"severity" = null
))
. += list(list(
"text" = "Propellant used per burn: [round(air_contents.get_mass() * volume_per_burn * thrust_limit / air_contents.volume, 0.01)] kg.",
"severity" = null
))
. += list(list(
"text" = "Propellant pressure: [round(XGM_PRESSURE(air_contents)/1000, 0.1)] MPa.",
"severity" = null
))
/obj/machinery/atmospherics/unary/engine/power_change()
. = ..()
@@ -246,7 +259,7 @@
new/obj/effect/engine_exhaust(T, dir, is_cmb && air_contents.temperature >= PHORON_MINIMUM_BURN_TEMPERATURE)
/obj/machinery/atmospherics/unary/engine/proc/calculate_thrust(datum/gas_mixture/propellant, used_part = 1)
return round(sqrt(propellant.get_mass() * used_part * sqrt(XGM_PRESSURE(air_contents)/200)),0.1)
return round(sqrt(propellant.get_mass() * used_part * sqrt(XGM_PRESSURE(air_contents)/200)),0.01)
//Exhaust effect
/obj/effect/engine_exhaust
@@ -52,7 +52,7 @@
var/datum/ship_engine/ion/controller
var/thrust_limit = 1
var/on = 1
var/on = TRUE
var/burn_cost = 36000
var/generated_thrust = 2.5
@@ -66,11 +66,14 @@
/obj/machinery/ion_engine/proc/get_status()
. = list()
.+= "Location: [get_area(src)]."
if(!powered())
.+= "Insufficient power to operate."
. = jointext(.,"<br>")
. += list(list(
"text" = "Location: [get_area(src)].",
"severity" = "info"
))
if(!powered())
. += list(list("text" = "Insufficient power to operate.", "severity" = "bad"))
/obj/machinery/ion_engine/proc/burn(var/power_modifier = 1)
if(!on && !powered())
@@ -71,8 +71,11 @@
/obj/machinery/maneuvering_engine/proc/get_status()
. = list()
.+= "Location: [get_area(src)]."
. = jointext(.,"<br>")
. += list(list(
"text" = "Location: [get_area(src)].",
"severity" = "info"
))
/obj/machinery/maneuvering_engine/proc/burn(var/power_modifier = 1)
. = thrust_limit * generated_thrust * power_modifier
+7
View File
@@ -0,0 +1,7 @@
author: Batrachophrenoboocosmomachia
delete-after: True
changes:
- refactor: "Migrates DNA Analyzer NanoUI to TGUI."
- refactor: "Migrates Engines Control NanoUI to TGUI."
- qol: "DNA Analyzer reports now provide the date and time the scan was produced."
- bugfix: "Ship computers no longer function after their power is cut."
-45
View File
@@ -1,45 +0,0 @@
<h3>Machine Status</h3>
<div class="item">
<div class="itemContent" style="width: 20%;">
{{:helper.link(data.scanning ? 'Halt Scan' : 'Begin Scan', 'signal-diag', {'scanItem' : 1}, null)}}
</div>
<div class="itemContent" style="width: 20%;">
{{:helper.link((data.lidstate ? 'Open Lid' : 'Close Lid'), (data.lidstate ? 'unlocked' : 'locked'), {'toggleLid' : 1}, null)}}
</div>
<div class="itemContent" style="width: 20%;">
{{:helper.link('Eject item', 'eject', {'ejectItem' : 1}, (data.bloodsamp && !data.scanning) ? null : 'disabled')}}
</div>
</div>
<h3>Scanner</h3>
<div class="item">
<div class="itemLabel" style="width: 21%;">Scan progress:</div>
<div class="itemContent" style="width: 35%;">
{{:helper.displayBar(data.scan_progress, 0, 100, 'good')}}
{{:data.scan_progress}} %
</div>
<div class="itemContent" style="width: 44%;">
{{if data.scan_progress >= 100}}
<span class="notice" style="width: 100%;">Scan completed successfully.</span>
{{/if}}
</div>
</div>
<div class="statusDisplay">
<div class="line">
<div class="statusLabel">Item:</div>
<div class="statusValue">
{{if data.bloodsamp}}
<span class="good">{{:data.bloodsamp}}</span>
{{else}}
<span class="bad">No item inserted</span>
{{/if}}
</div>
</div>
<div class="line">
<div class="statusLabel">Heuristic analysis:</div>
<div class="statusValue">
{{if data.bloodsamp_desc}}
<span class="average">{{:data.bloodsamp_desc}}</span>
{{/if}}
</div>
</div>
</div>
@@ -0,0 +1,90 @@
import { useBackend } from '../backend';
import { Box, Button, LabeledList, ProgressBar, Section } from '../components';
import { round } from 'common/math';
import { Window } from '../layouts';
import { BooleanLike } from '../../common/react';
type Data = {
scan_progress: number;
scanning: BooleanLike;
bloodsamp: string;
bloodsamp_desc: string;
lid_closed: BooleanLike;
};
export const DnaForensics = (props, context) => {
const { act, data } = useBackend<Data>(context);
const hasItem = !!data.bloodsamp;
return (
<Window width={540} height={326}>
<Window.Content>
<Section title="Machine Status">
<LabeledList>
<LabeledList.Item label="Controls">
<Button
icon="signal"
disabled={!hasItem || !data.lid_closed}
onClick={() => act('scanItem')}
>
{data.scanning ? 'Halt Scan' : 'Begin Scan'}
</Button>
<Button
icon={data.lid_closed ? 'unlock' : 'lock'}
onClick={() => act('toggleLid')}
>
{data.lid_closed ? 'Open Lid' : 'Close Lid'}
</Button>
<Button
icon="eject"
disabled={!hasItem || data.scanning || data.lid_closed}
onClick={() => act('ejectItem')}
>
Eject swab
</Button>
</LabeledList.Item>
</LabeledList>
</Section>
<Section title="Scanner">
<LabeledList>
<LabeledList.Item label="Scan progress">
<ProgressBar
value={data.scan_progress}
minValue={0}
maxValue={100}
/>
<Box mt={0.5}>{round(data.scan_progress, 1)}%</Box>
</LabeledList.Item>
{data.scan_progress >= 100 && (
<LabeledList.Item>
<Box color="good">Scan completed successfully.</Box>
</LabeledList.Item>
)}
</LabeledList>
</Section>
<Section title="Status">
<LabeledList>
<LabeledList.Item label="Item">
{hasItem ? (
<Box color="good">{data.bloodsamp}</Box>
) : (
<Box color="bad">No item inserted</Box>
)}
</LabeledList.Item>
<LabeledList.Item label="Heuristic analysis">
{data.bloodsamp_desc ? (
<Box color="average">{data.bloodsamp_desc}</Box>
) : (
<Box />
)}
</LabeledList.Item>
</LabeledList>
</Section>
</Window.Content>
</Window>
);
};
@@ -0,0 +1,217 @@
import { useBackend } from '../backend';
import { Window } from '../layouts';
import {
Box,
Button,
LabeledList,
NoticeBox,
Section,
Stack,
Tabs,
} from '../components';
import { round } from 'common/math';
import { BooleanLike } from '../../common/react';
type EngineInfo = {
eng_type: string;
eng_on: BooleanLike;
eng_thrust: number;
eng_thrust_limiter: number; // percentage
eng_status: StatusLine[];
eng_reference: string;
};
type StatusLine = {
text: string;
severity?: 'good' | 'average' | 'bad';
};
type Data = {
state: 'status' | 'engines' | string;
global_state: BooleanLike;
global_limit: number; // percentage
total_thrust: number;
engines_info: EngineInfo[];
};
export const EnginesControl = (_props, context) => {
const { act, data } = useBackend<Data>(context);
const engines = data.engines_info || [];
const globalOn = !!data.global_state;
const severityToColor = (sev?: StatusLine['severity']) => {
switch (sev) {
case 'bad':
return 'bad';
case 'average':
return 'average';
case 'good':
return 'good';
default:
return undefined;
}
};
return (
<Window width={390} height={530}>
<Window.Content scrollable>
<Section>
<Tabs>
<Tabs.Tab
selected={data.state === 'status'}
onClick={() => act('set_state', { state: 'status' })}
>
Overall info
</Tabs.Tab>
<Tabs.Tab
selected={data.state === 'engines'}
onClick={() => act('set_state', { state: 'engines' })}
>
Details
</Tabs.Tab>
</Tabs>
</Section>
<Section>
<LabeledList>
<LabeledList.Item label="Global controls">
<Button
icon="power-off"
selected={globalOn}
onClick={() => act('global_toggle')}
>
{globalOn ? 'Shut all down' : 'Power all up'}
</Button>
</LabeledList.Item>
<LabeledList.Item label="Thrust limit">
<Button
icon="minus-circle"
onClick={() => act('global_limit_delta', { delta: -0.1 })}
/>
<Button onClick={() => act('set_global_limit')}>
{round(data.global_limit, 1)}%
</Button>
<Button
icon="plus-circle"
onClick={() => act('global_limit_delta', { delta: 0.1 })}
/>
</LabeledList.Item>
<LabeledList.Item label="Total thrust">
{round(data.total_thrust * 10, 0.1) / 10}
</LabeledList.Item>
</LabeledList>
</Section>
{!engines.length && <NoticeBox>No engines detected.</NoticeBox>}
{data.state === 'status' &&
engines.map((E, i) => (
<Section key={E.eng_reference} title={`Engine #${i + 1}`}>
<LabeledList>
<LabeledList.Item label="Power">
<Button
icon="power-off"
selected={!!E.eng_on}
onClick={() =>
act('engine_toggle', { engine: E.eng_reference })
}
>
{E.eng_on ? 'Shutdown' : 'Power up'}
</Button>
</LabeledList.Item>
<LabeledList.Item label="Thrust">
{round(E.eng_thrust * 10, 0.1) / 10}
</LabeledList.Item>
<LabeledList.Item label="Thrust limit">
{round(E.eng_thrust_limiter, 1)}%
</LabeledList.Item>
</LabeledList>
</Section>
))}
{data.state === 'engines' &&
engines.map((E, i) => (
<Section key={E.eng_reference} title={`Engine #${i + 1}`}>
<LabeledList>
<LabeledList.Item label="Power">
<Button
icon="power-off"
selected={!!E.eng_on}
onClick={() =>
act('engine_toggle', { engine: E.eng_reference })
}
>
{E.eng_on ? 'Shutdown' : 'Power up'}
</Button>
</LabeledList.Item>
<LabeledList.Item label="Type">{E.eng_type}</LabeledList.Item>
<LabeledList.Item label="Status">
<Box color={E.eng_on ? 'good' : 'average'}>
{E.eng_on ? 'Online' : 'Offline'}
</Box>
<Box>
{E.eng_status?.length ? (
E.eng_status.map((line, idx) => (
<Box key={idx} color={severityToColor(line.severity)}>
{line.text}
</Box>
))
) : (
<Box>-</Box>
)}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Current thrust">
{round(E.eng_thrust * 10, 0.1) / 10}
</LabeledList.Item>
<LabeledList.Item label="Thrust limit">
<Stack align="center">
<Stack.Item>
<Button
icon="minus-circle"
onClick={() =>
act('engine_limit_delta', {
engine: E.eng_reference,
delta: -0.1,
})
}
/>
</Stack.Item>
<Stack.Item>
<Button
onClick={() =>
act('engine_set_limit', { engine: E.eng_reference })
}
>
{round(E.eng_thrust_limiter, 1)}%
</Button>
</Stack.Item>
<Stack.Item>
<Button
icon="plus-circle"
onClick={() =>
act('engine_limit_delta', {
engine: E.eng_reference,
delta: 0.1,
})
}
/>
</Stack.Item>
</Stack>
</LabeledList.Item>
</LabeledList>
</Section>
))}
</Window.Content>
</Window>
);
};
export default EnginesControl;