This commit is contained in:
Letter N
2020-07-22 10:02:34 +08:00
parent 5b73178ba0
commit 0c61efda93
19 changed files with 2718 additions and 29 deletions

View 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>
);
};

View 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;

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
}
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>
);
};

View 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>&quot;{request.description}&quot;</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>
);
};

View File

@@ -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

View 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>
);
};

View 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>
);
};

View 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>
);
};