Adds four TGUI Next UIs

This commit is contained in:
Persi
2020-02-24 03:52:10 -05:00
parent 5037f28eaf
commit 51c0f500ef
7 changed files with 809 additions and 24 deletions
+33 -22
View File
@@ -207,11 +207,14 @@
/obj/machinery/sleeper/ui_data()
var/list/data = list()
var/chemical_list = list()
var/blood_percent = 0
data["occupied"] = occupant ? 1 : 0
data["open"] = state_open
data["efficiency"] = efficiency
data["current_vol"] = reagents.total_volume
data["tot_capacity"] = reagents.maximum_volume
data["blood_levels"] = blood_percent
data["blood_status"] = "Patient either has no blood, or does not require it to function."
data["chemical_list"] = chemical_list
data["chems"] = list()
for(var/chem in available_chems)
@@ -248,9 +251,13 @@
data["occupant"]["cloneLoss"] = mob_occupant.getCloneLoss()
data["occupant"]["brainLoss"] = mob_occupant.getOrganLoss(ORGAN_SLOT_BRAIN)
data["occupant"]["reagents"] = list()
if(mob_occupant.reagents && mob_occupant.reagents.reagent_list.len)
if(mob_occupant.reagents.reagent_list.len)
for(var/datum/reagent/R in mob_occupant.reagents.reagent_list)
data["occupant"]["reagents"] += list(list("name" = R.name, "volume" = R.volume))
chemical_list += list(list("name" = R.name, "volume" = R.volume))
else
chemical_list = "Patient has no reagents."
data["occupant"]["failing_organs"] = list()
var/mob/living/carbon/C = mob_occupant
if(C)
@@ -259,21 +266,25 @@
continue
data["occupant"]["failing_organs"] += list(list("name" = Or.name))
if(mob_occupant.has_dna()) // Blood-stuff is mostly a copy-paste from the healthscanner.
var/blood_id = C.get_blood_id()
if(blood_id)
data["occupant"]["blood"] = list() // We can start populating this list.
var/blood_type = C.dna.blood_type
if(!(blood_id in GLOB.blood_reagent_types)) // special blood substance
var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id]
if(R)
blood_type = R.name
else
blood_type = blood_id
data["occupant"]["blood"]["maxBloodVolume"] = (BLOOD_VOLUME_NORMAL*C.blood_ratio)
data["occupant"]["blood"]["currentBloodVolume"] = C.blood_volume
data["occupant"]["blood"]["dangerBloodVolume"] = BLOOD_VOLUME_SAFE
data["occupant"]["blood"]["bloodType"] = blood_type
if(istype(C)) //Non-carbons shouldn't be able to enter sleepers, but this is to prevent runtimes if something ever breaks
if(mob_occupant.has_dna()) // Blood-stuff is mostly a copy-paste from the healthscanner.
blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL)*100)
var/blood_id = C.get_blood_id()
var/blood_warning = ""
if(blood_percent < 80)
blood_warning = "Patient has low blood levels."
if(blood_percent < 60)
blood_warning = "Patient has DANGEROUSLY low blood levels."
if(blood_id)
var/blood_type = C.dna.blood_type
if(!(blood_id in GLOB.blood_reagent_types)) // special blood substance
var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id]
if(R)
blood_type = R.name
else
blood_type = blood_id
data["blood_status"] = "Patient has [blood_type] type blood. [blood_warning]"
data["blood_levels"] = blood_percent - (rand(1,35))
return data
/obj/machinery/sleeper/ui_act(action, params)
@@ -309,14 +320,14 @@
if(allowed(usr))
if(!is_operational())
return
reagents.remove_reagent(chem, 10)
reagents.remove_reagent(chem, 1000)
return
if(chem in available_chems)
if(!is_operational())
return
/*var/datum/reagent/R = reagents.has_reagent(chem) //For when purity effects are in
if(R.purity < 0.8)*/
reagents.remove_reagent(chem, 10)
reagents.remove_reagent(chem, 1000)
else
visible_message("<span class='warning'>Access Denied.</span>")
playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, 0)
@@ -0,0 +1,180 @@
import { toFixed } from 'common/math';
import { toTitleCase } from 'common/string';
import { Fragment } from 'inferno';
import { useBackend } from '../backend';
import { AnimatedNumber, Box, Button, Icon, LabeledList, ProgressBar, Section } from '../components';
export const ChemDispenser = props => {
const { act, data } = useBackend(props);
const recording = !!data.recordingRecipe;
// TODO: Change how this piece of shit is built on server side
// It has to be a list, not a fucking OBJECT!
const recipes = Object.keys(data.recipes)
.map(name => ({
name,
contents: data.recipes[name],
}));
const beakerTransferAmounts = data.beakerTransferAmounts || [];
const beakerContents = recording
&& Object.keys(data.recordingRecipe)
.map(id => ({
id,
name: toTitleCase(id.replace(/_/, ' ')),
volume: data.recordingRecipe[id],
}))
|| data.beakerContents
|| [];
return (
<Fragment>
<Section
title="Status"
buttons={recording && (
<Box inline mx={1} color="red">
<Icon name="circle" mr={1} />
Recording
</Box>
)}>
<LabeledList>
<LabeledList.Item label="Energy">
<ProgressBar
value={data.energy / data.maxEnergy}
content={toFixed(data.energy) + ' units'} />
</LabeledList.Item>
</LabeledList>
</Section>
<Section
title="Recipes"
buttons={(
<Fragment>
{!recording && (
<Box inline mx={1}>
<Button
color="transparent"
content="Clear recipes"
onClick={() => act('clear_recipes')} />
</Box>
)}
{!recording && (
<Button
icon="circle"
disabled={!data.isBeakerLoaded}
content="Record"
onClick={() => act('record_recipe')} />
)}
{recording && (
<Button
icon="ban"
color="transparent"
content="Discard"
onClick={() => act('cancel_recording')} />
)}
{recording && (
<Button
icon="save"
color="green"
content="Save"
onClick={() => act('save_recording')} />
)}
</Fragment>
)}>
<Box mr={-1}>
{recipes.map(recipe => (
<Button key={recipe.name}
icon="tint"
width="129.5px"
lineHeight="21px"
content={recipe.name}
onClick={() => act('dispense_recipe', {
recipe: recipe.name,
})} />
))}
{recipes.length === 0 && (
<Box color="light-gray">
No recipes.
</Box>
)}
</Box>
</Section>
<Section
title="Dispense"
buttons={(
beakerTransferAmounts.map(amount => (
<Button key={amount}
icon="plus"
selected={amount === data.amount}
content={amount}
onClick={() => act('amount', {
target: amount,
})} />
))
)}>
<Box mr={-1}>
{data.chemicals.map(chemical => (
<Button key={chemical.id}
icon="tint"
width="129.5px"
lineHeight="21px"
content={chemical.title}
onClick={() => act('dispense', {
reagent: chemical.id,
})} />
))}
</Box>
</Section>
<Section
title="Beaker"
buttons={(
beakerTransferAmounts.map(amount => (
<Button key={amount}
icon="minus"
disabled={recording}
content={amount}
onClick={() => act('remove', { amount })} />
))
)}>
<LabeledList>
<LabeledList.Item
label="Beaker"
buttons={!!data.isBeakerLoaded && (
<Button
icon="eject"
content="Eject"
disabled={!data.isBeakerLoaded}
onClick={() => act('eject')} />
)}>
{recording
&& 'Virtual beaker'
|| data.isBeakerLoaded
&& (
<Fragment>
<AnimatedNumber
initial={0}
value={data.beakerCurrentVolume} />
/{data.beakerMaxVolume} units
</Fragment>
)
|| 'No beaker'}
</LabeledList.Item>
<LabeledList.Item
label="Contents">
<Box color="label">
{(!data.isBeakerLoaded && !recording) && 'N/A'
|| beakerContents.length === 0 && 'Nothing'}
</Box>
{beakerContents.map(chemical => (
<Box
key={chemical.name}
color="label">
<AnimatedNumber
initial={0}
value={chemical.volume} />
{' '}
units of {chemical.name}
</Box>
))}
</LabeledList.Item>
</LabeledList>
</Section>
</Fragment>
);
};
@@ -0,0 +1,75 @@
import { round, toFixed } from 'common/math';
import { Fragment } from 'inferno';
import { useBackend } from '../backend';
import { AnimatedNumber, Box, Button, LabeledList, NumberInput, Section } from '../components';
import { BeakerContents } from './common/BeakerContents';
export const ChemHeater = props => {
const { act, data } = useBackend(props);
const {
targetTemp,
isActive,
isBeakerLoaded,
currentTemp,
beakerCurrentVolume,
beakerMaxVolume,
beakerContents = [],
} = data;
return (
<Fragment>
<Section
title="Thermostat"
buttons={(
<Button
icon={isActive ? 'power-off' : 'times'}
selected={isActive}
content={isActive ? 'On' : 'Off'}
onClick={() => act('power')} />
)}>
<LabeledList>
<LabeledList.Item label="Target">
<NumberInput
width="65px"
unit="K"
step={2}
stepPixelSize={1}
value={round(targetTemp)}
minValue={0}
maxValue={1000}
onDrag={(e, value) => act('temperature', {
target: value,
})} />
</LabeledList.Item>
<LabeledList.Item label="Reading">
<Box
width="60px"
textAlign="right">
{isBeakerLoaded && (
<AnimatedNumber
value={currentTemp}
format={value => toFixed(value) + ' K'} />
) || '—'}
</Box>
</LabeledList.Item>
</LabeledList>
</Section>
<Section
title="Beaker"
buttons={!!isBeakerLoaded && (
<Fragment>
<Box inline color="label" mr={2}>
{beakerCurrentVolume} / {beakerMaxVolume} units
</Box>
<Button
icon="eject"
content="Eject"
onClick={() => act('eject')} />
</Fragment>
)}>
<BeakerContents
beakerLoaded={isBeakerLoaded}
beakerContents={beakerContents} />
</Section>
</Fragment>
);
};
@@ -0,0 +1,322 @@
import { Component, Fragment } from 'inferno';
import { act } from '../byond';
import { AnimatedNumber, Box, Button, ColorBox, LabeledList, NumberInput, Section, Table } from '../components';
export const ChemMaster = props => {
const { state } = props;
const { config, data } = state;
const { ref } = config;
const {
screen,
beakerContents = [],
bufferContents = [],
beakerCurrentVolume,
beakerMaxVolume,
isBeakerLoaded,
isPillBottleLoaded,
pillBottleCurrentAmount,
pillBottleMaxAmount,
} = data;
return (
<Fragment>
<Section
title="Beaker"
buttons={!!data.isBeakerLoaded && (
<Fragment>
<Box inline color="label" mr={2}>
<AnimatedNumber
value={beakerCurrentVolume}
initial={0} />
{` / ${beakerMaxVolume} units`}
</Box>
<Button
icon="eject"
content="Eject"
onClick={() => act(ref, 'eject')} />
</Fragment>
)}>
{!isBeakerLoaded && (
<Box color="label" mt="3px" mb="5px">
No beaker loaded.
</Box>
)}
{!!isBeakerLoaded && beakerContents.length === 0 && (
<Box color="label" mt="3px" mb="5px">
Beaker is empty.
</Box>
)}
<ChemicalBuffer>
{beakerContents.map(chemical => (
<ChemicalBufferEntry
key={chemical.id}
state={state}
chemical={chemical}
transferTo="buffer" />
))}
</ChemicalBuffer>
</Section>
<Section
title="Buffer"
buttons={(
<Fragment>
<Box inline color="label" mr={1}>
Mode:
</Box>
<Button
color={data.mode ? 'good' : 'bad'}
icon={data.mode ? 'exchange-alt' : 'times'}
content={data.mode ? 'Transfer' : 'Destroy'}
onClick={() => act(ref, 'toggleMode')} />
</Fragment>
)}>
{bufferContents.length === 0 && (
<Box color="label" mt="3px" mb="5px">
Buffer is empty.
</Box>
)}
<ChemicalBuffer>
{bufferContents.map(chemical => (
<ChemicalBufferEntry
key={chemical.id}
state={state}
chemical={chemical}
transferTo="beaker" />
))}
</ChemicalBuffer>
</Section>
<Section
title="Packaging">
<PackagingControls state={state} />
</Section>
{!!isPillBottleLoaded && (
<Section
title="Pill Bottle"
buttons={(
<Fragment>
<Box inline color="label" mr={2}>
{pillBottleCurrentAmount} / {pillBottleMaxAmount} pills
</Box>
<Button
icon="eject"
content="Eject"
onClick={() => act(ref, 'ejectPillBottle')} />
</Fragment>
)} />
)}
</Fragment>
);
};
const ChemicalBuffer = Table;
const ChemicalBufferEntry = props => {
const { state, chemical, transferTo } = props;
const { ref } = state.config;
return (
<Table.Row key={chemical.id}>
<Table.Cell color="label">
<AnimatedNumber
value={chemical.volume}
initial={0} />
{` units of ${chemical.name}`}
</Table.Cell>
<Table.Cell collapsing>
<Button
content="1"
onClick={() => act(ref, 'transfer', {
id: chemical.id,
amount: 1,
to: transferTo,
})} />
<Button
content="5"
onClick={() => act(ref, 'transfer', {
id: chemical.id,
amount: 5,
to: transferTo,
})} />
<Button
content="10"
onClick={() => act(ref, 'transfer', {
id: chemical.id,
amount: 10,
to: transferTo,
})} />
<Button
content="All"
onClick={() => act(ref, 'transfer', {
id: chemical.id,
amount: 1000,
to: transferTo,
})} />
<Button
icon="ellipsis-h"
title="Custom amount"
onClick={() => act(ref, 'transfer', {
id: chemical.id,
amount: -1,
to: transferTo,
})} />
<Button
icon="question"
title="Analyze"
onClick={() => act(ref, 'analyze', {
id: chemical.id,
})} />
</Table.Cell>
</Table.Row>
);
};
const PackagingControlsItem = props => {
const {
label,
amountUnit,
amount,
onChangeAmount,
onCreate,
sideNote,
} = props;
return (
<LabeledList.Item label={label}>
<NumberInput
width={14}
unit={amountUnit}
step={1}
stepPixelSize={15}
value={amount}
minValue={1}
maxValue={10}
onChange={onChangeAmount} />
<Button ml={1}
content="Create"
onClick={onCreate} />
<Box inline ml={1}
color="label"
content={sideNote} />
</LabeledList.Item>
);
};
class PackagingControls extends Component {
constructor() {
super();
this.state = {
pillAmount: 1,
patchAmount: 1,
bottleAmount: 1,
packAmount: 1,
};
}
render() {
const { state, props } = this;
const { ref } = props.state.config;
const {
pillAmount,
patchAmount,
bottleAmount,
packAmount,
} = this.state;
const {
condi,
chosenPillStyle,
pillStyles = [],
} = props.state.data;
return (
<LabeledList>
{!condi && (
<LabeledList.Item label="Pill type">
{pillStyles.map(pill => (
<Button
key={pill.id}
width={5}
selected={pill.id === chosenPillStyle}
textAlign="center"
color="transparent"
onClick={() => act(ref, 'pillStyle', { id: pill.id })}>
<Box mx={-1} className={pill.className} />
</Button>
))}
</LabeledList.Item>
)}
{!condi && (
<PackagingControlsItem
label="Pills"
amount={pillAmount}
amountUnit="pills"
sideNote="max 50u"
onChangeAmount={(e, value) => this.setState({
pillAmount: value,
})}
onCreate={() => act(ref, 'create', {
type: 'pill',
amount: pillAmount,
volume: 'auto',
})} />
)}
{!condi && (
<PackagingControlsItem
label="Patches"
amount={patchAmount}
amountUnit="patches"
sideNote="max 40u"
onChangeAmount={(e, value) => this.setState({
patchAmount: value,
})}
onCreate={() => act(ref, 'create', {
type: 'patch',
amount: patchAmount,
volume: 'auto',
})} />
)}
{!condi && (
<PackagingControlsItem
label="Bottles"
amount={bottleAmount}
amountUnit="bottles"
sideNote="max 30u"
onChangeAmount={(e, value) => this.setState({
bottleAmount: value,
})}
onCreate={() => act(ref, 'create', {
type: 'bottle',
amount: bottleAmount,
volume: 'auto',
})} />
)}
{!!condi && (
<PackagingControlsItem
label="Packs"
amount={packAmount}
amountUnit="packs"
sideNote="max 10u"
onChangeAmount={(e, value) => this.setState({
packAmount: value,
})}
onCreate={() => act(ref, 'create', {
type: 'condimentPack',
amount: packAmount,
volume: 'auto',
})} />
)}
{!!condi && (
<PackagingControlsItem
label="Bottles"
amount={bottleAmount}
amountUnit="bottles"
sideNote="max 50u"
onChangeAmount={(e, value) => this.setState({
bottleAmount: value,
})}
onCreate={() => act(ref, 'create', {
type: 'condimentBottle',
amount: bottleAmount,
volume: 'auto',
})} />
)}
</LabeledList>
);
}
}
@@ -0,0 +1,181 @@
import { useBackend } from '../backend';
import { Box, Section, LabeledList, Button, ProgressBar, Flex, AnimatedNumber } from '../components';
import { Fragment } from 'inferno';
export const StasisSleeper = props => {
const { act, data } = useBackend(props);
const {
occupied,
open,
occupant = [],
} = data;
const preSortChems = data.chems || [];
const chems = preSortChems.sort((a, b) => {
const descA = a.name.toLowerCase();
const descB = b.name.toLowerCase();
if (descA < descB) {
return -1;
}
if (descA > descB) {
return 1;
}
return 0;
});
const synthchems = preSortChems.sort((a, b) => {
const descA = a.name.toLowerCase();
const descB = b.name.toLowerCase();
if (descA < descB) {
return -1;
}
if (descA > descB) {
return 1;
}
return 0;
});
const damageTypes = [
{
label: 'Brute',
type: 'bruteLoss',
},
{
label: 'Burn',
type: 'fireLoss',
},
{
label: 'Toxin',
type: 'toxLoss',
},
{
label: 'Oxygen',
type: 'oxyLoss',
},
];
return (
<Fragment>
<Section
title={occupant.name ? occupant.name : 'No Occupant'}
minHeight="210px"
buttons={!!occupant.stat && (
<Box
inline
bold
color={occupant.statstate}>
{occupant.stat}
</Box>
)}>
{!!occupied && (
<Fragment>
<ProgressBar
value={occupant.health}
minValue={occupant.minHealth}
maxValue={occupant.maxHealth}
ranges={{
good: [50, Infinity],
average: [0, 50],
bad: [-Infinity, 0],
}} />
<Box mt={1} />
<LabeledList>
{damageTypes.map(type => (
<LabeledList.Item
key={type.type}
label={type.label}>
<ProgressBar
value={occupant[type.type]}
minValue={0}
maxValue={occupant.maxHealth}
color="bad" />
</LabeledList.Item>
))}
<LabeledList.Item
label={'Blood'}>
<ProgressBar
value={data.blood_levels/100}
color="bad">
<AnimatedNumber value={data.blood_levels} />
</ProgressBar>
{data.blood_status}
</LabeledList.Item>
<LabeledList.Item
label="Cells"
color={occupant.cloneLoss ? 'bad' : 'good'}>
{occupant.cloneLoss ? 'Damaged' : 'Healthy'}
</LabeledList.Item>
<LabeledList.Item
label="Brain"
color={occupant.brainLoss ? 'bad' : 'good'}>
{occupant.brainLoss ? 'Abnormal' : 'Healthy'}
</LabeledList.Item>
</LabeledList>
</Fragment>
)}
</Section>
<Section title="Chemical Analysis">
<LabeledList.Item label="Chemical Contents">
{data.chemical_list.map(specificChem => (
<Box
key={specificChem.id}
color="good" >
{specificChem.volume} units of {specificChem.name}
</Box>
),
)}
</LabeledList.Item>
</Section>
<Section
title="Inject Chemicals"
minHeight="105px"
buttons={(
<Button
icon={open ? 'door-open' : 'door-closed'}
content={open ? 'Open' : 'Closed'}
onClick={() => act('door')} />
)}>
{chems.map(chem => (
<Button
key={chem.name}
icon="flask"
content={chem.name}
disabled={!(occupied && chem.allowed)}
width="140px"
onClick={() => act('inject', {
chem: chem.id,
})}
/>
))}
</Section>
<Section
title="Synthesize Chemicals">
{synthchems.map(chem => (
<Button
key={chem.name}
content={chem.name}
disabled={!(chem.allowed)}
width="140px"
onClick={() => act('synth', {
chem: chem.id,
})}
/>
))}
</Section>
<Section
title="Purge Chemicals">
{chems.map(chem => (
<Button
key={chem.name}
content={chem.name}
disabled={!(chem.allowed)}
width="140px"
onClick={() => act('purge', {
chem: chem.id,
})}
/>
))}
</Section>
</Fragment>
);
};
File diff suppressed because one or more lines are too long
+16
View File
@@ -19,7 +19,10 @@ import { CellularEmporium } from './interfaces/CellularEmporium';
import { CentcomPodLauncher } from './interfaces/CentcomPodLauncher';
import { ChemAcclimator } from './interfaces/ChemAcclimator';
import { ChemDebugSynthesizer } from './interfaces/ChemDebugSynthesizer';
import { ChemDispenser } from './interfaces/ChemDispenser';
import { ChemFilter } from './interfaces/ChemFilter';
import { ChemHeater } from './interfaces/ChemHeater';
import { ChemMaster } from './interfaces/ChemMaster';
import { ChemPress } from './interfaces/ChemPress';
import { ChemReactionChamber } from './interfaces/ChemReactionChamber';
import { ChemSplitter } from './interfaces/ChemSplitter';
@@ -76,6 +79,7 @@ import { RapidPipeDispenser } from './interfaces/RapidPipeDispenser';
import { SatelliteControl } from './interfaces/SatelliteControl';
import { ScannerGate } from './interfaces/ScannerGate';
import { ShuttleManipulator } from './interfaces/ShuttleManipulator';
import { Sleeper } from './interfaces/Sleeper';
import { SlimeBodySwapper } from './interfaces/SlimeBodySwapper';
import { Signaler } from './interfaces/Signaler';
import { SmartVend } from './interfaces/SmartVend';
@@ -185,10 +189,22 @@ const ROUTES = {
component: () => ChemAcclimator,
scrollable: false,
},
chem_dispenser: {
component: () => ChemDispenser,
scrollable: true,
},
chemical_filter: {
component: () => ChemFilter,
scrollable: true,
},
chem_heater: {
component: () => ChemHeater,
scrollable: true,
},
chem_master: {
component: () => ChemMaster,
scrollable: true,
},
chem_press: {
component: () => ChemPress,
scrollable: false,