mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2025-12-11 10:22:13 +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 { 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 { Fragment } from 'inferno';
|
||||||
import { Window } from '../layouts';
|
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) => {
|
export const Sleeper = (props, context) => {
|
||||||
const { act, data } = useBackend(context);
|
const { act, data } = useBackend(context);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
open,
|
open,
|
||||||
occupant = {},
|
occupant = {},
|
||||||
occupied,
|
occupied,
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
const preSortChems = data.chems || [];
|
const preSortChems = data.chems || [];
|
||||||
const chems = preSortChems.sort((a, b) => {
|
const chems = preSortChems.sort((a, b) => {
|
||||||
const descA = a.name.toLowerCase();
|
const descA = a.name.toLowerCase();
|
||||||
@@ -36,28 +53,10 @@ export const Sleeper = (props, context) => {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const damageTypes = [
|
|
||||||
{
|
|
||||||
label: 'Brute',
|
|
||||||
type: 'bruteLoss',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Burn',
|
|
||||||
type: 'fireLoss',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Toxin',
|
|
||||||
type: 'toxLoss',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Oxygen',
|
|
||||||
type: 'oxyLoss',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Window>
|
<Window
|
||||||
|
width={310}
|
||||||
|
height={465}>
|
||||||
<Window.Content>
|
<Window.Content>
|
||||||
<Section
|
<Section
|
||||||
title={occupant.name ? occupant.name : 'No Occupant'}
|
title={occupant.name ? occupant.name : 'No Occupant'}
|
||||||
@@ -130,8 +129,8 @@ export const Sleeper = (props, context) => {
|
|||||||
</LabeledList.Item>
|
</LabeledList.Item>
|
||||||
</Section>
|
</Section>
|
||||||
<Section
|
<Section
|
||||||
title="Inject Chemicals"
|
title="Medicines"
|
||||||
minHeight="105px"
|
minHeight="205px"
|
||||||
buttons={(
|
buttons={(
|
||||||
<Button
|
<Button
|
||||||
icon={open ? 'door-open' : 'door-closed'}
|
icon={open ? 'door-open' : 'door-closed'}
|
||||||
@@ -143,12 +142,11 @@ export const Sleeper = (props, context) => {
|
|||||||
key={chem.name}
|
key={chem.name}
|
||||||
icon="flask"
|
icon="flask"
|
||||||
content={chem.name}
|
content={chem.name}
|
||||||
disabled={!(occupied && chem.allowed)}
|
disabled={!occupied || !chem.allowed}
|
||||||
width="140px"
|
width="140px"
|
||||||
onClick={() => act('inject', {
|
onClick={() => act('inject', {
|
||||||
chem: chem.id,
|
chem: chem.id,
|
||||||
})}
|
})} />
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</Section>
|
</Section>
|
||||||
<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