mirror of
https://github.com/quotefox/Hyper-Station-13.git
synced 2026-08-30 16:16:15 +01:00
462 lines
12 KiB
JavaScript
462 lines
12 KiB
JavaScript
import { toFixed } from 'common/math';
|
|
import { decodeHtmlEntities } from 'common/string';
|
|
import { Fragment } from 'inferno';
|
|
import { useBackend } from '../backend';
|
|
import { Box, Button, LabeledList, NumberInput, Section } from '../components';
|
|
import { getGasLabel } from '../constants';
|
|
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';
|
|
|
|
export const AirAlarm = props => {
|
|
const { state } = props;
|
|
const { act, data } = useBackend(props);
|
|
const locked = data.locked && !data.siliconUser;
|
|
return (
|
|
<Fragment>
|
|
<InterfaceLockNoticeBox
|
|
siliconUser={data.siliconUser}
|
|
locked={data.locked}
|
|
onLockStatusChange={() => act('lock')} />
|
|
<AirAlarmStatus state={state} />
|
|
{!locked && (
|
|
<AirAlarmControl state={state} />
|
|
)}
|
|
</Fragment>
|
|
);
|
|
};
|
|
|
|
const AirAlarmStatus = props => {
|
|
const { data } = useBackend(props);
|
|
const entries = (data.environment_data || [])
|
|
.filter(entry => entry.value >= 0.01);
|
|
const dangerMap = {
|
|
0: {
|
|
color: 'good',
|
|
localStatusText: 'Optimal',
|
|
},
|
|
1: {
|
|
color: 'average',
|
|
localStatusText: 'Caution',
|
|
},
|
|
2: {
|
|
color: 'bad',
|
|
localStatusText: 'Danger (Internals Required)',
|
|
},
|
|
};
|
|
const localStatus = dangerMap[data.danger_level] || dangerMap[0];
|
|
return (
|
|
<Section title="Air Status">
|
|
<LabeledList>
|
|
{entries.length > 0 && (
|
|
<Fragment>
|
|
{entries.map(entry => {
|
|
const status = dangerMap[entry.danger_level] || dangerMap[0];
|
|
return (
|
|
<LabeledList.Item
|
|
key={entry.name}
|
|
label={entry.name}
|
|
color={status.color}>
|
|
{toFixed(entry.value, 2)}{entry.unit}
|
|
</LabeledList.Item>
|
|
);
|
|
})}
|
|
<LabeledList.Item
|
|
label="Local status"
|
|
color={localStatus.color}>
|
|
{localStatus.localStatusText}
|
|
</LabeledList.Item>
|
|
<LabeledList.Item
|
|
label="Area status"
|
|
color={data.atmos_alarm || data.fire_alarm ? 'bad' : 'good'}>
|
|
{data.atmos_alarm && 'Atmosphere Alarm'
|
|
|| data.fire_alarm && 'Fire Alarm'
|
|
|| 'Nominal'}
|
|
</LabeledList.Item>
|
|
</Fragment>
|
|
) || (
|
|
<LabeledList.Item
|
|
label="Warning"
|
|
color="bad">
|
|
Cannot obtain air sample for analysis.
|
|
</LabeledList.Item>
|
|
)}
|
|
{!!data.emagged && (
|
|
<LabeledList.Item
|
|
label="Warning"
|
|
color="bad">
|
|
Safety measures offline. Device may exhibit abnormal behavior.
|
|
</LabeledList.Item>
|
|
)}
|
|
</LabeledList>
|
|
</Section>
|
|
);
|
|
};
|
|
|
|
const AIR_ALARM_ROUTES = {
|
|
home: {
|
|
title: 'Air Controls',
|
|
component: () => AirAlarmControlHome,
|
|
},
|
|
vents: {
|
|
title: 'Vent Controls',
|
|
component: () => AirAlarmControlVents,
|
|
},
|
|
scrubbers: {
|
|
title: 'Scrubber Controls',
|
|
component: () => AirAlarmControlScrubbers,
|
|
},
|
|
modes: {
|
|
title: 'Operating Mode',
|
|
component: () => AirAlarmControlModes,
|
|
},
|
|
thresholds: {
|
|
title: 'Alarm Thresholds',
|
|
component: () => AirAlarmControlThresholds,
|
|
},
|
|
};
|
|
|
|
const AirAlarmControl = props => {
|
|
const { state } = props;
|
|
const { act, config } = useBackend(props);
|
|
const route = AIR_ALARM_ROUTES[config.screen] || AIR_ALARM_ROUTES.home;
|
|
const Component = route.component();
|
|
return (
|
|
<Section
|
|
title={route.title}
|
|
buttons={config.screen !== 'home' && (
|
|
<Button
|
|
icon="arrow-left"
|
|
content="Back"
|
|
onClick={() => act('tgui:view', {
|
|
screen: 'home',
|
|
})} />
|
|
)}>
|
|
<Component state={state} />
|
|
</Section>
|
|
);
|
|
};
|
|
|
|
|
|
// Home screen
|
|
// --------------------------------------------------------
|
|
|
|
const AirAlarmControlHome = props => {
|
|
const { act, data } = useBackend(props);
|
|
const {
|
|
mode,
|
|
atmos_alarm,
|
|
} = data;
|
|
return (
|
|
<Fragment>
|
|
<Button
|
|
icon={atmos_alarm
|
|
? 'exclamation-triangle'
|
|
: 'exclamation'}
|
|
color={atmos_alarm && 'caution'}
|
|
content="Area Atmosphere Alarm"
|
|
onClick={() => act(atmos_alarm ? 'reset' : 'alarm')} />
|
|
<Box mt={1} />
|
|
<Button
|
|
icon={mode === 3
|
|
? 'exclamation-triangle'
|
|
: 'exclamation'}
|
|
color={mode === 3 && 'danger'}
|
|
content="Panic Siphon"
|
|
onClick={() => act('mode', {
|
|
mode: mode === 3 ? 1 : 3,
|
|
})} />
|
|
<Box mt={2} />
|
|
<Button
|
|
icon="sign-out-alt"
|
|
content="Vent Controls"
|
|
onClick={() => act('tgui:view', {
|
|
screen: 'vents',
|
|
})} />
|
|
<Box mt={1} />
|
|
<Button
|
|
icon="filter"
|
|
content="Scrubber Controls"
|
|
onClick={() => act('tgui:view', {
|
|
screen: 'scrubbers',
|
|
})} />
|
|
<Box mt={1} />
|
|
<Button
|
|
icon="cog"
|
|
content="Operating Mode"
|
|
onClick={() => act('tgui:view', {
|
|
screen: 'modes',
|
|
})} />
|
|
<Box mt={1} />
|
|
<Button
|
|
icon="chart-bar"
|
|
content="Alarm Thresholds"
|
|
onClick={() => act('tgui:view', {
|
|
screen: 'thresholds',
|
|
})} />
|
|
</Fragment>
|
|
);
|
|
};
|
|
|
|
|
|
// Vents
|
|
// --------------------------------------------------------
|
|
|
|
const AirAlarmControlVents = props => {
|
|
const { state } = props;
|
|
const { data } = useBackend(props);
|
|
const { vents } = data;
|
|
if (!vents || vents.length === 0) {
|
|
return 'Nothing to show';
|
|
}
|
|
return vents.map(vent => (
|
|
<Vent key={vent.id_tag}
|
|
state={state}
|
|
{...vent} />
|
|
));
|
|
};
|
|
|
|
const Vent = props => {
|
|
const {
|
|
id_tag,
|
|
long_name,
|
|
power,
|
|
checks,
|
|
excheck,
|
|
incheck,
|
|
direction,
|
|
external,
|
|
internal,
|
|
extdefault,
|
|
intdefault,
|
|
} = props;
|
|
const { act } = useBackend(props);
|
|
return (
|
|
<Section
|
|
level={2}
|
|
title={decodeHtmlEntities(long_name)}
|
|
buttons={(
|
|
<Button
|
|
icon={power ? 'power-off' : 'times'}
|
|
selected={power}
|
|
content={power ? 'On' : 'Off'}
|
|
onClick={() => act('power', {
|
|
id_tag,
|
|
val: Number(!power),
|
|
})} />
|
|
)}>
|
|
<LabeledList>
|
|
<LabeledList.Item label="Mode">
|
|
{direction === 'release' ? 'Pressurizing' : 'Releasing'}
|
|
</LabeledList.Item>
|
|
<LabeledList.Item label="Pressure Regulator">
|
|
<Button
|
|
icon="sign-in-alt"
|
|
content="Internal"
|
|
selected={incheck}
|
|
onClick={() => act('incheck', {
|
|
id_tag,
|
|
val: checks,
|
|
})} />
|
|
<Button
|
|
icon="sign-out-alt"
|
|
content="External"
|
|
selected={excheck}
|
|
onClick={() => act('excheck', {
|
|
id_tag,
|
|
val: checks,
|
|
})} />
|
|
</LabeledList.Item>
|
|
{!!incheck && (
|
|
<LabeledList.Item label="Internal Target">
|
|
<NumberInput
|
|
value={Math.round(internal)}
|
|
unit="kPa"
|
|
width="75px"
|
|
minValue={0}
|
|
step={10}
|
|
maxValue={5066}
|
|
onChange={(e, value) => act('set_internal_pressure', {
|
|
id_tag,
|
|
value,
|
|
})} />
|
|
<Button
|
|
icon="undo"
|
|
disabled={intdefault}
|
|
content="Reset"
|
|
onClick={() => act('reset_internal_pressure', {
|
|
id_tag,
|
|
})} />
|
|
</LabeledList.Item>
|
|
)}
|
|
{!!excheck && (
|
|
<LabeledList.Item label="External Target">
|
|
<NumberInput
|
|
value={Math.round(external)}
|
|
unit="kPa"
|
|
width="75px"
|
|
minValue={0}
|
|
step={10}
|
|
maxValue={5066}
|
|
onChange={(e, value) => act('set_external_pressure', {
|
|
id_tag,
|
|
value,
|
|
})} />
|
|
<Button
|
|
icon="undo"
|
|
disabled={extdefault}
|
|
content="Reset"
|
|
onClick={() => act('reset_external_pressure', {
|
|
id_tag,
|
|
})} />
|
|
</LabeledList.Item>
|
|
)}
|
|
</LabeledList>
|
|
</Section>
|
|
);
|
|
};
|
|
|
|
|
|
// Scrubbers
|
|
// --------------------------------------------------------
|
|
|
|
const AirAlarmControlScrubbers = props => {
|
|
const { state } = props;
|
|
const { data } = useBackend(props);
|
|
const { scrubbers } = data;
|
|
if (!scrubbers || scrubbers.length === 0) {
|
|
return 'Nothing to show';
|
|
}
|
|
return scrubbers.map(scrubber => (
|
|
<Scrubber
|
|
key={scrubber.id_tag}
|
|
state={state}
|
|
{...scrubber} />
|
|
));
|
|
};
|
|
|
|
const Scrubber = props => {
|
|
const {
|
|
long_name,
|
|
power,
|
|
scrubbing,
|
|
id_tag,
|
|
widenet,
|
|
filter_types,
|
|
} = props;
|
|
const { act } = useBackend(props);
|
|
return (
|
|
<Section
|
|
level={2}
|
|
title={decodeHtmlEntities(long_name)}
|
|
buttons={(
|
|
<Button
|
|
icon={power ? 'power-off' : 'times'}
|
|
content={power ? 'On' : 'Off'}
|
|
selected={power}
|
|
onClick={() => act('power', {
|
|
id_tag,
|
|
val: Number(!power),
|
|
})} />
|
|
)}>
|
|
<LabeledList>
|
|
<LabeledList.Item label="Mode">
|
|
<Button
|
|
icon={scrubbing ? 'filter' : 'sign-in-alt'}
|
|
color={scrubbing || 'danger'}
|
|
content={scrubbing ? 'Scrubbing' : 'Siphoning'}
|
|
onClick={() => act('scrubbing', {
|
|
id_tag,
|
|
val: Number(!scrubbing),
|
|
})} />
|
|
<Button
|
|
icon={widenet ? 'expand' : 'compress'}
|
|
selected={widenet}
|
|
content={widenet ? 'Expanded range' : 'Normal range'}
|
|
onClick={() => act('widenet', {
|
|
id_tag,
|
|
val: Number(!widenet),
|
|
})} />
|
|
</LabeledList.Item>
|
|
<LabeledList.Item label="Filters">
|
|
{scrubbing
|
|
&& filter_types.map(filter => (
|
|
<Button key={filter.gas_id}
|
|
icon={filter.enabled ? 'check-square-o' : 'square-o'}
|
|
content={getGasLabel(filter.gas_id, filter.gas_name)}
|
|
title={filter.gas_name}
|
|
selected={filter.enabled}
|
|
onClick={() => act('toggle_filter', {
|
|
id_tag,
|
|
val: filter.gas_id,
|
|
})} />
|
|
))
|
|
|| 'N/A'}
|
|
</LabeledList.Item>
|
|
</LabeledList>
|
|
</Section>
|
|
);
|
|
};
|
|
|
|
|
|
// Modes
|
|
// --------------------------------------------------------
|
|
|
|
const AirAlarmControlModes = props => {
|
|
const { act, data } = useBackend(props);
|
|
const { modes } = data;
|
|
if (!modes || modes.length === 0) {
|
|
return 'Nothing to show';
|
|
}
|
|
return modes.map(mode => (
|
|
<Fragment key={mode.mode}>
|
|
<Button
|
|
icon={mode.selected ? 'check-square-o' : 'square-o'}
|
|
selected={mode.selected}
|
|
color={mode.selected && mode.danger && 'danger'}
|
|
content={mode.name}
|
|
onClick={() => act('mode', { mode: mode.mode })} />
|
|
<Box mt={1} />
|
|
</Fragment>
|
|
));
|
|
};
|
|
|
|
|
|
// Thresholds
|
|
// --------------------------------------------------------
|
|
|
|
const AirAlarmControlThresholds = props => {
|
|
const { act, data } = useBackend(props);
|
|
const { thresholds } = data;
|
|
return (
|
|
<table
|
|
className="LabeledList"
|
|
style={{ width: '100%' }}>
|
|
<thead>
|
|
<tr>
|
|
<td />
|
|
<td className="color-bad">min2</td>
|
|
<td className="color-average">min1</td>
|
|
<td className="color-average">max1</td>
|
|
<td className="color-bad">max2</td>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{thresholds.map(threshold => (
|
|
<tr key={threshold.name}>
|
|
<td className="LabeledList__label">{threshold.name}</td>
|
|
{threshold.settings.map(setting => (
|
|
<td key={setting.val}>
|
|
<Button
|
|
content={toFixed(setting.selected, 2)}
|
|
onClick={() => act('threshold', {
|
|
env: setting.env,
|
|
var: setting.val,
|
|
})} />
|
|
</td>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
);
|
|
};
|