mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2025-12-09 16:07:40 +00:00
new uis
This commit is contained in:
157
tgui/packages/tgui/interfaces/AbductorConsole.js
Normal file
157
tgui/packages/tgui/interfaces/AbductorConsole.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import { GenericUplink } from './Uplink';
|
||||
import { useBackend, useSharedState } from '../backend';
|
||||
import { Button, LabeledList, NoticeBox, Section, Tabs } from '../components';
|
||||
import { Fragment } from 'inferno';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const AbductorConsole = (props, context) => {
|
||||
const [tab, setTab] = useSharedState(context, 'tab', 1);
|
||||
return (
|
||||
<Window
|
||||
theme="abductor"
|
||||
width={600}
|
||||
height={532}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
lineHeight="23px"
|
||||
selected={tab === 1}
|
||||
onClick={() => setTab(1)}>
|
||||
Abductsoft 3000
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
lineHeight="23px"
|
||||
selected={tab === 2}
|
||||
onClick={() => setTab(2)}>
|
||||
Mission Settings
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
{tab === 1 && (
|
||||
<Abductsoft />
|
||||
)}
|
||||
{tab === 2 && (
|
||||
<Fragment>
|
||||
<EmergencyTeleporter />
|
||||
<VestSettings />
|
||||
</Fragment>
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const Abductsoft = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
experiment,
|
||||
points,
|
||||
credits,
|
||||
} = data;
|
||||
|
||||
if (!experiment) {
|
||||
return (
|
||||
<NoticeBox danger>
|
||||
No Experiment Machine Detected
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Collected Samples">
|
||||
{points}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<GenericUplink
|
||||
currencyAmount={credits}
|
||||
currencySymbol="Credits" />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const EmergencyTeleporter = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
pad,
|
||||
gizmo,
|
||||
} = data;
|
||||
|
||||
if (!pad) {
|
||||
return (
|
||||
<NoticeBox danger>
|
||||
No Telepad Detected
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Emergency Teleport"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="exclamation-circle"
|
||||
content="Activate"
|
||||
color="bad"
|
||||
onClick={() => act('teleporter_send')} />
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Mark Retrieval">
|
||||
<Button
|
||||
icon={gizmo ? "user-plus" : "user-slash"}
|
||||
content={gizmo ? "Retrieve" : "No Mark"}
|
||||
disabled={!gizmo}
|
||||
onClick={() => act('teleporter_retrieve')} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const VestSettings = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
vest,
|
||||
vest_mode,
|
||||
vest_lock,
|
||||
} = data;
|
||||
|
||||
if (!vest) {
|
||||
return (
|
||||
<NoticeBox danger>
|
||||
No Agent Vest Detected
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Agent Vest Settings"
|
||||
buttons={(
|
||||
<Button
|
||||
icon={vest_lock ? 'lock' : 'unlock'}
|
||||
content={vest_lock ? 'Locked' : 'Unlocked'}
|
||||
onClick={() => act('toggle_vest')} />
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Mode">
|
||||
<Button
|
||||
icon={vest_mode === 1 ? 'eye-slash' : 'fist-raised'}
|
||||
content={vest_mode === 1 ? "Stealth" : "Combat"}
|
||||
onClick={() => act('flip_vest')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Disguise">
|
||||
<Button
|
||||
icon="user-secret"
|
||||
content="Select"
|
||||
onClick={() => act('select_disguise')} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
306
tgui/packages/tgui/interfaces/ApcControl.js
Normal file
306
tgui/packages/tgui/interfaces/ApcControl.js
Normal file
@@ -0,0 +1,306 @@
|
||||
import { map, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { pureComponentHooks } from 'common/react';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, Dimmer, Flex, Icon, Table, Tabs } from '../components';
|
||||
import { Fragment, Window } from '../layouts';
|
||||
import { AreaCharge, powerRank } from './PowerMonitor';
|
||||
|
||||
export const ApcControl = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
return (
|
||||
<Window
|
||||
title="APC Controller"
|
||||
width={550}
|
||||
height={500}
|
||||
resizable>
|
||||
{data.authenticated === 1 && (
|
||||
<ApcLoggedIn />
|
||||
)}
|
||||
{data.authenticated === 0 && (
|
||||
<ApcLoggedOut />
|
||||
)}
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const ApcLoggedOut = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { emagged } = data;
|
||||
const text = emagged === 1 ? 'Open' : 'Log In';
|
||||
return (
|
||||
<Window.Content>
|
||||
<Button
|
||||
fluid
|
||||
color={emagged === 1 ? '' : 'good'}
|
||||
content={text}
|
||||
onClick={() => act('log-in')} />
|
||||
</Window.Content>
|
||||
);
|
||||
};
|
||||
|
||||
const ApcLoggedIn = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { restoring } = data;
|
||||
const [
|
||||
tabIndex,
|
||||
setTabIndex,
|
||||
] = useLocalState(context, 'tab-index', 1);
|
||||
return (
|
||||
<Fragment>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
selected={tabIndex === 1}
|
||||
onClick={() => {
|
||||
setTabIndex(1);
|
||||
act('check-apcs');
|
||||
}}>
|
||||
APC Control Panel
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
selected={tabIndex === 2}
|
||||
onClick={() => {
|
||||
setTabIndex(2);
|
||||
act('check-logs');
|
||||
}}>
|
||||
Log View Panel
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
{restoring === 1 && (
|
||||
<Dimmer fontSize="32px">
|
||||
<Icon name="cog" spin />
|
||||
{' Resetting...'}
|
||||
</Dimmer>
|
||||
)}
|
||||
{tabIndex === 1 && (
|
||||
<Fragment>
|
||||
<ControlPanel />
|
||||
<Box fillPositionedParent top="53px">
|
||||
<Window.Content scrollable>
|
||||
<ApcControlScene />
|
||||
</Window.Content>
|
||||
</Box>
|
||||
</Fragment>
|
||||
)}
|
||||
{tabIndex === 2 && (
|
||||
<Box fillPositionedParent top="20px">
|
||||
<Window.Content scrollable>
|
||||
<LogPanel />
|
||||
</Window.Content>
|
||||
</Box>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const ControlPanel = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
emagged,
|
||||
logging,
|
||||
} = data;
|
||||
const [
|
||||
sortByField,
|
||||
setSortByField,
|
||||
] = useLocalState(context, 'sortByField', null);
|
||||
return (
|
||||
<Flex>
|
||||
<Flex.Item>
|
||||
<Box inline mr={2} color="label">
|
||||
Sort by:
|
||||
</Box>
|
||||
<Button.Checkbox
|
||||
checked={sortByField === 'name'}
|
||||
content="Name"
|
||||
onClick={() => setSortByField(sortByField !== 'name' && 'name')} />
|
||||
<Button.Checkbox
|
||||
checked={sortByField === 'charge'}
|
||||
content="Charge"
|
||||
onClick={() => setSortByField(
|
||||
sortByField !== 'charge' && 'charge'
|
||||
)} />
|
||||
<Button.Checkbox
|
||||
checked={sortByField === 'draw'}
|
||||
content="Draw"
|
||||
onClick={() => setSortByField(sortByField !== 'draw' && 'draw')} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} />
|
||||
<Flex.Item>
|
||||
{emagged === 1 && (
|
||||
<Fragment>
|
||||
<Button
|
||||
color={logging === 1 ? 'bad' : 'good'}
|
||||
content={logging === 1 ? 'Stop Logging' : 'Restore Logging'}
|
||||
onClick={() => act('toggle-logs')}
|
||||
/>
|
||||
<Button
|
||||
content="Reset Console"
|
||||
onClick={() => act('restore-console')}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
<Button
|
||||
color="bad"
|
||||
content="Log Out"
|
||||
onClick={() => act('log-out')}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const ApcControlScene = (props, context) => {
|
||||
const { data, act } = useBackend(context);
|
||||
|
||||
const [
|
||||
sortByField,
|
||||
] = useLocalState(context, 'sortByField', null);
|
||||
|
||||
const apcs = flow([
|
||||
map((apc, i) => ({
|
||||
...apc,
|
||||
// Generate a unique id
|
||||
id: apc.name + i,
|
||||
})),
|
||||
sortByField === 'name' && sortBy(apc => apc.name),
|
||||
sortByField === 'charge' && sortBy(apc => -apc.charge),
|
||||
sortByField === 'draw' && sortBy(
|
||||
apc => -powerRank(apc.load),
|
||||
apc => -parseFloat(apc.load)),
|
||||
])(data.apcs);
|
||||
return (
|
||||
<Table>
|
||||
<Table.Row header>
|
||||
<Table.Cell>
|
||||
On/Off
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
Area
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing>
|
||||
Charge
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing textAlign="right">
|
||||
Draw
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing title="Equipment">
|
||||
Eqp
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing title="Lighting">
|
||||
Lgt
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing title="Environment">
|
||||
Env
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{apcs.map((apc, i) => (
|
||||
<tr
|
||||
key={apc.id}
|
||||
className="Table__row candystripe">
|
||||
<td>
|
||||
<Button
|
||||
icon={apc.operating ? 'power-off' : 'times'}
|
||||
color={apc.operating ? 'good' : 'bad'}
|
||||
onClick={() => act('breaker', {
|
||||
ref: apc.ref,
|
||||
})}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
onClick={() => act('access-apc', {
|
||||
ref: apc.ref,
|
||||
})}>
|
||||
{apc.name}
|
||||
</Button>
|
||||
</td>
|
||||
<td className="Table__cell text-right text-nowrap">
|
||||
<AreaCharge
|
||||
charging={apc.charging}
|
||||
charge={apc.charge}
|
||||
/>
|
||||
</td>
|
||||
<td className="Table__cell text-right text-nowrap">
|
||||
{apc.load}
|
||||
</td>
|
||||
<td className="Table__cell text-center text-nowrap">
|
||||
<AreaStatusColorButton
|
||||
target="equipment"
|
||||
status={apc.eqp}
|
||||
apc={apc}
|
||||
act={act}
|
||||
/>
|
||||
</td>
|
||||
<td className="Table__cell text-center text-nowrap">
|
||||
<AreaStatusColorButton
|
||||
target="lighting"
|
||||
status={apc.lgt}
|
||||
apc={apc}
|
||||
act={act}
|
||||
/>
|
||||
</td>
|
||||
<td className="Table__cell text-center text-nowrap">
|
||||
<AreaStatusColorButton
|
||||
target="environ"
|
||||
status={apc.env}
|
||||
apc={apc}
|
||||
act={act}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
const LogPanel = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
|
||||
const logs = flow([
|
||||
map((line, i) => ({
|
||||
...line,
|
||||
// Generate a unique id
|
||||
id: line.entry + i,
|
||||
})),
|
||||
logs => logs.reverse(),
|
||||
])(data.logs);
|
||||
return (
|
||||
<Box m={-0.5}>
|
||||
{logs.map(line => (
|
||||
<Box
|
||||
p={0.5}
|
||||
key={line.id}
|
||||
className="candystripe"
|
||||
bold>
|
||||
{line.entry}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const AreaStatusColorButton = props => {
|
||||
const { target, status, apc, act } = props;
|
||||
const power = Boolean(status & 2);
|
||||
const mode = Boolean(status & 1);
|
||||
return (
|
||||
<Button
|
||||
icon={mode ? 'sync' : 'power-off'}
|
||||
color={power ? 'good' : 'bad'}
|
||||
onClick={() => act('toggle-minor', {
|
||||
type: target,
|
||||
value: statusChange(status),
|
||||
ref: apc.ref,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const statusChange = status => {
|
||||
// mode flip power flip both flip
|
||||
// 0, 2, 3
|
||||
return status === 0 ? 2 : status === 2 ? 3 : 0;
|
||||
};
|
||||
|
||||
AreaStatusColorButton.defaultHooks = pureComponentHooks;
|
||||
|
||||
101
tgui/packages/tgui/interfaces/BluespaceLocator.js
Normal file
101
tgui/packages/tgui/interfaces/BluespaceLocator.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import { useBackend, useSharedState } from '../backend';
|
||||
import { Icon, ProgressBar, Tabs } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const directionToIcon = {
|
||||
north: 0,
|
||||
northeast: 45,
|
||||
east: 90,
|
||||
southeast: 135,
|
||||
south: 180,
|
||||
southwest: 225,
|
||||
west: 270,
|
||||
northwest: 315,
|
||||
};
|
||||
|
||||
export const BluespaceLocator = (props, context) => {
|
||||
const [tab, setTab] = useSharedState(context, "tab", "implant");
|
||||
return (
|
||||
<Window
|
||||
width={300}
|
||||
height={300}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
selected={tab === "implant"}
|
||||
onClick={() => setTab("implant")}>
|
||||
Implants
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
selected={tab === "beacon"}
|
||||
onClick={() => setTab("beacon")}>
|
||||
Teleporter Beacons
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
{tab === "beacon" && (
|
||||
<TeleporterBeacons />
|
||||
)
|
||||
|| tab === "implant" && (
|
||||
<TrackingImplants />
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const TeleporterBeacons = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const { telebeacons } = data;
|
||||
return (
|
||||
telebeacons.map(beacon => (
|
||||
<SignalLocator
|
||||
key={beacon.name}
|
||||
name={beacon.name}
|
||||
distance={beacon.distance}
|
||||
direction={directionToIcon[beacon.direction]} />
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
const TrackingImplants = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const { trackimplants } = data;
|
||||
return (
|
||||
trackimplants.map(implant => (
|
||||
<SignalLocator
|
||||
key={implant.name}
|
||||
name={implant.name}
|
||||
distance={implant.distance}
|
||||
direction={directionToIcon[implant.direction]} />
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
const SignalLocator = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const { trackingrange } = data;
|
||||
const {
|
||||
name,
|
||||
direction,
|
||||
distance,
|
||||
} = props;
|
||||
return (
|
||||
<ProgressBar
|
||||
mb={1}
|
||||
value={trackingrange - distance}
|
||||
minValue={0}
|
||||
maxValue={trackingrange}
|
||||
ranges={{
|
||||
red: [0, trackingrange/3],
|
||||
yellow: [trackingrange/3, 2 * (trackingrange/3)],
|
||||
green: [2 * (trackingrange/3), trackingrange],
|
||||
}}>
|
||||
{name}
|
||||
<Icon
|
||||
ml={2}
|
||||
name="arrow-up"
|
||||
rotation={direction} />
|
||||
</ProgressBar>
|
||||
);
|
||||
};
|
||||
119
tgui/packages/tgui/interfaces/CargoBountyConsole.js
Normal file
119
tgui/packages/tgui/interfaces/CargoBountyConsole.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, Section, Table } from '../components';
|
||||
import { formatMoney } from '../format';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const CargoBountyConsole = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
bountydata = [],
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={750}
|
||||
height={600}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
<Section
|
||||
title={<BountyHeader />}
|
||||
buttons={(
|
||||
<Button
|
||||
icon="print"
|
||||
content="Print Bounty List"
|
||||
onClick={() => act('Print')} />
|
||||
)}>
|
||||
<Table border>
|
||||
<Table.Row
|
||||
bold
|
||||
italic
|
||||
color="label"
|
||||
fontSize={1.25}>
|
||||
<Table.Cell p={1} textAlign="center">
|
||||
Bounty Object
|
||||
</Table.Cell>
|
||||
<Table.Cell p={1} textAlign="center">
|
||||
Description
|
||||
</Table.Cell>
|
||||
<Table.Cell p={1} textAlign="center">
|
||||
Progress
|
||||
</Table.Cell>
|
||||
<Table.Cell p={1} textAlign="center">
|
||||
Value
|
||||
</Table.Cell>
|
||||
<Table.Cell p={1} textAlign="center">
|
||||
Claim
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{bountydata.map(bounty => (
|
||||
<Table.Row
|
||||
key={bounty.name}
|
||||
backgroundColor={bounty.priority === 1
|
||||
? 'rgba(252, 152, 3, 0.25)'
|
||||
: ''}>
|
||||
<Table.Cell bold p={1}>
|
||||
{bounty.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell
|
||||
italic
|
||||
textAlign="center"
|
||||
p={1}>
|
||||
{bounty.description}
|
||||
</Table.Cell>
|
||||
<Table.Cell
|
||||
bold
|
||||
p={1}
|
||||
textAlign="center">
|
||||
{bounty.priority === 1
|
||||
? <Box>High Priority</Box>
|
||||
: ""}
|
||||
{bounty.completion_string}
|
||||
</Table.Cell>
|
||||
<Table.Cell
|
||||
bold
|
||||
p={1}
|
||||
textAlign="center">
|
||||
{bounty.reward_string}
|
||||
</Table.Cell>
|
||||
<Table.Cell
|
||||
bold
|
||||
p={1}>
|
||||
<Button
|
||||
fluid
|
||||
textAlign="center"
|
||||
icon={bounty.claimed === 1
|
||||
? "check"
|
||||
: ""}
|
||||
content={bounty.claimed === 1
|
||||
? "Claimed"
|
||||
: "Claim"}
|
||||
disabled={bounty.claimed === 1}
|
||||
color={bounty.can_claim === 1
|
||||
? 'green'
|
||||
: 'red'}
|
||||
onClick={() => act('ClaimBounty', {
|
||||
bounty: bounty.bounty_ref,
|
||||
})} />
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const BountyHeader = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
stored_cash,
|
||||
} = data;
|
||||
return (
|
||||
<Box inline bold>
|
||||
<AnimatedNumber
|
||||
value={stored_cash}
|
||||
format={value => formatMoney(value)} />
|
||||
{' credits'}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
103
tgui/packages/tgui/interfaces/CivCargoHoldTerminal.js
Normal file
103
tgui/packages/tgui/interfaces/CivCargoHoldTerminal.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, Flex, LabeledList, NoticeBox, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const CivCargoHoldTerminal = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
pad,
|
||||
sending,
|
||||
status_report,
|
||||
id_inserted,
|
||||
id_bounty_info,
|
||||
id_bounty_value,
|
||||
id_bounty_num,
|
||||
} = data;
|
||||
const in_text = "Welcome valued employee.";
|
||||
const out_text = "To begin, insert your ID into the console.";
|
||||
return (
|
||||
<Window resizable
|
||||
width={500}
|
||||
height={375}>
|
||||
<Window.Content scrollable>
|
||||
<Flex>
|
||||
<Flex.Item>
|
||||
<NoticeBox
|
||||
color={!id_inserted ? 'default': 'blue'}>
|
||||
{id_inserted ? in_text : out_text}
|
||||
</NoticeBox>
|
||||
<Section title="Cargo Pad">
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Status"
|
||||
color={pad ? "good" : "bad"}>
|
||||
{pad ? "Online" : "Not Found"}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Cargo Report">
|
||||
{status_report}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<BountyTextBox />
|
||||
</Flex.Item>
|
||||
<Flex.Item m={1}>
|
||||
<Fragment>
|
||||
<Button
|
||||
fluid
|
||||
icon={"sync"}
|
||||
content={"Check Contents"}
|
||||
disabled={!pad || !id_inserted}
|
||||
onClick={() => act('recalc')} />
|
||||
<Button
|
||||
fluid
|
||||
icon={sending ? 'times' : 'arrow-up'}
|
||||
content={sending ? "Stop Sending" : "Send Goods"}
|
||||
selected={sending}
|
||||
disabled={!pad || !id_inserted}
|
||||
onClick={() => act(sending ? 'stop' : 'send')} />
|
||||
<Button
|
||||
fluid
|
||||
icon={id_bounty_info ? 'recycle' : 'pen'}
|
||||
color={id_bounty_info ? 'green' : 'default'}
|
||||
content={id_bounty_info ? "Replace Bounty" : "New Bounty"}
|
||||
disabled={!id_inserted}
|
||||
onClick={() => act('bounty')} />
|
||||
<Button
|
||||
fluid
|
||||
icon={'download'}
|
||||
content={"Eject"}
|
||||
disabled={!id_inserted}
|
||||
onClick={() => act('eject')} />
|
||||
</Fragment>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const BountyTextBox = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const {
|
||||
id_bounty_info,
|
||||
id_bounty_value,
|
||||
id_bounty_num,
|
||||
} = data;
|
||||
const na_text = "N/A, please add a new bounty.";
|
||||
return (
|
||||
<Section title="Bounty Info">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Description">
|
||||
{id_bounty_info ? id_bounty_info : na_text}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Quantity">
|
||||
{id_bounty_info ? id_bounty_num : "N/A"}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Value">
|
||||
{id_bounty_info ? id_bounty_value : "N/A"}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
796
tgui/packages/tgui/interfaces/ExosuitFabricator.js
Normal file
796
tgui/packages/tgui/interfaces/ExosuitFabricator.js
Normal file
@@ -0,0 +1,796 @@
|
||||
import { classes } from 'common/react';
|
||||
import { uniqBy } from 'common/collections';
|
||||
import { useBackend, useSharedState } from '../backend';
|
||||
import { formatSiUnit, formatMoney } from '../format';
|
||||
import { Flex, Section, Tabs, Box, Button, Fragment, ProgressBar, NumberInput, Icon, Input } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { createSearch } from 'common/string';
|
||||
|
||||
const MATERIAL_KEYS = {
|
||||
"iron": "sheet-metal_3",
|
||||
"glass": "sheet-glass_3",
|
||||
"silver": "sheet-silver_3",
|
||||
"gold": "sheet-gold_3",
|
||||
"diamond": "sheet-diamond",
|
||||
"plasma": "sheet-plasma_3",
|
||||
"uranium": "sheet-uranium",
|
||||
"bananium": "sheet-bananium",
|
||||
"titanium": "sheet-titanium_3",
|
||||
"bluespace crystal": "polycrystal",
|
||||
"plastic": "sheet-plastic_3",
|
||||
};
|
||||
|
||||
const COLOR_NONE = 0;
|
||||
const COLOR_AVERAGE = 1;
|
||||
const COLOR_BAD = 2;
|
||||
|
||||
const COLOR_KEYS = {
|
||||
[COLOR_NONE]: false,
|
||||
[COLOR_AVERAGE]: "average",
|
||||
[COLOR_BAD]: "bad",
|
||||
};
|
||||
|
||||
const materialArrayToObj = materials => {
|
||||
let materialObj = {};
|
||||
|
||||
materials.forEach(m => {
|
||||
materialObj[m.name] = m.amount; });
|
||||
|
||||
return materialObj;
|
||||
};
|
||||
|
||||
const partBuildColor = (cost, tally, material) => {
|
||||
if (cost > material) {
|
||||
return { color: COLOR_BAD, deficit: (cost - material) };
|
||||
}
|
||||
|
||||
if (tally > material) {
|
||||
return { color: COLOR_AVERAGE, deficit: cost };
|
||||
}
|
||||
|
||||
if (cost + tally > material) {
|
||||
return { color: COLOR_AVERAGE, deficit: ((cost + tally) - material) };
|
||||
}
|
||||
|
||||
return { color: COLOR_NONE, deficit: 0 };
|
||||
};
|
||||
|
||||
const partCondFormat = (materials, tally, part) => {
|
||||
let format = { "textColor": COLOR_NONE };
|
||||
|
||||
Object.keys(part.cost).forEach(mat => {
|
||||
format[mat] = partBuildColor(part.cost[mat], tally[mat], materials[mat]);
|
||||
|
||||
if (format[mat].color > format["textColor"]) {
|
||||
format["textColor"] = format[mat].color;
|
||||
}
|
||||
});
|
||||
|
||||
return format;
|
||||
};
|
||||
|
||||
const queueCondFormat = (materials, queue) => {
|
||||
let materialTally = {};
|
||||
let matFormat = {};
|
||||
let missingMatTally = {};
|
||||
let textColors = {};
|
||||
|
||||
queue.forEach((part, i) => {
|
||||
textColors[i] = COLOR_NONE;
|
||||
Object.keys(part.cost).forEach(mat => {
|
||||
materialTally[mat] = materialTally[mat] || 0;
|
||||
missingMatTally[mat] = missingMatTally[mat] || 0;
|
||||
|
||||
matFormat[mat] = partBuildColor(
|
||||
part.cost[mat], materialTally[mat], materials[mat]
|
||||
);
|
||||
|
||||
if (matFormat[mat].color !== COLOR_NONE) {
|
||||
if (textColors[i] < matFormat[mat].color) {
|
||||
textColors[i] = matFormat[mat].color;
|
||||
}
|
||||
}
|
||||
else {
|
||||
materialTally[mat] += part.cost[mat];
|
||||
}
|
||||
|
||||
missingMatTally[mat] += matFormat[mat].deficit;
|
||||
});
|
||||
});
|
||||
return { materialTally, missingMatTally, textColors, matFormat };
|
||||
};
|
||||
|
||||
const searchFilter = (search, allparts) => {
|
||||
let searchResults = [];
|
||||
|
||||
if (!search.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resultFilter = createSearch(search, part => (
|
||||
(part.name || "")
|
||||
+ (part.desc || "")
|
||||
+ (part.searchMeta || "")
|
||||
));
|
||||
|
||||
Object.keys(allparts).forEach(category => {
|
||||
allparts[category]
|
||||
.filter(resultFilter)
|
||||
.forEach(e => { searchResults.push(e); });
|
||||
});
|
||||
|
||||
searchResults = uniqBy(part => part.name)(searchResults);
|
||||
|
||||
return searchResults;
|
||||
};
|
||||
|
||||
export const ExosuitFabricator = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const queue = data.queue || [];
|
||||
const materialAsObj = materialArrayToObj(data.materials || []);
|
||||
|
||||
const {
|
||||
materialTally,
|
||||
missingMatTally,
|
||||
textColors,
|
||||
} = queueCondFormat(materialAsObj, queue);
|
||||
|
||||
const [
|
||||
displayMatCost,
|
||||
setDisplayMatCost,
|
||||
] = useSharedState(context, "display_mats", false);
|
||||
|
||||
return (
|
||||
<Window
|
||||
resizable
|
||||
title="Exosuit Fabricator"
|
||||
width={1100}
|
||||
height={640}>
|
||||
<Window.Content
|
||||
scrollable>
|
||||
<Flex
|
||||
fillPositionedParent
|
||||
direction="column">
|
||||
<Flex>
|
||||
<Flex.Item
|
||||
ml={1}
|
||||
mr={1}
|
||||
mt={1}
|
||||
basis="content"
|
||||
grow={1}>
|
||||
<Section
|
||||
title="Materials">
|
||||
<Materials />
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
mt={1}
|
||||
mr={1}>
|
||||
<Section
|
||||
title="Settings"
|
||||
height="100%">
|
||||
<Button.Checkbox
|
||||
onClick={() => setDisplayMatCost(!displayMatCost)}
|
||||
checked={displayMatCost}>
|
||||
Display Material Costs
|
||||
</Button.Checkbox>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex.Item
|
||||
grow={1}
|
||||
m={1}>
|
||||
<Flex
|
||||
spacing={1}
|
||||
height="100%"
|
||||
overflowY="hide">
|
||||
<Flex.Item position="relative" basis="content">
|
||||
<Section
|
||||
height="100%"
|
||||
overflowY="auto"
|
||||
title="Categories"
|
||||
buttons={(
|
||||
<Button
|
||||
content="R&D Sync"
|
||||
onClick={() => act("sync_rnd")} />
|
||||
)}>
|
||||
<PartSets />
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
position="relative"
|
||||
grow={1}>
|
||||
<Box
|
||||
fillPositionedParent
|
||||
overflowY="auto">
|
||||
<PartLists
|
||||
queueMaterials={materialTally}
|
||||
materials={materialAsObj} />
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
width="420px"
|
||||
position="relative">
|
||||
<Queue
|
||||
queueMaterials={materialTally}
|
||||
missingMaterials={missingMatTally}
|
||||
textColors={textColors} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const EjectMaterial = (props, context) => {
|
||||
const { act } = useBackend(context);
|
||||
|
||||
const { material } = props;
|
||||
|
||||
const {
|
||||
name,
|
||||
removable,
|
||||
sheets,
|
||||
ref,
|
||||
} = material;
|
||||
|
||||
const [
|
||||
removeMaterials,
|
||||
setRemoveMaterials,
|
||||
] = useSharedState(context, "remove_mats_" + name, 1);
|
||||
|
||||
if ((removeMaterials > 1) && (sheets < removeMaterials)) {
|
||||
setRemoveMaterials(sheets || 1);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<NumberInput
|
||||
width="30px"
|
||||
animated
|
||||
value={removeMaterials}
|
||||
minValue={1}
|
||||
maxValue={sheets || 1}
|
||||
initial={1}
|
||||
onDrag={(e, val) => {
|
||||
const newVal = parseInt(val, 10);
|
||||
if (Number.isInteger(newVal)) {
|
||||
setRemoveMaterials(newVal);
|
||||
}
|
||||
}} />
|
||||
<Button
|
||||
icon="eject"
|
||||
disabled={!removable}
|
||||
onClick={() => act("remove_mat", {
|
||||
ref: ref,
|
||||
amount: removeMaterials,
|
||||
})} />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const Materials = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
|
||||
const materials = data.materials || [];
|
||||
|
||||
return (
|
||||
<Flex
|
||||
wrap="wrap">
|
||||
{materials.map(material => (
|
||||
<Flex.Item
|
||||
width="80px"
|
||||
key={material.name}>
|
||||
<MaterialAmount
|
||||
name={material.name}
|
||||
amount={material.amount}
|
||||
formatsi />
|
||||
<Box
|
||||
mt={1}
|
||||
style={{ "text-align": "center" }}>
|
||||
<EjectMaterial
|
||||
material={material} />
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const MaterialAmount = (props, context) => {
|
||||
const {
|
||||
name,
|
||||
amount,
|
||||
formatsi,
|
||||
formatmoney,
|
||||
color,
|
||||
style,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center">
|
||||
<Flex.Item>
|
||||
<Box
|
||||
className={classes([
|
||||
'sheetmaterials32x32',
|
||||
MATERIAL_KEYS[name],
|
||||
])}
|
||||
style={style} />
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Box
|
||||
textColor={color}
|
||||
style={{ "text-align": "center" }}>
|
||||
{(formatsi && formatSiUnit(amount, 0))
|
||||
|| (formatmoney && formatMoney(amount))
|
||||
|| (amount)}
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const PartSets = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
|
||||
const partSets = data.partSets || [];
|
||||
const buildableParts = data.buildableParts || {};
|
||||
|
||||
const [
|
||||
selectedPartTab,
|
||||
setSelectedPartTab,
|
||||
] = useSharedState(
|
||||
context,
|
||||
"part_tab",
|
||||
partSets.length ? buildableParts[0] : ""
|
||||
);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
vertical>
|
||||
{partSets.map(set => (
|
||||
!!(buildableParts[set]) && (
|
||||
<Tabs.Tab
|
||||
key={set}
|
||||
selected={set === selectedPartTab}
|
||||
disabled={!(buildableParts[set])}
|
||||
onClick={() => setSelectedPartTab(set)}>
|
||||
{set}
|
||||
</Tabs.Tab>
|
||||
)
|
||||
))}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
const PartLists = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
|
||||
const getFirstValidPartSet = (sets => {
|
||||
for (let set of sets) {
|
||||
if (buildableParts[set]) {
|
||||
return set;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const partSets = data.partSets || [];
|
||||
const buildableParts = data.buildableParts || [];
|
||||
|
||||
const {
|
||||
queueMaterials,
|
||||
materials,
|
||||
} = props;
|
||||
|
||||
const [
|
||||
selectedPartTab,
|
||||
setSelectedPartTab,
|
||||
] = useSharedState(
|
||||
context,
|
||||
"part_tab",
|
||||
getFirstValidPartSet(partSets)
|
||||
);
|
||||
|
||||
const [
|
||||
searchText,
|
||||
setSearchText,
|
||||
] = useSharedState(context, "search_text", "");
|
||||
|
||||
if (!selectedPartTab || !buildableParts[selectedPartTab]) {
|
||||
const validSet = getFirstValidPartSet(partSets);
|
||||
if (validSet) {
|
||||
setSelectedPartTab(validSet);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let partsList;
|
||||
// Build list of sub-categories if not using a search filter.
|
||||
if (!searchText) {
|
||||
partsList = { "Parts": [] };
|
||||
buildableParts[selectedPartTab].forEach(part => {
|
||||
part["format"] = partCondFormat(materials, queueMaterials, part);
|
||||
if (!part.subCategory) {
|
||||
partsList["Parts"].push(part);
|
||||
return;
|
||||
}
|
||||
if (!(part.subCategory in partsList)) {
|
||||
partsList[part.subCategory] = [];
|
||||
}
|
||||
partsList[part.subCategory].push(part);
|
||||
});
|
||||
}
|
||||
else {
|
||||
partsList = [];
|
||||
searchFilter(searchText, buildableParts).forEach(part => {
|
||||
part["format"] = partCondFormat(materials, queueMaterials, part);
|
||||
partsList.push(part);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Section>
|
||||
<Flex>
|
||||
<Flex.Item mr={1}>
|
||||
<Icon
|
||||
name="search" />
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
grow={1}>
|
||||
<Input
|
||||
fluid
|
||||
placeholder="Search for..."
|
||||
onInput={(e, v) => setSearchText(v)} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
{(!!searchText && (
|
||||
<PartCategory
|
||||
name={"Search Results"}
|
||||
parts={partsList}
|
||||
forceShow
|
||||
placeholder="No matching results..." />
|
||||
)) || (
|
||||
Object.keys(partsList).map(category => (
|
||||
<PartCategory
|
||||
key={category}
|
||||
name={category}
|
||||
parts={partsList[category]} />
|
||||
))
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const PartCategory = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const {
|
||||
buildingPart,
|
||||
} = data;
|
||||
|
||||
const {
|
||||
parts,
|
||||
name,
|
||||
forceShow,
|
||||
placeholder,
|
||||
} = props;
|
||||
|
||||
const [
|
||||
displayMatCost,
|
||||
] = useSharedState(context, "display_mats", false);
|
||||
|
||||
return (
|
||||
((!!parts.length || forceShow) && (
|
||||
<Section
|
||||
title={name}
|
||||
buttons={
|
||||
<Button
|
||||
disabled={!parts.length}
|
||||
color="good"
|
||||
content="Queue All"
|
||||
icon="plus-circle"
|
||||
onClick={() => act("add_queue_set", {
|
||||
part_list: parts.map(part => part.id),
|
||||
})} />
|
||||
}>
|
||||
{(!parts.length) && (placeholder)}
|
||||
{parts.map(part => (
|
||||
<Fragment
|
||||
key={part.name}>
|
||||
<Flex
|
||||
align="center">
|
||||
<Flex.Item>
|
||||
<Button
|
||||
disabled={buildingPart
|
||||
|| (part.format.textColor === COLOR_BAD)}
|
||||
color="good"
|
||||
height="20px"
|
||||
mr={1}
|
||||
icon="play"
|
||||
onClick={() => act("build_part", { id: part.id })} />
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Button
|
||||
color="average"
|
||||
height="20px"
|
||||
mr={1}
|
||||
icon="plus-circle"
|
||||
onClick={() => act("add_queue_part", { id: part.id })} />
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Box
|
||||
inline
|
||||
textColor={COLOR_KEYS[part.format.textColor]}>
|
||||
{part.name}
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
grow={1} />
|
||||
<Flex.Item>
|
||||
<Button
|
||||
icon="question-circle"
|
||||
transparent
|
||||
height="20px"
|
||||
tooltip={
|
||||
"Build Time: "
|
||||
+ part.printTime + "s. "
|
||||
+ (part.desc || "")
|
||||
}
|
||||
tooltipPosition="left" />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
{(displayMatCost && (
|
||||
<Flex mb={2}>
|
||||
{Object.keys(part.cost).map(material => (
|
||||
<Flex.Item
|
||||
width={"50px"}
|
||||
key={material}
|
||||
color={COLOR_KEYS[part.format[material].color]}>
|
||||
<MaterialAmount
|
||||
formatmoney
|
||||
style={{
|
||||
transform: 'scale(0.75) translate(0%, 10%)',
|
||||
}}
|
||||
name={material}
|
||||
amount={part.cost[material]} />
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
))}
|
||||
|
||||
</Fragment>
|
||||
))}
|
||||
</Section>
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
const Queue = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const { isProcessingQueue } = data;
|
||||
|
||||
const queue = data.queue || [];
|
||||
|
||||
const {
|
||||
queueMaterials,
|
||||
missingMaterials,
|
||||
textColors,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
height="100%"
|
||||
width="100%"
|
||||
direction="column">
|
||||
<Flex.Item
|
||||
height={0}
|
||||
grow={1}>
|
||||
<Section
|
||||
height="100%"
|
||||
title="Queue"
|
||||
overflowY="auto"
|
||||
buttons={
|
||||
<Fragment>
|
||||
<Button.Confirm
|
||||
disabled={!queue.length}
|
||||
color="bad"
|
||||
icon="minus-circle"
|
||||
content="Clear Queue"
|
||||
onClick={() => act("clear_queue")} />
|
||||
{(!!isProcessingQueue && (
|
||||
<Button
|
||||
disabled={!queue.length}
|
||||
content="Stop"
|
||||
icon="stop"
|
||||
onClick={() => act("stop_queue")} />
|
||||
)) || (
|
||||
<Button
|
||||
disabled={!queue.length}
|
||||
content="Build Queue"
|
||||
icon="play"
|
||||
onClick={() => act("build_queue")} />
|
||||
)}
|
||||
</Fragment>
|
||||
}>
|
||||
<Flex
|
||||
direction="column"
|
||||
height="100%">
|
||||
<Flex.Item>
|
||||
<BeingBuilt />
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<QueueList
|
||||
textColors={textColors} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
{!!queue.length && (
|
||||
<Flex.Item mt={1}>
|
||||
<Section
|
||||
title="Material Cost">
|
||||
<QueueMaterials
|
||||
queueMaterials={queueMaterials}
|
||||
missingMaterials={missingMaterials} />
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const QueueMaterials = (props, context) => {
|
||||
const {
|
||||
queueMaterials,
|
||||
missingMaterials,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Flex wrap="wrap">
|
||||
{Object.keys(queueMaterials).map(material => (
|
||||
<Flex.Item
|
||||
width="12%"
|
||||
key={material}>
|
||||
<MaterialAmount
|
||||
formatmoney
|
||||
name={material}
|
||||
amount={queueMaterials[material]} />
|
||||
{(!!missingMaterials[material] && (
|
||||
<Box
|
||||
textColor="bad"
|
||||
style={{ "text-align": "center" }}>
|
||||
{formatMoney(missingMaterials[material])}
|
||||
</Box>
|
||||
))}
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const QueueList = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const {
|
||||
textColors,
|
||||
} = props;
|
||||
|
||||
const queue = data.queue || [];
|
||||
|
||||
if (!queue.length) {
|
||||
return (
|
||||
<Fragment>
|
||||
No parts in queue.
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
queue.map((part, index) => (
|
||||
<Box
|
||||
key={part.name}>
|
||||
<Flex
|
||||
mb={0.5}
|
||||
direction="column"
|
||||
justify="center"
|
||||
wrap="wrap"
|
||||
height="20px" inline>
|
||||
<Flex.Item
|
||||
basis="content">
|
||||
<Button
|
||||
height="20px"
|
||||
mr={1}
|
||||
icon="minus-circle"
|
||||
color="bad"
|
||||
onClick={() => act("del_queue_part", { index: index+1 })} />
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Box
|
||||
inline
|
||||
textColor={COLOR_KEYS[textColors[index]]}>
|
||||
{part.name}
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Box>
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
const BeingBuilt = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
|
||||
const {
|
||||
buildingPart,
|
||||
storedPart,
|
||||
} = data;
|
||||
|
||||
if (storedPart) {
|
||||
const {
|
||||
name,
|
||||
} = storedPart;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<ProgressBar
|
||||
minValue={0}
|
||||
maxValue={1}
|
||||
value={1}
|
||||
color="average">
|
||||
<Flex>
|
||||
<Flex.Item>
|
||||
{name}
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
grow={1} />
|
||||
<Flex.Item>
|
||||
{"Fabricator outlet obstructed..."}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</ProgressBar>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (buildingPart) {
|
||||
const {
|
||||
name,
|
||||
duration,
|
||||
printTime,
|
||||
} = buildingPart;
|
||||
|
||||
const timeLeft = Math.ceil(duration/10);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<ProgressBar
|
||||
minValue={0}
|
||||
maxValue={printTime}
|
||||
value={duration}>
|
||||
<Flex>
|
||||
<Flex.Item>
|
||||
{name}
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
grow={1} />
|
||||
<Flex.Item>
|
||||
{((timeLeft >= 0) && (timeLeft + "s")) || ("Dispensing...")}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</ProgressBar>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
61
tgui/packages/tgui/interfaces/ForbiddenLore.js
Normal file
61
tgui/packages/tgui/interfaces/ForbiddenLore.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const ForbiddenLore = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
charges,
|
||||
} = data;
|
||||
const to_know = flow([
|
||||
sortBy(to_know => to_know.state !== "Research",
|
||||
to_know => to_know.path === "Side"),
|
||||
])(data.to_know || []);
|
||||
return (
|
||||
<Window
|
||||
width={500}
|
||||
height={900}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
<Section title="Research Eldritch Knowledge">
|
||||
Charges left : {charges}
|
||||
{to_know!== null ? (
|
||||
to_know.map(knowledge => (
|
||||
<Section
|
||||
key={knowledge.name}
|
||||
title={knowledge.name}
|
||||
level={2}>
|
||||
<Box bold my={1}>
|
||||
{knowledge.path} path
|
||||
</Box>
|
||||
<Box my={1}>
|
||||
<Button
|
||||
content={knowledge.state}
|
||||
disabled={knowledge.disabled}
|
||||
onClick={() => act('research', {
|
||||
name: knowledge.name,
|
||||
cost: knowledge.cost,
|
||||
})} />
|
||||
{' '}
|
||||
Cost : {knowledge.cost}
|
||||
</Box >
|
||||
<Box italic my={1}>
|
||||
{knowledge.flavour}
|
||||
</Box>
|
||||
<Box my={1}>
|
||||
{knowledge.desc}
|
||||
</Box>
|
||||
</Section>
|
||||
))
|
||||
) : (
|
||||
<Box>
|
||||
No more knowledge can be found
|
||||
</Box>
|
||||
)}
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
37
tgui/packages/tgui/interfaces/GlandDispenser.js
Normal file
37
tgui/packages/tgui/interfaces/GlandDispenser.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const GlandDispenser = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
glands = [],
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={300}
|
||||
height={338}
|
||||
theme="abductor">
|
||||
<Window.Content>
|
||||
<Section>
|
||||
{glands.map(gland => (
|
||||
<Button
|
||||
key={gland.id}
|
||||
width="60px"
|
||||
height="60px"
|
||||
m={0.75}
|
||||
textAlign="center"
|
||||
lineHeight="55px"
|
||||
icon="eject"
|
||||
backgroundColor={gland.color}
|
||||
content={gland.amount || "0"}
|
||||
disabled={!gland.amount}
|
||||
onClick={() => act('dispense', {
|
||||
gland_id: gland.id,
|
||||
})} />
|
||||
))}
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
159
tgui/packages/tgui/interfaces/Holopad.js
Normal file
159
tgui/packages/tgui/interfaces/Holopad.js
Normal file
@@ -0,0 +1,159 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Flex, Icon, LabeledList, Modal, NoticeBox, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const Holopad = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
calling,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={440}
|
||||
height={245}
|
||||
resizable>
|
||||
{!!calling && (
|
||||
<Modal
|
||||
fontSize="36px"
|
||||
fontFamily="monospace">
|
||||
<Flex align="center">
|
||||
<Flex.Item mr={2} mt={2}>
|
||||
<Icon
|
||||
name="phone-alt"
|
||||
rotation={25} />
|
||||
</Flex.Item>
|
||||
<Flex.Item mr={2}>
|
||||
{'Dialing...'}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Box
|
||||
mt={2}
|
||||
textAlign="center"
|
||||
fontSize="24px">
|
||||
<Button
|
||||
lineHeight="40px"
|
||||
icon="times"
|
||||
content="Hang Up"
|
||||
color="bad"
|
||||
onClick={() => act('hang_up')} />
|
||||
</Box>
|
||||
</Modal>
|
||||
)}
|
||||
<Window.Content scrollable>
|
||||
<HolopadContent />
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const HolopadContent = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
on_network,
|
||||
on_cooldown,
|
||||
allowed,
|
||||
disk,
|
||||
disk_record,
|
||||
replay_mode,
|
||||
loop_mode,
|
||||
record_mode,
|
||||
holo_calls = [],
|
||||
} = data;
|
||||
return (
|
||||
<Fragment>
|
||||
<Section
|
||||
title="Holopad"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="bell"
|
||||
content={on_cooldown
|
||||
? "AI Presence Requested"
|
||||
: "Request AI Presence"}
|
||||
disabled={!on_network || on_cooldown}
|
||||
onClick={() => act('AIrequest')} />
|
||||
)} >
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Communicator">
|
||||
<Button
|
||||
icon="phone-alt"
|
||||
content={allowed ? "Connect To Holopad" : "Call Holopad"}
|
||||
disabled={!on_network}
|
||||
onClick={() => act('holocall', { headcall: allowed })} />
|
||||
</LabeledList.Item>
|
||||
{holo_calls.map((call => {
|
||||
return (
|
||||
<LabeledList.Item
|
||||
label={call.connected
|
||||
? "Current Call"
|
||||
: "Incoming Call"}
|
||||
key={call.ref}>
|
||||
<Button
|
||||
icon={call.connected ? 'phone-slash' : 'phone-alt'}
|
||||
content={call.connected
|
||||
? "Disconnect call from " + call.caller
|
||||
: "Answer call from " + call.caller}
|
||||
color={call.connected ? 'bad' : 'good'}
|
||||
disabled={!on_network}
|
||||
onClick={() => act(call.connected
|
||||
? 'disconnectcall'
|
||||
: 'connectcall', { holopad: call.ref })} />
|
||||
</LabeledList.Item>
|
||||
);
|
||||
}))}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section
|
||||
title="Holodisk"
|
||||
buttons={
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject"
|
||||
disabled={!disk || replay_mode || record_mode}
|
||||
onClick={() => act('disk_eject')} />
|
||||
}>
|
||||
{!disk && (
|
||||
<NoticeBox>
|
||||
No holodisk
|
||||
</NoticeBox>
|
||||
) || (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Disk Player">
|
||||
<Button
|
||||
icon={replay_mode ? 'pause' : 'play'}
|
||||
content={replay_mode ? 'Stop' : 'Replay'}
|
||||
selected={replay_mode}
|
||||
disabled={record_mode || !disk_record}
|
||||
onClick={() => act('replay_mode')} />
|
||||
<Button
|
||||
icon={'sync'}
|
||||
content={loop_mode ? 'Looping' : 'Loop'}
|
||||
selected={loop_mode}
|
||||
disabled={record_mode || !disk_record}
|
||||
onClick={() => act('loop_mode')} />
|
||||
<Button
|
||||
icon="exchange-alt"
|
||||
content="Change Offset"
|
||||
disabled={!replay_mode}
|
||||
onClick={() => act('offset')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Recorder">
|
||||
<Button
|
||||
icon={record_mode ? 'pause' : 'video'}
|
||||
content={record_mode ? 'End Recording' : 'Record'}
|
||||
selected={record_mode}
|
||||
disabled={(disk_record && !record_mode) || replay_mode}
|
||||
onClick={() => act('record_mode')} />
|
||||
<Button
|
||||
icon="trash"
|
||||
content="Clear Recording"
|
||||
color="bad"
|
||||
disabled={!disk_record || replay_mode || record_mode}
|
||||
onClick={() => act('record_clear')} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
)}
|
||||
</Section>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
109
tgui/packages/tgui/interfaces/Jukebox.js
Normal file
109
tgui/packages/tgui/interfaces/Jukebox.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import { sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Dropdown, Section, Knob, LabeledControls, LabeledList } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const Jukebox = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
active,
|
||||
track_selected,
|
||||
track_length,
|
||||
track_beat,
|
||||
volume,
|
||||
} = data;
|
||||
const songs = flow([
|
||||
sortBy(
|
||||
song => song.name),
|
||||
])(data.songs || []);
|
||||
return (
|
||||
<Window
|
||||
width={370}
|
||||
height={313}>
|
||||
<Window.Content>
|
||||
<Section
|
||||
title="Song Player"
|
||||
buttons={(
|
||||
<Button
|
||||
icon={active ? 'pause' : 'play'}
|
||||
content={active ? 'Stop' : 'Play'}
|
||||
selected={active}
|
||||
onClick={() => act('toggle')} />
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Track Selected">
|
||||
<Dropdown
|
||||
overflow-y="scroll"
|
||||
width="240px"
|
||||
options={songs.map(song => song.name)}
|
||||
disabled={active}
|
||||
selected={track_selected || "Select a Track"}
|
||||
onSelected={value => act('select_track', {
|
||||
track: value,
|
||||
})} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Track Length">
|
||||
{track_selected ? track_length : "No Track Selected"}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Track Beat">
|
||||
{track_selected ? track_beat : "No Track Selected"}
|
||||
{track_beat === 1 ? " beat" : " beats"}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section title="Machine Settings">
|
||||
<LabeledControls justify="center">
|
||||
<LabeledControls.Item label="Volume">
|
||||
<Box position="relative">
|
||||
<Knob
|
||||
size={3.2}
|
||||
color={volume >= 50 ? 'red' : 'green'}
|
||||
value={volume}
|
||||
unit="%"
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
step={1}
|
||||
stepPixelSize={1}
|
||||
disabled={active}
|
||||
onDrag={(e, value) => act('set_volume', {
|
||||
volume: value,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="-2px"
|
||||
right="-22px"
|
||||
color="transparent"
|
||||
icon="fast-backward"
|
||||
onClick={() => act('set_volume', {
|
||||
volume: "min",
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="16px"
|
||||
right="-22px"
|
||||
color="transparent"
|
||||
icon="fast-forward"
|
||||
onClick={() => act('set_volume', {
|
||||
volume: "max",
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="34px"
|
||||
right="-22px"
|
||||
color="transparent"
|
||||
icon="undo"
|
||||
onClick={() => act('set_volume', {
|
||||
volume: "reset",
|
||||
})} />
|
||||
</Box>
|
||||
</LabeledControls.Item>
|
||||
</LabeledControls>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
15
tgui/packages/tgui/interfaces/NtosRequestKiosk.js
Normal file
15
tgui/packages/tgui/interfaces/NtosRequestKiosk.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { RequestKioskContent } from './RequestKiosk';
|
||||
import { NtosWindow } from '../layouts';
|
||||
|
||||
export const NtosRequestKiosk = (props, context) => {
|
||||
return (
|
||||
<NtosWindow
|
||||
width={550}
|
||||
height={600}
|
||||
resizable>
|
||||
<NtosWindow.Content scrollable>
|
||||
<RequestKioskContent />
|
||||
</NtosWindow.Content>
|
||||
</NtosWindow>
|
||||
);
|
||||
};
|
||||
114
tgui/packages/tgui/interfaces/PortableChemMixer.js
Normal file
114
tgui/packages/tgui/interfaces/PortableChemMixer.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import { toTitleCase } from 'common/string';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { sortBy } from 'common/collections';
|
||||
|
||||
export const PortableChemMixer = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const recording = !!data.recordingRecipe;
|
||||
const beakerTransferAmounts = data.beakerTransferAmounts || [];
|
||||
const beakerContents = recording
|
||||
&& Object.keys(data.recordingRecipe)
|
||||
.map(id => ({
|
||||
id,
|
||||
name: toTitleCase(id.replace(/_/, ' ')),
|
||||
volume: data.recordingRecipe[id],
|
||||
}))
|
||||
|| data.beakerContents
|
||||
|| [];
|
||||
const chemicals = sortBy(chem => chem.title)(data.chemicals);
|
||||
return (
|
||||
<Window
|
||||
width={645}
|
||||
height={550}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
<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}>
|
||||
{chemicals.map(chemical => (
|
||||
<Button
|
||||
key={chemical.id}
|
||||
icon="tint"
|
||||
width="150px"
|
||||
lineHeight="21px"
|
||||
content={`(${chemical.volume}) ${chemical.title}`}
|
||||
onClick={() => act('dispense', {
|
||||
reagent: chemical.id,
|
||||
})} />
|
||||
))}
|
||||
</Box>
|
||||
</Section>
|
||||
<Section
|
||||
title="Disposal controls"
|
||||
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>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
108
tgui/packages/tgui/interfaces/PortableTurret.js
Normal file
108
tgui/packages/tgui/interfaces/PortableTurret.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, LabeledList, NoticeBox, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const PortableTurret = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
silicon_user,
|
||||
locked,
|
||||
on,
|
||||
check_weapons,
|
||||
neutralize_criminals,
|
||||
neutralize_all,
|
||||
neutralize_unidentified,
|
||||
neutralize_nonmindshielded,
|
||||
neutralize_cyborgs,
|
||||
ignore_heads,
|
||||
manual_control,
|
||||
allow_manual_control,
|
||||
lasertag_turret,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={305}
|
||||
height={lasertag_turret ? 110 : 300}>
|
||||
<Window.Content>
|
||||
<NoticeBox>
|
||||
Swipe an ID card to {locked ? 'unlock' : 'lock'} this interface.
|
||||
</NoticeBox>
|
||||
<Fragment>
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Status"
|
||||
buttons={!lasertag_turret && (!!allow_manual_control
|
||||
|| (!!manual_control && !!silicon_user)) && (
|
||||
<Button
|
||||
icon={manual_control ? "wifi" : "terminal"}
|
||||
content={manual_control
|
||||
? "Remotely Controlled"
|
||||
: "Manual Control"}
|
||||
disabled={manual_control}
|
||||
color="bad"
|
||||
onClick={() => act('manual')} />
|
||||
)}>
|
||||
<Button
|
||||
icon={on ? 'power-off' : 'times'}
|
||||
content={on ? 'On' : 'Off'}
|
||||
selected={on}
|
||||
disabled={locked}
|
||||
onClick={() => act('power')} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
{!lasertag_turret && (
|
||||
<Section
|
||||
title="Target Settings"
|
||||
buttons={(
|
||||
<Button.Checkbox
|
||||
checked={ignore_heads}
|
||||
content="Ignore Heads"
|
||||
disabled={locked}
|
||||
onClick={() => act('shootheads')} />
|
||||
)}>
|
||||
<Button.Checkbox
|
||||
fluid
|
||||
checked={neutralize_all}
|
||||
content="Non-Security and Non-Command"
|
||||
disabled={locked}
|
||||
onClick={() => act('shootall')} />
|
||||
<Button.Checkbox
|
||||
fluid
|
||||
checked={check_weapons}
|
||||
content="Unauthorized Weapons"
|
||||
disabled={locked}
|
||||
onClick={() => act('authweapon')} />
|
||||
<Button.Checkbox
|
||||
fluid
|
||||
checked={neutralize_unidentified}
|
||||
content="Unidentified Life Signs"
|
||||
disabled={locked}
|
||||
onClick={() => act('checkxenos')} />
|
||||
<Button.Checkbox
|
||||
fluid
|
||||
checked={neutralize_nonmindshielded}
|
||||
content="Non-Mindshielded"
|
||||
disabled={locked}
|
||||
onClick={() => act('checkloyal')} />
|
||||
<Button.Checkbox
|
||||
fluid
|
||||
checked={neutralize_criminals}
|
||||
content="Wanted Criminals"
|
||||
disabled={locked}
|
||||
onClick={() => act('shootcriminals')} />
|
||||
<Button.Checkbox
|
||||
fluid
|
||||
checked={neutralize_cyborgs}
|
||||
content="Cyborgs"
|
||||
disabled={locked}
|
||||
onClick={() => act('shootborgs')} />
|
||||
</Section>
|
||||
)}
|
||||
</Fragment>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
83
tgui/packages/tgui/interfaces/ProbingConsole.js
Normal file
83
tgui/packages/tgui/interfaces/ProbingConsole.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, LabeledList, NoticeBox, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const ProbingConsole = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
open,
|
||||
feedback,
|
||||
occupant,
|
||||
occupant_name,
|
||||
occupant_status,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={330}
|
||||
height={207}
|
||||
theme="abductor">
|
||||
<Window.Content>
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Machine Report">
|
||||
{feedback}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section
|
||||
title="Scanner"
|
||||
buttons={(
|
||||
<Button
|
||||
icon={open ? 'sign-out-alt' : 'sign-in-alt'}
|
||||
content={open ? 'Close' : 'Open'}
|
||||
onClick={() => act('door')} />
|
||||
)}>
|
||||
{occupant && (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Name">
|
||||
{occupant_name}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Status"
|
||||
color={occupant_status === 3
|
||||
? 'bad'
|
||||
: occupant_status === 2
|
||||
? 'average'
|
||||
: 'good'}>
|
||||
{occupant_status === 3
|
||||
? 'Deceased'
|
||||
: occupant_status === 2
|
||||
? 'Unconcious'
|
||||
: 'Concious'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Experiments">
|
||||
<Button
|
||||
icon="thermometer"
|
||||
content="Probe"
|
||||
onClick={() => act('experiment', {
|
||||
experiment_type: 1,
|
||||
})} />
|
||||
<Button
|
||||
icon="brain"
|
||||
content="Dissect"
|
||||
onClick={() => act('experiment', {
|
||||
experiment_type: 2,
|
||||
})} />
|
||||
<Button
|
||||
icon="search"
|
||||
content="Analyze"
|
||||
onClick={() => act('experiment', {
|
||||
experiment_type: 3,
|
||||
})} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
) || (
|
||||
<NoticeBox>
|
||||
No Subject
|
||||
</NoticeBox>
|
||||
)}
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
155
tgui/packages/tgui/interfaces/RequestKiosk.js
Normal file
155
tgui/packages/tgui/interfaces/RequestKiosk.js
Normal file
@@ -0,0 +1,155 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Collapsible, Flex, LabeledList, NumberInput, Section, TextArea } from '../components';
|
||||
import { formatMoney } from '../format';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const RequestKiosk = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={550}
|
||||
height={600}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
<RequestKioskContent />
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export const RequestKioskContent = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
accountName,
|
||||
requests = [],
|
||||
applicants = [],
|
||||
bountyValue,
|
||||
bountyText,
|
||||
} = data;
|
||||
const color = 'rgba(13, 13, 213, 0.7)';
|
||||
const backColor = 'rgba(0, 0, 69, 0.5)';
|
||||
return (
|
||||
<Fragment>
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Current Account"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="power-off"
|
||||
content="Log out"
|
||||
onClick={() => act('clear')} />
|
||||
)}>
|
||||
{accountName || 'N/A'}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Flex mb={1}>
|
||||
<Flex.Item grow={1} basis={0}>
|
||||
{requests?.map(request => (
|
||||
<Collapsible
|
||||
key={request.name}
|
||||
title={request.owner}
|
||||
width="300px">
|
||||
<Section
|
||||
key={request.name}
|
||||
width="300px">
|
||||
<Flex spacing={1} align="baseline">
|
||||
<Flex.Item bold width="310px">
|
||||
{request.owner}
|
||||
</Flex.Item>
|
||||
<Flex.Item width="100px">
|
||||
{formatMoney(request.value) + ' cr'}
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Button
|
||||
fluid
|
||||
icon="pen-fancy"
|
||||
content="Apply"
|
||||
onClick={() => act('apply', {
|
||||
request: request.acc_number,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
icon="trash-alt"
|
||||
content="Delete"
|
||||
color="red"
|
||||
onClick={() => act('deleteRequest', {
|
||||
request: request.acc_number,
|
||||
})} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Section align="center">
|
||||
<i>"{request.description}"</i>
|
||||
</Section>
|
||||
<Section
|
||||
title="Request Applicants">
|
||||
{applicants?.map(applicant => (
|
||||
applicant.request_id === request.acc_number && (
|
||||
<Flex>
|
||||
<Flex.Item
|
||||
grow={1}
|
||||
p={0.5}
|
||||
backgroundColor={backColor}
|
||||
width="510px"
|
||||
style={{
|
||||
border: `2px solid ${color}`,
|
||||
}}>
|
||||
{applicant.name}
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
align="end">
|
||||
<Button
|
||||
fluid
|
||||
icon="cash-register"
|
||||
onClick={() => act('payApplicant', {
|
||||
applicant: applicant.requestee_id,
|
||||
request: request.acc_number,
|
||||
})} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
)
|
||||
))}
|
||||
</Section>
|
||||
</Section>
|
||||
</Collapsible>
|
||||
))}
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Collapsible
|
||||
title="New Bounty"
|
||||
width="220px"
|
||||
color="green">
|
||||
<Section>
|
||||
<TextArea
|
||||
fluid
|
||||
height="250px"
|
||||
width="200px"
|
||||
backgroundColor="black"
|
||||
textColor="white"
|
||||
onChange={(e, value) => act('bountyText', {
|
||||
bountytext: value,
|
||||
})} />
|
||||
<Box>
|
||||
<NumberInput
|
||||
animate
|
||||
unit="cr"
|
||||
minValue={1}
|
||||
maxValue={1000}
|
||||
value={bountyValue}
|
||||
width="80px"
|
||||
onChange={(e, value) => act('bountyVal', {
|
||||
bountyval: value,
|
||||
})} />
|
||||
</Box>
|
||||
<Button
|
||||
icon="print"
|
||||
content="Submit bounty"
|
||||
onClick={() => act('createBounty')} />
|
||||
</Section>
|
||||
</Collapsible>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
@@ -1,17 +1,34 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Section, LabeledList, Button, ProgressBar, AnimatedNumber } from '../components';
|
||||
import { Box, Section, LabeledList, Button, ProgressBar } from '../components';
|
||||
import { Fragment } from 'inferno';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const damageTypes = [
|
||||
{
|
||||
label: 'Brute',
|
||||
type: 'bruteLoss',
|
||||
},
|
||||
{
|
||||
label: 'Burn',
|
||||
type: 'fireLoss',
|
||||
},
|
||||
{
|
||||
label: 'Toxin',
|
||||
type: 'toxLoss',
|
||||
},
|
||||
{
|
||||
label: 'Oxygen',
|
||||
type: 'oxyLoss',
|
||||
},
|
||||
];
|
||||
|
||||
export const Sleeper = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const {
|
||||
open,
|
||||
occupant = {},
|
||||
occupied,
|
||||
} = data;
|
||||
|
||||
const preSortChems = data.chems || [];
|
||||
const chems = preSortChems.sort((a, b) => {
|
||||
const descA = a.name.toLowerCase();
|
||||
@@ -36,28 +53,10 @@ export const Sleeper = (props, context) => {
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
const damageTypes = [
|
||||
{
|
||||
label: 'Brute',
|
||||
type: 'bruteLoss',
|
||||
},
|
||||
{
|
||||
label: 'Burn',
|
||||
type: 'fireLoss',
|
||||
},
|
||||
{
|
||||
label: 'Toxin',
|
||||
type: 'toxLoss',
|
||||
},
|
||||
{
|
||||
label: 'Oxygen',
|
||||
type: 'oxyLoss',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Window>
|
||||
<Window
|
||||
width={310}
|
||||
height={465}>
|
||||
<Window.Content>
|
||||
<Section
|
||||
title={occupant.name ? occupant.name : 'No Occupant'}
|
||||
@@ -130,8 +129,8 @@ export const Sleeper = (props, context) => {
|
||||
</LabeledList.Item>
|
||||
</Section>
|
||||
<Section
|
||||
title="Inject Chemicals"
|
||||
minHeight="105px"
|
||||
title="Medicines"
|
||||
minHeight="205px"
|
||||
buttons={(
|
||||
<Button
|
||||
icon={open ? 'door-open' : 'door-closed'}
|
||||
@@ -143,12 +142,11 @@ export const Sleeper = (props, context) => {
|
||||
key={chem.name}
|
||||
icon="flask"
|
||||
content={chem.name}
|
||||
disabled={!(occupied && chem.allowed)}
|
||||
disabled={!occupied || !chem.allowed}
|
||||
width="140px"
|
||||
onClick={() => act('inject', {
|
||||
chem: chem.id,
|
||||
})}
|
||||
/>
|
||||
})} />
|
||||
))}
|
||||
</Section>
|
||||
<Section
|
||||
|
||||
120
tgui/packages/tgui/interfaces/TachyonArray.js
Normal file
120
tgui/packages/tgui/interfaces/TachyonArray.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useSharedState } from '../backend';
|
||||
import { Button, Flex, LabeledList, NoticeBox, Section, Tabs } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const TachyonArray = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
records = [],
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={500}
|
||||
height={225}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
{!records.length ? (
|
||||
<NoticeBox>
|
||||
No Records
|
||||
</NoticeBox>
|
||||
) : (
|
||||
<TachyonArrayContent />
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export const TachyonArrayContent = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
records = [],
|
||||
} = data;
|
||||
const [
|
||||
activeRecordName,
|
||||
setActiveRecordName,
|
||||
] = useSharedState(context, 'record', records[0]?.name);
|
||||
const activeRecord = records.find(record => {
|
||||
return record.name === activeRecordName;
|
||||
});
|
||||
return (
|
||||
<Section>
|
||||
<Flex>
|
||||
<Flex.Item>
|
||||
<Tabs vertical>
|
||||
{records.map(record => (
|
||||
<Tabs.Tab
|
||||
icon="file"
|
||||
key={record.name}
|
||||
selected={record.name === activeRecordName}
|
||||
onClick={() => setActiveRecordName(record.name)}>
|
||||
{record.name}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Flex.Item>
|
||||
{activeRecord ? (
|
||||
<Flex.Item>
|
||||
<Section
|
||||
level="2"
|
||||
title={activeRecord.name}
|
||||
buttons={(
|
||||
<Fragment>
|
||||
<Button.Confirm
|
||||
icon="trash"
|
||||
content="Delete"
|
||||
color="bad"
|
||||
onClick={() => act('delete_record', {
|
||||
'ref': activeRecord.ref,
|
||||
})} />
|
||||
<Button
|
||||
icon="print"
|
||||
content="Print"
|
||||
onClick={() => act('print_record', {
|
||||
'ref': activeRecord.ref,
|
||||
})} />
|
||||
</Fragment>
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Timestamp">
|
||||
{activeRecord.timestamp}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Coordinates">
|
||||
{activeRecord.coordinates}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Displacement">
|
||||
{activeRecord.displacement} seconds
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Epicenter Radius">
|
||||
{activeRecord.factual_epicenter_radius}
|
||||
{activeRecord.theory_epicenter_radius
|
||||
&& " (Theoretical: "
|
||||
+ activeRecord.theory_epicenter_radius + ")"}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Outer Radius">
|
||||
{activeRecord.factual_outer_radius}
|
||||
{activeRecord.theory_outer_radius
|
||||
&& " (Theoretical: "
|
||||
+ activeRecord.theory_outer_radius + ")"}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Shockwave Radius">
|
||||
{activeRecord.factual_shockwave_radius}
|
||||
{activeRecord.theory_shockwave_radius
|
||||
&& " (Theoretical: "
|
||||
+ activeRecord.theory_shockwave_radius + ")"}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
) : (
|
||||
<Flex.Item grow={1} basis={0}>
|
||||
<NoticeBox>
|
||||
No Record Selected
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
)}
|
||||
</Flex>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
51
tgui/packages/tgui/interfaces/TurretControl.js
Normal file
51
tgui/packages/tgui/interfaces/TurretControl.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';
|
||||
|
||||
export const TurretControl = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const locked = data.locked && !data.siliconUser;
|
||||
const {
|
||||
enabled,
|
||||
lethal,
|
||||
shootCyborgs,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={305}
|
||||
height={168}>
|
||||
<Window.Content>
|
||||
<InterfaceLockNoticeBox />
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Turret Status">
|
||||
<Button
|
||||
icon={enabled ? 'power-off' : 'times'}
|
||||
content={enabled ? 'Enabled' : 'Disabled'}
|
||||
selected={enabled}
|
||||
disabled={locked}
|
||||
onClick={() => act('power')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Turret Mode">
|
||||
<Button
|
||||
icon={lethal ? 'exclamation-triangle' : 'minus-circle'}
|
||||
content={lethal ? 'Lethal' : 'Stun'}
|
||||
color={lethal ? "bad" : "average"}
|
||||
disabled={locked}
|
||||
onClick={() => act('mode')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Target Cyborgs">
|
||||
<Button
|
||||
icon={shootCyborgs ? 'check' : 'times'}
|
||||
content={shootCyborgs ? 'Yes' : 'No'}
|
||||
selected={shootCyborgs}
|
||||
disabled={locked}
|
||||
onClick={() => act('shoot_silicons')} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
97
tgui/packages/tgui/interfaces/Vendatray.js
Normal file
97
tgui/packages/tgui/interfaces/Vendatray.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Flex, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const Vendatray = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
product_name,
|
||||
product_cost,
|
||||
tray_open,
|
||||
registered,
|
||||
owner_name,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={300}
|
||||
height={270}>
|
||||
<Window.Content>
|
||||
<Flex
|
||||
mb={1}>
|
||||
<Flex.Item
|
||||
mr={1}>
|
||||
{!!product_name && (
|
||||
<VendingImage />
|
||||
)}
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
grow={1}>
|
||||
<Section
|
||||
fontSize="18px"
|
||||
align="center">
|
||||
<b>{product_name ? product_name : "Empty"}</b>
|
||||
<Box fontSize="16px">
|
||||
<i>{product_name ? product_cost : "N/A"} cr </i>
|
||||
<Button
|
||||
icon="pen"
|
||||
onClick={() => act('Adjust')} />
|
||||
</Box>
|
||||
</Section>
|
||||
<Fragment>
|
||||
<Button
|
||||
fluid
|
||||
icon="window-restore"
|
||||
content={tray_open ? 'Open' : 'Closed'}
|
||||
selected={tray_open}
|
||||
onClick={() => act('Open')} />
|
||||
<Button.Confirm
|
||||
fluid
|
||||
icon="money-bill-wave"
|
||||
content="Purchase Item"
|
||||
disabled={!product_name}
|
||||
onClick={() => act('Buy')} />
|
||||
</Fragment>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
{registered?(
|
||||
<Section italics>
|
||||
Pays to the account of {owner_name}.
|
||||
</Section>
|
||||
):(
|
||||
<Fragment>
|
||||
<Section>
|
||||
Tray is unregistered.
|
||||
</Section>
|
||||
<Button
|
||||
fluid
|
||||
icon="cash-register"
|
||||
content="Register Tray"
|
||||
disabled={registered}
|
||||
onClick={() => act('Register')} />
|
||||
</Fragment>
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const VendingImage = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const {
|
||||
product_icon,
|
||||
} = data;
|
||||
return (
|
||||
<Section height="100%">
|
||||
<Box as="img"
|
||||
m={1}
|
||||
src={`data:image/jpeg;base64,${product_icon}`}
|
||||
height="96px"
|
||||
width="96px"
|
||||
style={{
|
||||
'-ms-interpolation-mode': 'nearest-neighbor',
|
||||
'vertical-align': 'middle',
|
||||
}} />
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user