Fixes, Reworks, New Tabs and General Misery

This commit is contained in:
Aleksej Komarov
2020-04-19 19:38:55 +03:00
parent f73d00d2c4
commit 2979838c38
40 changed files with 1641 additions and 1548 deletions
@@ -97,7 +97,7 @@
if(!ui)
var/datum/asset/spritesheet/simple/assets = get_asset_datum(/datum/asset/spritesheet/simple/achievements)
assets.send(user)
ui = new(user, src, ui_key, "Achievements", "Achievements Menu", 800, 1000, master_ui, state)
ui = new(user, src, ui_key, "Achievements", "Achievements Menu", 540, 680, master_ui, state)
ui.open()
/datum/achievement_data/ui_data(mob/user)
+1 -2
View File
@@ -135,8 +135,7 @@
var/list/data = list()
data["telecrystals"] = telecrystals
data["lockable"] = lockable
data["compact_mode"] = compact_mode
data["compactMode"] = compact_mode
return data
/datum/component/uplink/ui_static_data(mob/user)
+1 -2
View File
@@ -96,7 +96,6 @@
else
data["patient"] = null
return data
switch(patient.stat)
if(CONSCIOUS)
data["patient"]["stat"] = "Conscious"
@@ -118,8 +117,8 @@
data["patient"]["fireLoss"] = patient.getFireLoss()
data["patient"]["toxLoss"] = patient.getToxLoss()
data["patient"]["oxyLoss"] = patient.getOxyLoss()
data["procedures"] = list()
if(patient.surgeries.len)
data["procedures"] = list()
for(var/datum/surgery/procedure in patient.surgeries)
var/datum/surgery_step/surgery_step = procedure.get_surgery_step()
var/chems_needed = surgery_step.get_chem_list()
+8 -23
View File
@@ -172,7 +172,7 @@
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "MedicalKiosk", name, 625, 550, master_ui, state)
ui = new(user, src, ui_key, "MedicalKiosk", name, 575, 420, master_ui, state)
ui.open()
icon_state = "kiosk_off"
RefreshParts()
@@ -236,29 +236,18 @@
trauma_text += trauma_desc
trauma_status = "Cerebral traumas detected: patient appears to be suffering from [english_list(trauma_text)]."
var/chem_status = FALSE
var/chemical_list= list()
var/overdose_status = FALSE
var/chemical_list = list()
var/overdose_list = list()
var/addict_status = FALSE
var/addict_list = list()
var/hallucination_status = "Patient is not hallucinating."
for(var/datum/reagent/R in altPatient.reagents.reagent_list)
if(R.overdosed)
overdose_status = TRUE
if(altPatient.reagents.reagent_list.len) //Chemical Analysis details.
chem_status = TRUE
for(var/datum/reagent/R in altPatient.reagents.reagent_list)
chemical_list += list(list("name" = R.name, "volume" = round(R.volume, 0.01)))
if(R.overdosed == 1)
overdose_list += list(list("name" = R.name))
else
chemical_list = "Patient contains no reagents"
if(altPatient.reagents.addiction_list.len)
addict_status = TRUE
for(var/datum/reagent/R in altPatient.reagents.addiction_list)
addict_list += list(list("name" = R.name))
if (altPatient.hallucinating())
@@ -296,7 +285,6 @@
else if (user.hallucinating())
chaos_modifier = 0.3
data["kiosk_cost"] = active_price + (chaos_modifier * (rand(1,25)))
data["patient_name"] = patient_name
data["patient_health"] = round(((total_health - (chaos_modifier * (rand(1,50)))) / max_health) * 100, 0.001)
@@ -316,18 +304,15 @@
data["bleed_status"] = bleed_status
data["blood_levels"] = blood_percent - (chaos_modifier * (rand(1,35)))
data["blood_status"] = blood_status
data["are_chems_present"] = chem_status ? TRUE : FALSE
data["chemical_list"] = chemical_list
data["are_overdoses_present"] = overdose_status ? TRUE : FALSE
data["overdose_status"] = overdose_list
data["are_addictions_present"] = addict_status ? TRUE : FALSE
data["addiction_status"] = addict_list
data["overdose_list"] = overdose_list
data["addict_list"] = addict_list
data["hallucinating_status"] = hallucination_status
data["active_status_1"] = scan_active_1 ? FALSE : TRUE //General Scan Check
data["active_status_2"] = scan_active_2 ? FALSE : TRUE //Symptom Scan Check
data["active_status_3"] = scan_active_3 ? FALSE : TRUE //Radio-Neuro Scan Check
data["active_status_4"] = scan_active_4 ? FALSE : TRUE //Radio-Neuro Scan Check
data["active_status_1"] = scan_active_1 // General Scan Check
data["active_status_2"] = scan_active_2 // Symptom Scan Check
data["active_status_3"] = scan_active_3 // Radio-Neuro Scan Check
data["active_status_4"] = scan_active_4 // Radio-Neuro Scan Check
return data
/obj/machinery/medical_kiosk/ui_act(action,active)
@@ -39,8 +39,8 @@
/datum/module_picker/ui_data(mob/user)
var/list/data = list()
data["processing_time"] = processing_time
data["compact_mode"] = compact_mode
data["processingTime"] = processing_time
data["compactMode"] = compact_mode
return data
/datum/module_picker/ui_static_data(mob/user)
@@ -4,10 +4,6 @@
assets = list(
"tgui.bundle.js" = 'tgui/packages/tgui/public/tgui.bundle.js',
"tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css',
"shim-html5shiv.js" = 'tgui/packages/tgui/public/shim-html5shiv.js',
"shim-ie8.js" = 'tgui/packages/tgui/public/shim-ie8.js',
"shim-dom4.js" = 'tgui/packages/tgui/public/shim-dom4.js',
"shim-css-om.js" = 'tgui/packages/tgui/public/shim-css-om.js',
)
/datum/asset/group/tgui
@@ -4,7 +4,7 @@
icon_state = "uplink"
// UI variables.
var/ui_x = 720
var/ui_x = 600
var/ui_y = 480
var/viewing_category
var/viewing_market
@@ -58,7 +58,7 @@
/obj/item/blackmarket_uplink/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "BlackmarketUplink", name, ui_x, ui_y, master_ui, state)
ui = new(user, src, ui_key, "BlackMarketUplink", name, ui_x, ui_y, master_ui, state)
ui.open()
/obj/item/blackmarket_uplink/ui_data(mob/user)
+40 -40
View File
@@ -171,6 +171,13 @@ all available horizontal space.
- `mb: number` - Bottom margin.
- `ml: number` - Left margin.
- `mr: number` - Right margin.
- `p: number` - Padding on all sides.
- `px: number` - Horizontal padding.
- `py: number` - Vertical padding.
- `pt: number` - Top padding.
- `pb: number` - Bottom padding.
- `pl: number` - Left padding.
- `pr: number` - Right padding.
- `opacity: number` - Opacity, from 0 to 1.
- `bold: boolean` - Make text bold.
- `italic: boolean` - Make text italic.
@@ -848,55 +855,47 @@ Here is an example of how you would construct a simple tabbed view:
```jsx
<Tabs>
<Tabs.Tab label="Item one">
Content for Item one.
<Tabs.Tab
selected={tabIndex === 1}
onClick={() => setTabIndex(1)}>
Tab one
</Tabs.Tab>
<Tabs.Tab label="Item two">
Content for Item two.
<Tabs.Tab
selected={tabIndex === 2}
onClick={() => setTabIndex(2)}>
Tab two
</Tabs.Tab>
</Tabs>
<Box>
Tab selected: {tabIndex}
</Box>
```
This is a rather simple example. In the real world, you might be
constructing very complex tabbed views which can tax UI performance.
This is because your tabs are being rendered regardless of their
visibility status!
Notice that tabs do not contain state. It is your job to track the selected
tab, handle clicks and place tab content where you need it. In return, you get
a lot of flexibility in regards to how you can layout your tabs.
There is a simple fix however. Tabs accept functions as children, which
will be called to retrieve content only when the tab is visible:
Tabs also support a vertical configuration. This is usually paired with a
[Flex](#flex) component to render tab content to the right.
```jsx
<Tabs>
<Tabs.Tab key="tab_1" label="Item one">
{() => (
<Fragment>
Content for Item one.
</Fragment>
)}
</Tabs.Tab>
<Tabs.Tab key="tab_2" label="Item two">
{() => (
<Fragment>
Content for Item two.
</Fragment>
)}
</Tabs.Tab>
</Tabs>
<Flex>
<Flex.Item>
<Tabs vertical>
...
</Tabs>
</Flex.Item>
<Flex.Item grow={1} basis={0}>
Tab content.
</Flex.Item>
</Flex>
```
You might not always need this, but it is highly recommended to always
use this method. Notice the `key` prop on tabs - it uniquely identifies
the tab and is used for determining which tab is currently active. It can
be either explicitly provided as a `key` prop, or if omitted, it will be
implicitly derived from the tab's `label` prop.
Props:
- `vertical: boolean` - Use a vertical configuration, where tabs will appear
stacked on the left side of the container.
- `altSelection` - Whether the tab buttons select via standard select (color
change) or by adding a white indicator to the selected tab.
Intended for usage on interfaces where tab color has relevance.
- See inherited props: [Box](#box)
- `vertical: boolean` - Use a vertical configuration, where tabs will be
stacked vertically.
- `children: Tab[]` - This component only accepts tabs as its children.
### `Tabs.Tab`
@@ -907,10 +906,11 @@ a lot of `Button` props.
Props:
- See inherited props: [Button](#button)
- `key: string` - A unique identifier for the tab.
- `label: string` - Tab label.
- `altSelection` - Whether the tab buttons select via standard select (color
change) or by adding a white indicator to the selected tab.
Intended for usage on interfaces where tab color has relevance.
- `icon: string` - Tab icon.
- `content/children: any` - Content to render inside the tab.
- `children: any` - Tab text.
- `onClick: function` - Called when element is clicked.
### `Tooltip`
+23 -3
View File
@@ -82,13 +82,33 @@ const styleMapperByPropName = {
italic: mapBooleanPropTo('font-style', 'italic'),
nowrap: mapBooleanPropTo('white-space', 'nowrap'),
// Margins
m: mapDirectionalUnitPropTo('margin', ['top', 'bottom', 'left', 'right']),
mx: mapDirectionalUnitPropTo('margin', ['left', 'right']),
my: mapDirectionalUnitPropTo('margin', ['top', 'bottom']),
m: mapDirectionalUnitPropTo('margin', [
'top', 'bottom', 'left', 'right',
]),
mx: mapDirectionalUnitPropTo('margin', [
'left', 'right',
]),
my: mapDirectionalUnitPropTo('margin', [
'top', 'bottom',
]),
mt: mapUnitPropTo('margin-top'),
mb: mapUnitPropTo('margin-bottom'),
ml: mapUnitPropTo('margin-left'),
mr: mapUnitPropTo('margin-right'),
// Margins
p: mapDirectionalUnitPropTo('padding', [
'top', 'bottom', 'left', 'right',
]),
px: mapDirectionalUnitPropTo('padding', [
'left', 'right',
]),
py: mapDirectionalUnitPropTo('padding', [
'top', 'bottom',
]),
pt: mapUnitPropTo('padding-top'),
pb: mapUnitPropTo('padding-bottom'),
pl: mapUnitPropTo('padding-left'),
pr: mapUnitPropTo('padding-right'),
// Color props
color: mapColorPropTo('color'),
textColor: mapColorPropTo('color'),
+15 -7
View File
@@ -1,19 +1,27 @@
import { classes, pureComponentHooks } from 'common/react';
import { Box } from './Box';
import { computeBoxClassName, computeBoxProps } from './Box';
export const ColorBox = props => {
const { color, content, className, ...rest } = props;
const {
content,
children,
className,
color,
backgroundColor,
...rest
} = props;
rest.color = content ? null : 'transparent';
rest.backgroundColor = color || backgroundColor;
return (
<Box
<div
className={classes([
'ColorBox',
className,
computeBoxClassName(rest),
])}
color={content ? null : 'transparent'}
backgroundColor={color}
{...rest}>
{...computeBoxProps(rest)}>
{content || '.'}
</Box>
</div>
);
};
+12 -4
View File
@@ -1,5 +1,6 @@
import { classes, pureComponentHooks } from 'common/react';
import { Box, unit } from './Box';
import { Divider } from './Divider';
export const LabeledList = props => {
const { children } = props;
@@ -62,12 +63,19 @@ export const LabeledListItem = props => {
LabeledListItem.defaultHooks = pureComponentHooks;
export const LabeledListDivider = props => {
const { size = 1 } = props;
const padding = props.size
? unit(Math.max(0, props.size - 1))
: 0;
return (
<tr className="LabeledList__row">
<td style={{
'padding-bottom': unit(size),
}} />
<td
colSpan={3}
style={{
'padding-top': padding,
'padding-bottom': padding,
}}>
<Divider />
</td>
</tr>
);
};
+24 -6
View File
@@ -56,10 +56,28 @@ export const formatPower = (value, minBase1000 = 0) => {
};
export const formatMoney = (value, precision = 0) => {
return round(value, precision)
.toLocaleString('en', {
minimumFractionDigits: precision,
})
// Thin space
.replace(/,/g, '\u2009');
if (!Number.isFinite(value)) {
return value;
}
// Round the number and make it fixed precision
let fixed = round(value, precision);
if (precision > 0) {
fixed = toFixed(value, precision);
}
fixed = String(fixed);
// Place thousand separators
const length = fixed.length;
let indexOfPoint = fixed.indexOf('.');
if (indexOfPoint === -1) {
indexOfPoint = length;
}
let result = '';
for (let i = 0; i < length; i++) {
if (i > 0 && i < indexOfPoint && (indexOfPoint - i) % 3 === 0) {
// Thin space
result += '\u2009';
}
result += fixed.charAt(i);
}
return result;
};
+14 -4
View File
@@ -1,13 +1,23 @@
// Polyfills
import 'core-js/es';
import 'core-js/web/immediate';
import 'core-js/web/queue-microtask';
import 'core-js/web/timers';
import 'regenerator-runtime/runtime';
import './polyfills/html5shiv';
import './polyfills/ie8';
import './polyfills/dom4';
import './polyfills/css-om';
import './polyfills/inferno';
// This one is necessary for Inferno.
if (!window.Int32Array) {
window.Int32Array = Array;
}
// Themes
import './styles/main.scss';
import './styles/themes/cardtable.scss';
import './styles/themes/malfunction.scss';
import './styles/themes/ntos.scss';
import './styles/themes/hackerman.scss';
import './styles/themes/retro.scss';
import './styles/themes/syndicate.scss';
import { loadCSS } from 'fg-loadcss';
import { render } from 'inferno';
+57 -54
View File
@@ -1,7 +1,6 @@
import { useBackend, useLocalState } from '../backend';
import { Box, Icon, Table, Tabs } from '../components';
import { Box, Flex, Icon, Table, Tabs } from '../components';
import { Window } from '../layouts';
import { Fragment } from 'inferno';
export const Achievements = (props, context) => {
const { data } = useBackend(context);
@@ -63,11 +62,11 @@ const Achievement = props => {
score,
} = achievement;
return (
<tr key={name}>
<td style={{ 'padding': '6px' }}>
<Box className={icon_class} />
</td>
<td style={{ 'vertical-align': 'top' }}>
<Table.Row key={name}>
<Table.Cell collapsing>
<Box m={1} className={icon_class} />
</Table.Cell>
<Table.Cell verticalAlign="top">
<h1>{name}</h1>
{desc}
{score && (
@@ -79,8 +78,8 @@ const Achievement = props => {
{value ? 'Unlocked' : 'Locked'}
</Box>
)}
</td>
</tr>
</Table.Cell>
</Table.Row>
);
};
@@ -105,54 +104,58 @@ const HighScoreTable = (props, context) => {
value: highscore.scores[key],
}));
return (
<Fragment>
<Tabs vertical>
{highscores.map((highscore, i) => (
<Tabs.Tab
key={highscore.name}
selected={highScoreIndex === i}
onClick={() => setHighScoreIndex(i)}>
{highscore.name}
</Tabs.Tab>
))}
</Tabs>
<Table>
<Table.Row className="candystripe">
<Table.Cell color="label" textAlign="center">
#
</Table.Cell>
<Table.Cell color="label" textAlign="center">
Key
</Table.Cell>
<Table.Cell color="label" textAlign="center">
Score
</Table.Cell>
</Table.Row>
{scores.map((score, i) => (
<Table.Row
key={score.ckey}
className="candystripe"
m={2}>
<Table.Cell color="label" textAlign="center">
{i + 1}
</Table.Cell>
<Table.Cell
color={score.ckey === user_ckey && 'green'}
textAlign="center">
{i === 0 && (
<Icon name="crown" color="gold" mr={2} />
)}
{score.ckey}
{i === 0 && (
<Icon name="crown" color="gold" ml={2} />
)}
<Flex>
<Flex.Item>
<Tabs vertical>
{highscores.map((highscore, i) => (
<Tabs.Tab
key={highscore.name}
selected={highScoreIndex === i}
onClick={() => setHighScoreIndex(i)}>
{highscore.name}
</Tabs.Tab>
))}
</Tabs>
</Flex.Item>
<Flex.Item grow={1} basis={0}>
<Table>
<Table.Row header>
<Table.Cell textAlign="center">
#
</Table.Cell>
<Table.Cell textAlign="center">
{score.value}
Key
</Table.Cell>
<Table.Cell textAlign="center">
Score
</Table.Cell>
</Table.Row>
))}
</Table>
</Fragment>
{scores.map((score, i) => (
<Table.Row
key={score.ckey}
className="candystripe"
m={2}>
<Table.Cell color="label" textAlign="center">
{i + 1}
</Table.Cell>
<Table.Cell
color={score.ckey === user_ckey && 'green'}
textAlign="center">
{i === 0 && (
<Icon name="crown" color="yellow" mr={2} />
)}
{score.ckey}
{i === 0 && (
<Icon name="crown" color="yellow" ml={2} />
)}
</Table.Cell>
<Table.Cell textAlign="center">
{score.value}
</Table.Cell>
</Table.Row>
))}
</Table>
</Flex.Item>
</Flex>
);
};
@@ -1,45 +1,145 @@
import { map } from 'common/collections';
import { useBackend } from '../backend';
import { AnimatedNumber, Box, Button, Flex, Modal, Section, Table, Tabs } from '../components';
import { formatMoney } from '../format';
import { Window } from '../layouts';
export const BlackmarketUplink = (props, context) => {
export const BlackMarketUplink = (props, context) => {
const { act, data } = useBackend(context);
const categories = data.categories || [];
const deliveryMethods = data.delivery_methods || [];
const deliveryMethodDesc = data.delivery_method_description || [];
const markets = data.markets || {};
const items = data.items || {};
const {
categories = [],
markets = [],
items = [],
money,
viewing_market,
viewing_category,
} = data;
return (
<Window
theme="hackerman"
resizable>
<ShipmentSelector />
<Window.Content scrollable>
<Section
title="Black Market Uplink"
buttons={(
<Box inline bold>
<AnimatedNumber
value={money}
format={value => formatMoney(value) + ' cr'} />
</Box>
)} />
<Tabs>
{markets.map(market => (
<Tabs.Tab
key={market.id}
selected={market.id === viewing_market}
onClick={() => act('set_market', {
market: market.id,
})}>
{market.name}
</Tabs.Tab>
))}
</Tabs>
<Flex>
<Flex.Item>
<Tabs vertical>
{categories.map(category => (
<Tabs.Tab
key={category}
height={4}
mt={0.5}
selected={viewing_category === category}
onClick={() => act('set_category', {
category: category,
})}>
{category}
</Tabs.Tab>
))}
</Tabs>
</Flex.Item>
<Flex.Item grow={1} basis={0}>
{items.map(item => (
<Box
key={item.name}
className="candystripe"
p={1}
pb={2}>
<Flex spacing={1} align="baseline">
<Flex.Item bold grow={1}>
{item.name}
</Flex.Item>
<Flex.Item color="label">
{item.amount
? item.amount + " in stock"
: "Out of stock"}
</Flex.Item>
<Flex.Item>
{formatMoney(item.cost) + ' cr'}
</Flex.Item>
<Flex.Item>
<Button
content="Buy"
disabled={!item.amount || item.cost > money}
onClick={() => act('select', {
item: item.id,
})} />
</Flex.Item>
</Flex>
{item.desc}
</Box>
))}
</Flex.Item>
</Flex>
</Window.Content>
</Window>
);
};
const shipmentSelector = !!data.buying && (
const ShipmentSelector = (props, context) => {
const { act, data } = useBackend(context);
const {
buying,
ltsrbt_built,
money,
} = data;
if (!buying) {
return null;
}
const deliveryMethods = data.delivery_methods.map(method => {
const description = data.delivery_method_description[method.name];
return {
...method,
description,
};
});
return (
<Modal textAlign="center">
<Flex mb={1}>
{map(deliveryMethod => {
const name = deliveryMethod.name;
if (name === "LTSRBT" && !data.ltsrbt_built) {
{deliveryMethods.map(method => {
if (method.name === 'LTSRBT' && !ltsrbt_built) {
return null;
}
return (
<Flex.Item
key={name}
key={method.name}
mx={1}
width="250px">
<Box fontSize="30px">
{name}
{method.name}
</Box>
<Box mt={1}>
{deliveryMethodDesc[name]}
{method.description}
</Box>
<Button
mt={2}
content={deliveryMethod.price+' cr'}
disabled={data.money < deliveryMethod.price}
content={formatMoney(method.price) + ' cr'}
disabled={money < method.price}
onClick={() => act('buy', {
method: name,
method: method.name,
})} />
</Flex.Item>
);
})(deliveryMethods)}
})}
</Flex>
<Button
content="Cancel"
@@ -47,83 +147,4 @@ export const BlackmarketUplink = (props, context) => {
onClick={() => act('cancel')} />
</Modal>
);
return (
<Window
theme="hackerman"
resizable>
{shipmentSelector}
<Window.Content scrollable>
<Section
title="Black Market Uplink"
buttons={(
<Box inline bold>
<AnimatedNumber value={Math.round(data.money)} /> cr
</Box>
)} />
<Tabs
activeTab={data.viewing_market}>
{map(market => {
const id = market.id;
const name = market.name;
return (
<Tabs.Tab
key={id}
label={name}
onClick={() => act('set_market', {
market: id,
})} />
);
})(markets)}
</Tabs>
<Box>
<Tabs vertical
activeTab={data.viewing_category}>
{categories.map(category => (
<Tabs.Tab
key={category}
label={category}
height={4}
mt={0.5}
onClick={() => act('set_category', {
category: category,
})}>
{items.map(item => (
<Table
key={item.name}
mt={1}
className="candystripe">
<Table.Row>
<Table.Cell bold>
{item.name}
</Table.Cell>
<Table.Cell collapsing textAlign="right">
{item.amount ? item.amount+" in stock" : "Out of stock"}
</Table.Cell>
<Table.Cell collapsing textAlign="right">
{item.cost+'cr'}
</Table.Cell>
<Table.Cell collapsing textAlign="right">
<Button
content="Buy"
disabled={!item.amount || item.cost > data.money}
onClick={() => act('select', {
item: item.id,
})} />
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
{item.desc}
</Table.Cell>
</Table.Row>
</Table>
))}
</Tabs.Tab>
))}
</Tabs>
</Box>
</Window.Content>
</Window>
);
};
@@ -1,167 +1,21 @@
import { createSearch, decodeHtmlEntities } from 'common/string';
import { Fragment } from 'inferno';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Input, Section, Table, Tabs } from '../components';
import { useBackend } from '../backend';
import { Window } from '../layouts';
const MAX_SEARCH_RESULTS = 25;
import { GenericUplink } from './Uplink';
export const MalfunctionModulePicker = (props, context) => {
const { act, data } = useBackend(context);
const {
compact_mode,
processing_time,
categories = [],
processingTime,
} = data;
const [
hoveredItem,
setHoveredItem,
] = useLocalState(context, 'hoveredItem', {});
const [
searchText,
setSearchText,
] = useLocalState(context, 'searchText', '');
const testSearch = createSearch(searchText, item => {
return item.name + item.desc;
});
return (
<Window
theme="malfunction"
resizable>
<Window.Content scrollable>
<Section
title={(
<Box
inline
color={processing_time > 0 ? 'good' : 'bad'}>
{processing_time} Processing Time
</Box>
)}
buttons={(
<Fragment>
Search
<Input
value={searchText}
onInput={(e, value) => setSearchText(value)}
ml={1}
mr={1} />
<Button
icon={compact_mode ? 'list' : 'info'}
content={compact_mode ? 'Compact' : 'Detailed'}
onClick={() => act('compact_toggle')} />
</Fragment>
)}>
{searchText.length > 0 ? (
<table className="Table">
<ItemList
compact
items={categories
.flatMap(category => category.items || [])
.filter(testSearch)
.filter((item, i) => i < MAX_SEARCH_RESULTS)}
hoveredItem={hoveredItem}
onBuyMouseOver={item => setHoveredItem(item)}
onBuyMouseOut={item => setHoveredItem({})}
onBuy={item => act('buy', {
ref: item.ref,
})} />
</table>
) : (
<Tabs vertical>
{categories.map(category => {
const { name, items } = category;
if (items === null) {
return;
}
return (
<Tabs.Tab
key={name}
label={`${name} (${items.length})`}>
{() => (
<ItemList
compact={compact_mode}
items={items}
hoveredItem={hoveredItem}
processing_time={processing_time}
onBuyMouseOver={item => setHoveredItem(item)}
onBuyMouseOut={item => setHoveredItem({})}
onBuy={item => act('buy', {
ref: item.ref,
})} />
)}
</Tabs.Tab>
);
})}
</Tabs>
)}
</Section>
<GenericUplink
currencyAmount={processingTime}
currencySymbol="PT" />
</Window.Content>
</Window>
);
};
const ItemList = props => {
const {
items,
hoveredItem,
processing_time,
compact,
onBuy,
onBuyMouseOver,
onBuyMouseOut,
} = props;
const hoveredCost = hoveredItem && hoveredItem.cost || 0;
if (compact) {
return (
<Table>
{items.map(item => {
const notSameItem = hoveredItem && hoveredItem.name !== item.name;
const notEnoughHovered = processing_time - hoveredCost < item.cost;
const disabledDueToHovered = notSameItem && notEnoughHovered;
return (
<Table.Row
key={item.name}
className="candystripe">
<Table.Cell bold>
{decodeHtmlEntities(item.name)}
</Table.Cell>
<Table.Cell collapsing textAlign="right">
<Button
fluid
content={item.cost + ' PT'}
disabled={processing_time < item.cost
|| disabledDueToHovered}
tooltip={item.desc}
tooltipPosition="left"
onmouseover={() => onBuyMouseOver(item)}
onmouseout={() => onBuyMouseOut(item)}
onClick={() => onBuy(item)} />
</Table.Cell>
</Table.Row>
);
})}
</Table>
);
}
return items.map(item => {
const notSameItem = hoveredItem && hoveredItem.name !== item.name;
const notEnoughHovered = processing_time - hoveredCost < item.cost;
const disabledDueToHovered = notSameItem && notEnoughHovered;
return (
<Section
key={item.name}
title={item.name}
level={2}
buttons={(
<Button
content={item.cost + ' PT'}
disabled={processing_time < item.cost || disabledDueToHovered}
onmouseover={() => onBuyMouseOver(item)}
onmouseout={() => onBuyMouseOut(item)}
onClick={() => onBuy(item)} />
)}>
{decodeHtmlEntities(item.desc)}
</Section>
);
});
};
+346 -290
View File
@@ -1,302 +1,358 @@
import { multiline } from 'common/string';
import { useBackend } from '../backend';
import { AnimatedNumber, Box, Button, LabeledList, ProgressBar, Section, Tabs } from '../components';
import { useBackend, useSharedState } from '../backend';
import { AnimatedNumber, Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from '../components';
import { Window } from '../layouts';
export const MedicalKiosk = (props, context) => {
const { act, data } = useBackend(context);
const [scanIndex] = useSharedState(context, 'scanIndex');
const {
active_status_1,
active_status_2,
active_status_3,
active_status_4,
} = data;
return (
<Window resizable>
<Window.Content scrollable>
<Section title="Health Kiosk"
textAlign="center"
icon="procedures">
<Box my={1} textAlign="center">
Greetings Valued Employee. Please select your desired diagnosis.
Diagnosis costs {data.kiosk_cost} credits.
<Box mt={1} />
<Box textAlign="center">
Current patient targeted for scanning: {data.patient_name} |
</Box>
<Box my={1} mx={4} />
<Button
icon="procedures"
disabled={!data.active_status_1}
tooltip={multiline`
Reads back exact values of your general health scan.
`}
onClick={() => act('beginScan_1')}
content="General Health Scan" />
<Button
icon="heartbeat"
disabled={!data.active_status_2}
tooltip={multiline`
Provides information based on various non-obvious symptoms,
like blood levels or disease status.
`}
onClick={() => act('beginScan_2')}
content="Symptom Based Checkup" />
<Button
tooltip={multiline`
Resets the current scanning target, cancelling current scans.
`}
icon="sync"
color="average"
onClick={() => act('clearTarget')}
content="Reset Scanner" />
</Box>
<Box my={1} textAlign="center">
<Button
icon="radiation-alt"
disabled={!data.active_status_3}
tooltip={multiline`
Provides information about brain trauma and radiation.
`}
onClick={() => act('beginScan_3')}
content="Neurological/Radiological Scan" />
<Button
icon="mortar-pestle"
disabled={!data.active_status_4}
tooltip={multiline`
Provides a list of consumed chemicals, as well as potential
side effects.
`}
onClick={() => act('beginScan_4')}
content="Chemical Analysis and Psychoactive Scan" />
</Box>
</Section>
<Tabs>
<Tabs.Tab
key="tab_1"
color="normal"
label="General Health Scan">
{() => (
<Box>
{data.active_status_1 === 0 && (
<Section title="Patient Health"
textAlign="center">
<LabeledList>
<LabeledList.Item
label="Total Health">
<ProgressBar
value={(data.patient_health)/100}>
<AnimatedNumber value={data.patient_health} />%
</ProgressBar>
</LabeledList.Item>
<LabeledList.Divider size={2} />
<LabeledList.Item
label="Brute Damage">
<ProgressBar
value={data.brute_health/100}
color="bad">
<AnimatedNumber value={data.brute_health} />
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item
label="Burn Damage">
<ProgressBar
value={data.burn_health/100}
color="bad">
<AnimatedNumber value={data.burn_health} />
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item
label="Oxygen Damage">
<ProgressBar
value={data.suffocation_health/100}
color="bad">
<AnimatedNumber value={data.suffocation_health} />
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item
label="Toxin Damage">
<ProgressBar
value={data.toxin_health/100}
color="bad">
<AnimatedNumber value={data.toxin_health} />
</ProgressBar>
</LabeledList.Item>
</LabeledList>
</Section>
)}
</Box>
)}
</Tabs.Tab>
<Tabs.Tab
key="tab_2"
color="normal"
label="Symptom Based Checkup">
{() => (
<Box>
{data.active_status_2 === 0 && (
<Section title="Symptom Based Checkup"
textAlign="center">
<LabeledList>
<LabeledList.Item
label="Patient Status"
color="good">
{data.patient_status}
</LabeledList.Item>
<LabeledList.Divider size={1} />
<LabeledList.Item
label="Disease Status">
{data.patient_illness}
</LabeledList.Item>
<LabeledList.Item
label="Disease information">
{data.illness_info}
</LabeledList.Item>
<LabeledList.Divider size={1} />
<LabeledList.Item
label="Blood Levels">
{data.bleed_status}
<LabeledList.Divider size={1} />
<ProgressBar
value={data.blood_levels/100}
color="bad">
<AnimatedNumber value={data.blood_levels} />
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item
label="Blood Information">
{data.blood_status}
</LabeledList.Item>
</LabeledList>
</Section>
)}
</Box>
)}
</Tabs.Tab>
<Tabs.Tab
key="tab_3"
color="normal"
label="Neurological/Radiological Scan">
{() => (
<Box>
{data.active_status_3 === 0 && (
<Section title="Patient Neurological and Radiological Health "
textAlign="center">
<LabeledList.Item
label="Cellular Damage">
<ProgressBar
value={data.clone_health/100}
color="good">
<AnimatedNumber value={data.clone_health} />
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item
label="Brain Damage">
<ProgressBar
value={data.brain_damage/100}
color="good">
<AnimatedNumber value={data.brain_damage} />
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item
label="Brain Status"
color="health-0">
{data.brain_health}
</LabeledList.Item>
<LabeledList.Item
label="Radiation Status">
{data.rad_status}
</LabeledList.Item>
<LabeledList.Item
label="Irradiation Percentage">
{data.rad_value}%
</LabeledList.Item>
<LabeledList.Item
label="Brain Trauma Status">
{data.trauma_status}
</LabeledList.Item>
</Section>
)}
</Box>
)}
</Tabs.Tab>
<Tabs.Tab
key="tab_4"
color="normal"
label="Chemical Analysis and Psychoactive Scan">
{() => (
<Box>
{data.active_status_4 === 0 && (
<Section title="Chemical and Psychoactive Analysis"
textAlign="center">
<LabeledList.Item label="Chemical Contents">
{data.are_chems_present ? (
data.chemical_list.length ? (
data.chemical_list.map(specificChem => (
<Box
key={specificChem.id}
color="good" >
{specificChem.volume} units of {specificChem.name}
</Box>
))
) : (
<Box>
No reagents detected.
</Box>
)
) : (
<Box color="average">
No reagents detected.
</Box>
)}
</LabeledList.Item>
<LabeledList.Item
label="Overdose Status"
color="bad">
{data.are_overdoses_present ? (
data.overdose_status.length ? (
data.overdose_status.map(specificOD => (
<Box key={specificOD.id}>
Overdosing on {specificOD.name}
</Box>
))
) : (
<Box>
No reagents detected.
</Box>
)
) : (
<Box color="good">
Patient is not overdosing.
</Box>
)}
</LabeledList.Item>
<LabeledList.Item
label="Addiction Status"
color="bad">
{data.are_addictions_present ? (
data.addiction_status.length ? (
data.addiction_status.map(specificAddict => (
<Box key={specificAddict.id}>
Addicted to {specificAddict.name}
</Box>
))
) : (
<Box>
Patient has no addictions.
</Box>
)
) : (
<Box color="good">
Patient has no addictions detected.
</Box>
)}
</LabeledList.Item>
<LabeledList.Item
label="Psychoactive Status">
{data.hallucinating_status}
</LabeledList.Item>
</Section>
)}
</Box>
)}
</Tabs.Tab>
</Tabs>
<Flex mb={1}>
<Flex.Item mr={1}>
<Section minHeight="100%">
<MedicalKioskScanButton
index={1}
icon="procedures"
name="General Health Scan"
description={multiline`
Reads back exact values of your general health scan.
`} />
<MedicalKioskScanButton
index={2}
icon="heartbeat"
name="Symptom Based Checkup"
description={multiline`
Provides information based on various non-obvious symptoms,
like blood levels or disease status.
`} />
<MedicalKioskScanButton
index={3}
icon="radiation-alt"
name="Neurological/Radiological Scan"
description={multiline`
Provides information about brain trauma and radiation.
`} />
<MedicalKioskScanButton
index={4}
icon="mortar-pestle"
name="Chemical and Psychoactive Scan"
description={multiline`
Provides a list of consumed chemicals, as well as potential
side effects.
`} />
</Section>
</Flex.Item>
<Flex.Item grow={1} basis={0}>
<MedicalKioskInstructions />
</Flex.Item>
</Flex>
{!!active_status_1 && scanIndex === 1 && (
<MedicalKioskScanResults1 />
)}
{!!active_status_2 && scanIndex === 2 && (
<MedicalKioskScanResults2 />
)}
{!!active_status_3 && scanIndex === 3 && (
<MedicalKioskScanResults3 />
)}
{!!active_status_4 && scanIndex === 4 && (
<MedicalKioskScanResults4 />
)}
</Window.Content>
</Window>
);
};
const MedicalKioskScanButton = (props, context) => {
const {
index,
name,
description,
icon,
} = props;
const { act, data } = useBackend(context);
const [scanIndex, setScanIndex] = useSharedState(context, 'scanIndex');
const paid = data[`active_status_${index}`];
return (
<Flex spacing={1} align="baseline">
<Flex.Item grow={1}>
<Button
fluid
icon={icon}
selected={paid && scanIndex === index}
tooltip={description}
tooltipPosition="right"
content={name}
onClick={() => {
act(`beginScan_${index}`);
setScanIndex(index);
}} />
</Flex.Item>
<Flex.Item width="16px" textAlign="center">
<Icon
name={paid ? 'check' : 'dollar-sign'}
color={paid ? 'green' : 'grey'} />
</Flex.Item>
</Flex>
);
};
const MedicalKioskInstructions = (props, context) => {
const { act, data } = useBackend(context);
const {
kiosk_cost,
patient_name,
} = data;
return (
<Section minHeight="100%">
<Box italic>
Greetings Valued Employee! Please select a desired automatic
health check procedure. Diagnosis costs <b>{kiosk_cost} credits.</b>
</Box>
<Box mt={1}>
<Box inline color="label" mr={1}>
Patient:
</Box>
{patient_name}
</Box>
<Button
mt={1}
tooltip={multiline`
Resets the current scanning target, cancelling current scans.
`}
icon="sync"
color="average"
onClick={() => act('clearTarget')}
content="Reset Scanner" />
</Section>
);
};
const MedicalKioskScanResults1 = (props, context) => {
const { data } = useBackend(context);
const {
patient_health,
brute_health,
burn_health,
suffocation_health,
toxin_health,
} = data;
return (
<Section title="Patient Health">
<LabeledList>
<LabeledList.Item
label="Total Health">
<ProgressBar
value={patient_health / 100}>
<AnimatedNumber value={patient_health} />%
</ProgressBar>
</LabeledList.Item>
<LabeledList.Divider />
<LabeledList.Item
label="Brute Damage">
<ProgressBar
value={brute_health / 100}
color="bad">
<AnimatedNumber value={brute_health} />
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item
label="Burn Damage">
<ProgressBar
value={burn_health / 100}
color="bad">
<AnimatedNumber value={burn_health} />
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item
label="Oxygen Damage">
<ProgressBar
value={suffocation_health / 100}
color="bad">
<AnimatedNumber value={suffocation_health} />
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item
label="Toxin Damage">
<ProgressBar
value={toxin_health / 100}
color="bad">
<AnimatedNumber value={toxin_health} />
</ProgressBar>
</LabeledList.Item>
</LabeledList>
</Section>
);
};
const MedicalKioskScanResults2 = (props, context) => {
const { data } = useBackend(context);
const {
patient_status,
patient_illness,
illness_info,
bleed_status,
blood_levels,
blood_status,
} = data;
return (
<Section title="Symptom Based Checkup">
<LabeledList>
<LabeledList.Item
label="Patient Status"
color="good">
{patient_status}
</LabeledList.Item>
<LabeledList.Divider />
<LabeledList.Item
label="Disease Status">
{patient_illness}
</LabeledList.Item>
<LabeledList.Item
label="Disease information">
{illness_info}
</LabeledList.Item>
<LabeledList.Divider />
<LabeledList.Item
label="Blood Levels">
<ProgressBar
value={blood_levels / 100}
color="bad">
<AnimatedNumber value={blood_levels} />
</ProgressBar>
<Box mt={1} color="label">
{bleed_status}
</Box>
</LabeledList.Item>
<LabeledList.Item
label="Blood Information">
{blood_status}
</LabeledList.Item>
</LabeledList>
</Section>
);
};
const MedicalKioskScanResults3 = (props, context) => {
const { data } = useBackend(context);
const {
clone_health,
brain_damage,
brain_health,
rad_status,
rad_value,
trauma_status,
} = data;
return (
<Section title="Patient Neurological and Radiological Health">
<LabeledList>
<LabeledList.Item
label="Cellular Damage">
<ProgressBar
value={clone_health / 100}
color="good">
<AnimatedNumber value={clone_health} />
</ProgressBar>
</LabeledList.Item>
<LabeledList.Divider />
<LabeledList.Item
label="Brain Damage">
<ProgressBar
value={brain_damage / 100}
color="good">
<AnimatedNumber value={brain_damage} />
</ProgressBar>
</LabeledList.Item>
<LabeledList.Item
label="Brain Status"
color="health-0">
{brain_health}
</LabeledList.Item>
<LabeledList.Item
label="Brain Trauma Status">
{trauma_status}
</LabeledList.Item>
<LabeledList.Divider />
<LabeledList.Item
label="Radiation Status">
{rad_status}
</LabeledList.Item>
<LabeledList.Item
label="Irradiation Percentage">
{rad_value}%
</LabeledList.Item>
</LabeledList>
</Section>
);
};
const MedicalKioskScanResults4 = (props, context) => {
const { data } = useBackend(context);
const {
chemical_list = [],
overdose_list = [],
addict_list = [],
hallucinating_status,
} = data;
return (
<Section title="Chemical and Psychoactive Analysis">
<LabeledList>
<LabeledList.Item
label="Chemical Contents">
{chemical_list.length === 0 && (
<Box color="average">
No reagents detected.
</Box>
)}
{chemical_list.map(chem => (
<Box
key={chem.id}
color="good">
{chem.volume} units of {chem.name}
</Box>
))}
</LabeledList.Item>
<LabeledList.Item
label="Overdose Status"
color="bad">
{overdose_list.length === 0 && (
<Box color="good">
Patient is not overdosing.
</Box>
)}
{overdose_list.map(chem => (
<Box key={chem.id}>
Overdosing on {chem.name}
</Box>
))}
</LabeledList.Item>
<LabeledList.Item
label="Addiction Status"
color="bad">
{addict_list.length === 0 && (
<Box color="good">
Patient has no addictions.
</Box>
)}
{addict_list.map(chem => (
<Box key={chem.id}>
Addicted to {chem.name}
</Box>
))}
</LabeledList.Item>
<LabeledList.Item
label="Psychoactive Status">
{hallucinating_status}
</LabeledList.Item>
</LabeledList>
</Section>
);
};
@@ -1,7 +1,7 @@
import { map } from 'common/collections';
import { Fragment } from 'inferno';
import { useBackend } from '../backend';
import { Button, LabeledList, NoticeBox, Section, Tabs } from '../components';
import { useBackend, useSharedState } from '../backend';
import { Button, Flex, LabeledList, NoticeBox, Section, Tabs } from '../components';
import { Window } from '../layouts';
export const NaniteProgramHub = (props, context) => {
@@ -13,6 +13,11 @@ export const NaniteProgramHub = (props, context) => {
has_program,
programs = {},
} = data;
const [
selectedCategory,
setSelectedCategory,
] = useSharedState(context, 'category', Object.keys(programs)[0]);
const programsInCategory = programs[selectedCategory] || [];
return (
<Window resizable>
<Window.Content scrollable>
@@ -66,56 +71,65 @@ export const NaniteProgramHub = (props, context) => {
</Fragment>
)}>
{programs !== null ? (
<Tabs vertical>
{map((cat_contents, category) => {
const progs = cat_contents || [];
// Backend was sending stupid data that would have been
// annoying to fix
const tabLabel = category.substring(0, category.length - 8);
return (
<Tabs.Tab
key={category}
label={tabLabel}>
{detail_view ? (
progs.map(program => (
<Section
key={program.id}
title={program.name}
level={2}
buttons={(
<Button
icon="download"
content="Download"
disabled={!has_disk}
onClick={() => act('download', {
program_id: program.id,
})} />
)}>
{program.desc}
</Section>
))
) : (
<LabeledList>
{progs.map(program => (
<LabeledList.Item
key={program.id}
label={program.name}
buttons={(
<Button
icon="download"
content="Download"
disabled={!has_disk}
onClick={() => act('download', {
program_id: program.id,
})} />
)} />
))}
</LabeledList>
)}
</Tabs.Tab>
);
})(programs)}
</Tabs>
<Flex>
<Flex.Item minWidth="110px">
<Tabs vertical>
{map((cat_contents, category) => {
const progs = cat_contents || [];
// Backend was sending stupid data that would have been
// annoying to fix
const tabLabel = category
.substring(0, category.length - 8);
return (
<Tabs.Tab
key={category}
selected={category === selectedCategory}
onClick={() => setSelectedCategory(category)}>
{tabLabel}
</Tabs.Tab>
);
})(programs)}
</Tabs>
</Flex.Item>
<Flex.Item grow={1} basis={0}>
{detail_view ? (
programsInCategory.map(program => (
<Section
key={program.id}
title={program.name}
level={2}
buttons={(
<Button
icon="download"
content="Download"
disabled={!has_disk}
onClick={() => act('download', {
program_id: program.id,
})} />
)}>
{program.desc}
</Section>
))
) : (
<LabeledList>
{programsInCategory.map(program => (
<LabeledList.Item
key={program.id}
label={program.name}
buttons={(
<Button
icon="download"
content="Download"
disabled={!has_disk}
onClick={() => act('download', {
program_id: program.id,
})} />
)} />
))}
</LabeledList>
)}
</Flex.Item>
</Flex>
) : (
<NoticeBox>
No nanite programs are currently researched.
+52 -33
View File
@@ -1,9 +1,8 @@
import { Fragment } from 'inferno';
import { useBackend } from '../backend';
import { Button, NoticeBox, Section, Tabs, Input } from '../components';
import { AccessList } from './common/AccessList';
import { map } from 'common/collections';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Flex, Input, NoticeBox, Section, Tabs } from '../components';
import { NtosWindow } from '../layouts';
import { AccessList } from './common/AccessList';
export const NtosCard = (props, context) => {
return (
@@ -17,7 +16,7 @@ export const NtosCard = (props, context) => {
export const NtosCardContent = (props, context) => {
const { act, data } = useBackend(context);
const [tab, setTab] = useLocalState(context, 'tab', 1);
const {
authenticated,
regions = [],
@@ -30,7 +29,10 @@ export const NtosCardContent = (props, context) => {
have_id_slot,
id_name,
} = data;
const [
selectedDepartment,
setSelectedDepartment,
] = useLocalState(context, 'department', Object.keys(jobs)[0]);
if (!have_id_slot) {
return (
<NoticeBox>
@@ -38,7 +40,7 @@ export const NtosCardContent = (props, context) => {
</NoticeBox>
);
}
const departmentJobs = jobs[selectedDepartment] || [];
return (
<Fragment>
<Section
@@ -75,8 +77,20 @@ export const NtosCardContent = (props, context) => {
onClick={() => act('PRG_eject')} />
</Section>
{(!!has_id && !!authenticated) && (
<Tabs>
<Tabs.Tab label="Access">
<Box>
<Tabs>
<Tabs.Tab
selected={tab === 1}
onClick={() => setTab(1)}>
Access
</Tabs.Tab>
<Tabs.Tab
selected={tab === 2}
onClick={() => setTab(2)}>
Jobs
</Tabs.Tab>
</Tabs>
{tab === 1 && (
<AccessList
accesses={regions}
selectedList={access_on_card}
@@ -91,8 +105,8 @@ export const NtosCardContent = (props, context) => {
denyDep={dep => act('PRG_denyregion', {
region: dep,
})} />
</Tabs.Tab>
<Tabs.Tab label="Jobs">
)}
{tab === 2 && (
<Section
title={id_rank}
buttons={(
@@ -109,29 +123,34 @@ export const NtosCardContent = (props, context) => {
assign_target: 'Custom',
custom_name: value,
})} />
<Tabs vertical>
{map((jobs_param, department) => {
const jobs = jobs_param || [];
return (
<Tabs.Tab
key={department}
label={department}>
{jobs.map(job => (
<Button
fluid
key={job.job}
content={job.display_name}
onClick={() => act('PRG_assign', {
assign_target: job.job,
})} />
))}
</Tabs.Tab>
);
})(jobs)}
</Tabs>
<Flex>
<Flex.Item>
<Tabs vertical>
{Object.keys(jobs).map(department => (
<Tabs.Tab
key={department}
selected={department === selectedDepartment}
onClick={() => setSelectedDepartment(department)}>
{department}
</Tabs.Tab>
))}
</Tabs>
</Flex.Item>
<Flex.Item grow={1}>
{departmentJobs.map(job => (
<Button
fluid
key={job.job}
content={job.display_name}
onClick={() => act('PRG_assign', {
assign_target: job.job,
})} />
))}
</Flex.Item>
</Flex>
</Section>
</Tabs.Tab>
</Tabs>
)}
</Box>
)}
</Fragment>
);
+145 -130
View File
@@ -1,148 +1,163 @@
import { Fragment } from 'inferno';
import { useBackend } from '../backend';
import { useBackend, useSharedState } from '../backend';
import { AnimatedNumber, Button, LabeledList, NoticeBox, ProgressBar, Section, Tabs } from '../components';
import { Window } from '../layouts';
const damageTypes = [
{
label: 'Brute',
type: 'bruteLoss',
},
{
label: 'Burn',
type: 'fireLoss',
},
{
label: 'Toxin',
type: 'toxLoss',
},
{
label: 'Respiratory',
type: 'oxyLoss',
},
];
export const OperatingComputer = (props, context) => {
const { act, data } = useBackend(context);
const damageTypes = [
{
label: 'Brute',
type: 'bruteLoss',
},
{
label: 'Burn',
type: 'fireLoss',
},
{
label: 'Toxin',
type: 'toxLoss',
},
{
label: 'Respiratory',
type: 'oxyLoss',
},
];
const {
table,
surgeries = [],
procedures = [],
patient = {},
} = data;
const [tab, setTab] = useSharedState(context, 'tab', 1);
return (
<Window resizable>
<Window.Content scrollable>
<Tabs>
<Tabs.Tab
key="state"
label="Patient State">
{!table && (
<NoticeBox>
No Table Detected
</NoticeBox>
)}
<Section>
<Section
title="Patient State"
level={2}>
{patient ? (
<LabeledList>
<LabeledList.Item
label="State"
color={patient.statstate}>
{patient.stat}
</LabeledList.Item>
<LabeledList.Item label="Blood Type">
{patient.blood_type}
</LabeledList.Item>
<LabeledList.Item label="Health">
<ProgressBar
value={patient.health}
minValue={patient.minHealth}
maxValue={patient.maxHealth}
color={patient.health >= 0 ? 'good' : 'average'}>
<AnimatedNumber value={patient.health} />
</ProgressBar>
</LabeledList.Item>
{damageTypes.map(type => (
<LabeledList.Item key={type.type} label={type.label}>
<ProgressBar
value={patient[type.type] / patient.maxHealth}
color="bad">
<AnimatedNumber value={patient[type.type]} />
</ProgressBar>
</LabeledList.Item>
))}
</LabeledList>
) : (
'No Patient Detected'
)}
</Section>
<Section
title="Initiated Procedures"
level={2}>
{procedures.length ? (
procedures.map(procedure => (
<Section
key={procedure.name}
title={procedure.name}
level={3}>
<LabeledList>
<LabeledList.Item label="Next Step">
{procedure.next_step}
{procedure.chems_needed && (
<Fragment>
<b>
Required Chemicals:
</b>
<br />
{procedure.chems_needed}
</Fragment>
)}
</LabeledList.Item>
{!!data.alternative_step && (
<LabeledList.Item label="Alternative Step">
{procedure.alternative_step}
{procedure.alt_chems_needed && (
<Fragment>
<b>
Required Chemicals:
</b>
<br />
{procedure.alt_chems_needed}
</Fragment>
)}
</LabeledList.Item>
)}
</LabeledList>
</Section>
))
) : (
'No Active Procedures'
)}
</Section>
</Section>
selected={tab === 1}
onClick={() => setTab(1)}>
Patient State
</Tabs.Tab>
<Tabs.Tab
key="procedures"
label="Surgery Procedures">
<Section title="Advanced Surgery Procedures">
<Button
icon="download"
content="Sync Research Database"
onClick={() => act('sync')} />
{surgeries.map(surgery => (
<Section
title={surgery.name}
key={surgery.name}
level={2}>
{surgery.desc}
</Section>
))}
</Section>
selected={tab === 2}
onClick={() => setTab(2)}>
Surgery Procedures
</Tabs.Tab>
</Tabs>
{tab === 1 && (
<PatientStateView />
)}
{tab === 2 && (
<SurgeryProceduresView />
)}
</Window.Content>
</Window>
);
};
const PatientStateView = (props, context) => {
const { act, data } = useBackend(context);
const {
table,
procedures = [],
patient = {},
} = data;
if (!table) {
return (
<NoticeBox>
No Table Detected
</NoticeBox>
);
}
return (
<Fragment>
<Section title="Patient State">
{patient && (
<LabeledList>
<LabeledList.Item
label="State"
color={patient.statstate}>
{patient.stat}
</LabeledList.Item>
<LabeledList.Item label="Blood Type">
{patient.blood_type}
</LabeledList.Item>
<LabeledList.Item label="Health">
<ProgressBar
value={patient.health}
minValue={patient.minHealth}
maxValue={patient.maxHealth}
color={patient.health >= 0 ? 'good' : 'average'}>
<AnimatedNumber value={patient.health} />
</ProgressBar>
</LabeledList.Item>
{damageTypes.map(type => (
<LabeledList.Item key={type.type} label={type.label}>
<ProgressBar
value={patient[type.type] / patient.maxHealth}
color="bad">
<AnimatedNumber value={patient[type.type]} />
</ProgressBar>
</LabeledList.Item>
))}
</LabeledList>
) || (
'No Patient Detected'
)}
</Section>
{procedures.length === 0 && (
<Section>
No Active Procedures
</Section>
)}
{procedures.map(procedure => (
<Section
key={procedure.name}
title={procedure.name}>
<LabeledList>
<LabeledList.Item label="Next Step">
{procedure.next_step}
{procedure.chems_needed && (
<Fragment>
<b>Required Chemicals:</b>
<br />
{procedure.chems_needed}
</Fragment>
)}
</LabeledList.Item>
{!!data.alternative_step && (
<LabeledList.Item label="Alternative Step">
{procedure.alternative_step}
{procedure.alt_chems_needed && (
<Fragment>
<b>Required Chemicals:</b>
<br />
{procedure.alt_chems_needed}
</Fragment>
)}
</LabeledList.Item>
)}
</LabeledList>
</Section>
))}
</Fragment>
);
};
const SurgeryProceduresView = (props, context) => {
const { act, data } = useBackend(context);
const {
surgeries = [],
} = data;
return (
<Section title="Advanced Surgery Procedures">
<Button
icon="download"
content="Sync Research Database"
onClick={() => act('sync')} />
{surgeries.map(surgery => (
<Section
title={surgery.name}
key={surgery.name}
level={2}>
{surgery.desc}
</Section>
))}
</Section>
);
};
@@ -1,9 +1,100 @@
import { map } from 'common/collections';
import { Fragment } from 'inferno';
import { useBackend } from '../backend';
import { Button, Dimmer, Icon, LabeledList, Section, Tabs } from '../components';
import { useBackend, useLocalState } from '../backend';
import { Button, Dimmer, Flex, Icon, LabeledList, Section, Tabs } from '../components';
import { Window } from '../layouts';
export const PersonalCrafting = (props, context) => {
const { act, data } = useBackend(context);
const {
busy,
display_craftable_only,
display_compact,
} = data;
const crafting_recipes = data.crafting_recipes || {};
// Sort everything into flat categories
const categories = [];
const recipes = [];
for (let category of Object.keys(crafting_recipes)) {
const subcategories = crafting_recipes[category];
if ('has_subcats' in subcategories) {
for (let subcategory of Object.keys(subcategories)) {
if (subcategory === 'has_subcats') {
continue;
}
// Push category
categories.push(subcategory);
// Push recipes
const _recipes = subcategories[subcategory];
for (let recipe of _recipes) {
recipes.push({
...recipe,
category: subcategory,
});
}
}
continue;
}
// Push category
categories.push(category);
// Push recipes
const _recipes = crafting_recipes[category];
for (let recipe of _recipes) {
recipes.push({
...recipe,
category,
});
}
}
// Sort out the tab state
const [tab, setTab] = useLocalState(context, 'tab', categories[0]);
const shownRecipes = recipes
.filter(recipe => recipe.category === tab);
return (
<Window resizable>
<Window.Content scrollable>
{!!busy && (
<Dimmer fontSize="32px">
<Icon name="cog" spin={1} />
{' Crafting...'}
</Dimmer>
)}
<Section
title="Personal Crafting"
buttons={(
<Fragment>
<Button.Checkbox
content="Compact"
checked={display_compact}
onClick={() => act('toggle_compact')} />
<Button.Checkbox
content="Craftable Only"
checked={display_craftable_only}
onClick={() => act('toggle_recipes')} />
</Fragment>
)}>
<Flex>
<Flex.Item>
<Tabs vertical>
{categories.map(category => (
<Tabs.Tab
key={category}
selected={category === tab}
onClick={() => setTab(category)}>
{category}
</Tabs.Tab>
))}
</Tabs>
</Flex.Item>
<Flex.Item grow={1} basis={0}>
<CraftingList craftables={shownRecipes} />
</Flex.Item>
</Flex>
</Section>
</Window.Content>
</Window>
);
};
const CraftingList = (props, context) => {
const {
craftables = [],
@@ -78,94 +169,3 @@ const CraftingList = (props, context) => {
);
});
};
export const PersonalCrafting = (props, context) => {
const { act, data } = useBackend(context);
const {
busy,
display_craftable_only,
display_compact,
} = data;
const craftingRecipes = map((subcategory, category) => {
const hasSubcats = ('has_subcats' in subcategory);
const firstSubcatName = Object.keys(subcategory)
.find(name => name !== 'has_subcats');
return {
category,
subcategory,
hasSubcats,
firstSubcatName,
};
})(data.crafting_recipes || {});
// Shows an overlay when crafting an item
const busyBox = !!busy && (
<Dimmer fontSize="32px">
<Icon name="cog" spin={1} />
{' Crafting...'}
</Dimmer>
);
return (
<Window resizable>
<Window.Content scrollable>
{busyBox}
<Section
title="Personal Crafting"
buttons={(
<Fragment>
<Button
icon={display_compact ? "check-square-o" : "square-o"}
content="Compact"
selected={display_compact}
onClick={() => act('toggle_compact')} />
<Button
icon={display_craftable_only ? "check-square-o" : "square-o"}
content="Craftable Only"
selected={display_craftable_only}
onClick={() => act('toggle_recipes')} />
</Fragment>
)}>
<Tabs>
{craftingRecipes.map(recipe => (
<Tabs.Tab
key={recipe.category}
label={recipe.category}
onClick={() => act('set_category', {
category: recipe.category,
// Backend expects "0" or "" to indicate no subcategory
subcategory: recipe.firstSubcatName,
})}>
{() => !recipe.hasSubcats && (
<CraftingList
craftables={recipe.subcategory} />
) || (
<Tabs vertical>
{map((value, name) => {
if (name === "has_subcats") {
return;
}
return (
<Tabs.Tab
label={name}
onClick={() => act('set_category', {
subcategory: name,
})}>
{() => (
<CraftingList
craftables={value} />
)}
</Tabs.Tab>
);
})(recipe.subcategory)}
</Tabs>
)}
</Tabs.Tab>
))}
</Tabs>
</Section>
</Window.Content>
</Window>
);
};
@@ -1,5 +1,5 @@
import { classes } from 'common/react';
import { useBackend } from '../backend';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, ColorBox, Flex, LabeledList, Section, Tabs } from '../components';
import { Window } from '../layouts';
@@ -64,6 +64,13 @@ export const RapidPipeDispenser = (props, context) => {
mode,
} = data;
const previews = data.preview_rows.flatMap(row => row.previews);
const [
categoryName,
setCategoryName,
] = useLocalState(context, 'categoryName');
const shownCategory = categories
.find(category => category.cat_name === categoryName)
|| categories[0];
return (
<Window resizable>
<Window.Content scrollable>
@@ -161,28 +168,30 @@ export const RapidPipeDispenser = (props, context) => {
<Flex.Item m={0.5} grow={1}>
<Section>
<Tabs>
{categories.map(category => (
{categories.map((category, i) => (
<Tabs.Tab
fluid
key={category.cat_name}
icon={ICON_BY_CATEGORY_NAME[category.cat_name]}
label={category.cat_name}>
{() => category.recipes.map(recipe => (
<Button.Checkbox
key={recipe.pipe_index}
fluid
ellipsis
checked={recipe.selected}
content={recipe.pipe_name}
title={recipe.pipe_name}
onClick={() => act('pipe_type', {
pipe_type: recipe.pipe_index,
category: category.cat_name,
})} />
))}
selected={category.cat_name === shownCategory.cat_name}
onClick={() => setCategoryName(category.cat_name)}>
{category.cat_name}
</Tabs.Tab>
))}
</Tabs>
{shownCategory?.recipes.map(recipe => (
<Button.Checkbox
key={recipe.pipe_index}
fluid
ellipsis
checked={recipe.selected}
content={recipe.pipe_name}
title={recipe.pipe_name}
onClick={() => act('pipe_type', {
pipe_type: recipe.pipe_index,
category: shownCategory.cat_name,
})} />
))}
</Section>
</Flex.Item>
</Flex>
@@ -1,10 +1,11 @@
import { Fragment } from 'inferno';
import { useBackend } from '../backend';
import { Box, Button, NoticeBox, Section, Tabs, LabeledList } from '../components';
import { useBackend, useSharedState } from '../backend';
import { Box, Button, LabeledList, NoticeBox, Section, Tabs } from '../components';
import { Window } from '../layouts';
export const RoboticsControlConsole = (props, context) => {
const { act, data } = useBackend(context);
const [tab, setTab] = useSharedState(context, 'tab', 1);
const {
can_hack,
cyborgs = [],
@@ -15,24 +16,26 @@ export const RoboticsControlConsole = (props, context) => {
<Window.Content scrollable>
<Tabs>
<Tabs.Tab
key="cyborgs"
label={`Cyborgs (${cyborgs.length})`}
icon="list"
lineHeight="23px">
{() => (
<Cyborgs cyborgs={cyborgs} can_hack={can_hack} />
)}
lineHeight="23px"
selected={tab === 1}
onClick={() => setTab(1)}>
Cyborgs ({cyborgs.length})
</Tabs.Tab>
<Tabs.Tab
key="drones"
label={`Drones (${drones.length})`}
icon="list"
lineHeight="23px">
{() => (
<Drones drones={drones} />
)}
lineHeight="23px"
selected={tab === 2}
onClick={() => setTab(2)}>
Drones ({drones.length})
</Tabs.Tab>
</Tabs>
{tab === 1 && (
<Cyborgs cyborgs={cyborgs} can_hack={can_hack} />
)}
{tab === 2 && (
<Drones drones={drones} />
)}
</Window.Content>
</Window>
);
@@ -43,11 +46,9 @@ const Cyborgs = (props, context) => {
const { act, data } = useBackend(context);
if (!cyborgs.length) {
return (
<Section>
<NoticeBox textAlign="center">
No cyborg units detected within access parameters
</NoticeBox>
</Section>
<NoticeBox>
No cyborg units detected within access parameters
</NoticeBox>
);
}
return cyborgs.map(cyborg => {
@@ -127,11 +128,9 @@ const Drones = (props, context) => {
if (!drones.length) {
return (
<Section>
<NoticeBox textAlign="center">
No drone units detected within access parameters
</NoticeBox>
</Section>
<NoticeBox>
No drone units detected within access parameters
</NoticeBox>
);
}
@@ -1,14 +1,48 @@
import { map } from 'common/collections';
import { Fragment } from 'inferno';
import { useBackend } from '../backend';
import { Button, LabeledList, Section, Table, Tabs } from '../components';
import { useBackend, useLocalState } from '../backend';
import { Button, Flex, LabeledList, Section, Table, Tabs } from '../components';
import { Window } from '../layouts';
export const ShuttleManipulator = (props, context) => {
const [tab, setTab] = useLocalState(context, 'tab', 1);
return (
<Window resizable>
<Window.Content scrollable>
<Tabs>
<Tabs.Tab
selected={tab === 1}
onClick={() => setTab(1)}>
Status
</Tabs.Tab>
<Tabs.Tab
selected={tab === 2}
onClick={() => setTab(2)}>
Templates
</Tabs.Tab>
<Tabs.Tab
selected={tab === 3}
onClick={() => setTab(3)}>
Modification
</Tabs.Tab>
</Tabs>
{tab === 1 && (
<ShuttleManipulatorStatus />
)}
{tab === 2 && (
<ShuttleManipulatorTemplates />
)}
{tab === 3 && (
<ShuttleManipulatorModification />
)}
</Window.Content>
</Window>
);
};
export const ShuttleManipulatorStatus = (props, context) => {
const { act, data } = useBackend(context);
const shuttles = data.shuttles || [];
return (
<Section>
<Table>
@@ -65,70 +99,76 @@ export const ShuttleManipulatorStatus = (props, context) => {
export const ShuttleManipulatorTemplates = (props, context) => {
const { act, data } = useBackend(context);
const templateObject = data.templates || {};
const selected = data.selected || {};
const [
selectedTemplateId,
setSelectedTemplateId,
] = useLocalState(context, 'templateId', Object.keys(templateObject)[0]);
const actualTemplates = templateObject[selectedTemplateId]?.templates;
return (
<Section>
<Tabs vertical>
{map((template, templateId) => {
const templates = template.templates || [];
return (
<Tabs.Tab
key={templateId}
label={template.port_id}>
{templates.map(actualTemplate => {
const isSelected = (
actualTemplate.shuttle_id === selected.shuttle_id
);
// Whoever made the structure being sent is an asshole
return (
<Section
title={actualTemplate.name}
level={2}
key={actualTemplate.shuttle_id}
buttons={(
<Button
content={isSelected ? 'Selected' : 'Select'}
selected={isSelected}
onClick={() => act('select_template', {
shuttle_id: actualTemplate.shuttle_id,
})} />
)}>
{(!!actualTemplate.description
|| !!actualTemplate.admin_notes
) && (
<LabeledList>
{!!actualTemplate.description && (
<LabeledList.Item label="Description">
{actualTemplate.description}
</LabeledList.Item>
)}
{!!actualTemplate.admin_notes && (
<LabeledList.Item label="Admin Notes">
{actualTemplate.admin_notes}
</LabeledList.Item>
)}
</LabeledList>
<Flex>
<Flex.Item>
<Tabs vertical>
{map((template, templateId) => (
<Tabs.Tab
key={templateId}
selected={selectedTemplateId === templateId}
onClick={() => setSelectedTemplateId(templateId)}>
{template.port_id}
</Tabs.Tab>
))(templateObject)}
</Tabs>
</Flex.Item>
<Flex.Item grow={1} basis={0}>
{actualTemplates.map(actualTemplate => {
const isSelected = (
actualTemplate.shuttle_id === selected.shuttle_id
);
// Whoever made the structure being sent is an asshole
return (
<Section
title={actualTemplate.name}
level={2}
key={actualTemplate.shuttle_id}
buttons={(
<Button
content={isSelected ? 'Selected' : 'Select'}
selected={isSelected}
onClick={() => act('select_template', {
shuttle_id: actualTemplate.shuttle_id,
})} />
)}>
{(!!actualTemplate.description
|| !!actualTemplate.admin_notes
) && (
<LabeledList>
{!!actualTemplate.description && (
<LabeledList.Item label="Description">
{actualTemplate.description}
</LabeledList.Item>
)}
</Section>
);
})}
</Tabs.Tab>
);
})(templateObject)}
</Tabs>
{!!actualTemplate.admin_notes && (
<LabeledList.Item label="Admin Notes">
{actualTemplate.admin_notes}
</LabeledList.Item>
)}
</LabeledList>
)}
</Section>
);
})}
</Flex.Item>
</Flex>
</Section>
);
};
export const ShuttleManipulatorModification = (props, context) => {
const { act, data } = useBackend(context);
const selected = data.selected || {};
const existingShuttle = data.existing_shuttle || {};
return (
<Section>
{selected ? (
@@ -200,29 +240,3 @@ export const ShuttleManipulatorModification = (props, context) => {
</Section>
);
};
export const ShuttleManipulator = (props, context) => {
const { act, data } = useBackend(context);
const selected = data.selected || {};
const existingShuttle = data.existing_shuttle || {};
return (
<Window resizable>
<Window.Content scrollable>
<Tabs>
<Tabs.Tab
label="Status">
<ShuttleManipulatorStatus />
</Tabs.Tab>
<Tabs.Tab
label="Templates">
<ShuttleManipulatorTemplates />
</Tabs.Tab>
<Tabs.Tab
label="Modification">
<ShuttleManipulatorModification />
</Tabs.Tab>
</Tabs>
</Window.Content>
</Window>
);
};
+179 -163
View File
@@ -1,8 +1,15 @@
import { Component, Fragment } from 'inferno';
import { useBackend } from '../backend';
import { Box, Button, Dimmer, Grid, Icon, LabeledList, NoticeBox, Section, Table, Tabs } from '../components';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Flex, Grid, Icon, LabeledList, Modal, NoticeBox, Section, Table, Tabs } from '../components';
import { Window } from '../layouts';
const CONTRACT_STATUS_INACTIVE = 1;
const CONTRACT_STATUS_ACTIVE = 2;
const CONTRACT_STATUS_BOUNTY_CONSOLE_ACTIVE = 3;
const CONTRACT_STATUS_EXTRACTING = 4;
const CONTRACT_STATUS_COMPLETE = 5;
const CONTRACT_STATUS_ABORTED = 6;
export class FakeTerminal extends Component {
constructor(props) {
super(props);
@@ -123,36 +130,29 @@ export const SyndContractorContent = (props, context) => {
];
const errorPane = !!data.error && (
<Dimmer>
<Box
backgroundColor="red"
minHeight="150px">
<Table m={1}>
<Table.Row>
<Table.Cell collapsing fontSize="100px">
<Icon
name="exclamation-triangle"
mt={4}
ml={2} />
</Table.Cell>
<Table.Cell
verticalAlign="top"
textAlign="center">
<Box
m={1}
textAlign="left"
width="100%"
minHeight="110px">
{data.error}
</Box>
<Button
content="Dismiss"
onClick={() => act('PRG_clear_error')} />
</Table.Cell>
</Table.Row>
</Table>
</Box>
</Dimmer>
<Modal backgroundColor="red">
<Flex align="center">
<Flex.Item mr={2}>
<Icon
size={4}
name="exclamation-triangle" />
</Flex.Item>
<Flex.Item
mr={2}
grow={1}
textAlign="center">
<Box
width="260px"
textAlign="left"
minHeight="80px">
{data.error}
</Box>
<Button
content="Dismiss"
onClick={() => act('PRG_clear_error')} />
</Flex.Item>
</Flex>
</Modal>
);
if (!data.logged_in) {
@@ -271,144 +271,160 @@ export const StatusPane = (props, context) => {
};
export const SyndPane = (props, context) => {
const { act, data } = useBackend(context);
const contractor_hub_items = data.contractor_hub_items || [];
const contracts = data.contracts || [];
const CONTRACT_STATUS_INACTIVE = 1;
const CONTRACT_STATUS_ACTIVE = 2;
const CONTRACT_STATUS_BOUNTY_CONSOLE_ACTIVE = 3;
const CONTRACT_STATUS_EXTRACTING = 4;
const CONTRACT_STATUS_COMPLETE = 5;
const CONTRACT_STATUS_ABORTED = 6;
const [tab, setTab] = useLocalState(context, 'tab', 1);
return (
<Fragment>
<StatusPane state={props.state} />
<Tabs>
<Tabs.Tab label="Contracts">
<Section
title="Availible Contracts"
buttons={(
<Button
content="Call Extraction"
disabled={!data.ongoing_contract || data.extraction_enroute}
onClick={() => act('PRG_call_extraction')} />
)}>
{contracts.map(contract => {
if (data.ongoing_contract
&& contract.status !== CONTRACT_STATUS_ACTIVE) {
return;
}
const active = (contract.status > CONTRACT_STATUS_INACTIVE);
if (contract.status >= CONTRACT_STATUS_COMPLETE) {
return;
}
return (
<Section
key={contract.target}
title={contract.target
? `${contract.target} (${contract.target_rank})`
: "Invalid Target"}
level={active ? 1 : 2}
buttons={(
<Fragment>
<Box
inline
bold
mr={1}>
{contract.payout} (+{contract.payout_bonus}) TC
</Box>
<Button
content={active ? "Abort" : "Accept"}
disabled={contract.extraction_enroute}
color={active && "bad"}
onClick={() => act(
'PRG_contract' + (active ? '_abort' : '-accept'),
{
contract_id: contract.id,
})} />
</Fragment>
)}>
<Grid>
<Grid.Column>
{contract.message}
</Grid.Column>
<Grid.Column size={0.5}>
<Box
bold
mb={1}>
Dropoff Location:
</Box>
<Box>
{contract.dropoff}
</Box>
</Grid.Column>
</Grid>
</Section>
);
})}
</Section>
<Section
title="Dropoff Locator"
textAlign="center"
opacity={data.ongoing_contract ? 100 : 0}>
<Box bold>
{data.dropoff_direction}
</Box>
</Section>
<Tabs.Tab
selected={tab === 1}
onClick={() => setTab(1)}>
Contracts
</Tabs.Tab>
<Tabs.Tab label="Hub">
<Section>
{contractor_hub_items.map(item => {
const repInfo = item.cost ? (item.cost + ' Rep') : 'FREE';
const limited = (item.limited !== -1);
return (
<Section
key={item.name}
title={item.name + ' - ' + repInfo}
level={2}
buttons={(
<Fragment>
{limited && (
<Box
inline
bold
mr={1}>
{item.limited} remaining
</Box>
)}
<Button
content="Purchase"
disabled={data.contract_rep < item.cost
|| (limited && item.limited <= 0)}
onClick={() => act('buy_hub', {
item: item.name,
cost: item.cost,
})} />
</Fragment>
)}>
<Table>
<Table.Row>
<Table.Cell>
<Icon
fontSize="60px"
name={item.item_icon} />
</Table.Cell>
<Table.Cell
verticalAlign="top">
{item.desc}
</Table.Cell>
</Table.Row>
</Table>
</Section>
);
})}
</Section>
<Tabs.Tab
selected={tab === 2}
onClick={() => setTab(2)}>
Hub
</Tabs.Tab>
</Tabs>
{tab === 1 && (
<ContractsTab />
)}
{tab === 2 && (
<HubTab />
)}
</Fragment>
);
};
const ContractsTab = (props, context) => {
const { act, data } = useBackend(context);
const contracts = data.contracts || [];
return (
<Fragment>
<Section
title="Availible Contracts"
buttons={(
<Button
content="Call Extraction"
disabled={!data.ongoing_contract || data.extraction_enroute}
onClick={() => act('PRG_call_extraction')} />
)}>
{contracts.map(contract => {
if (data.ongoing_contract
&& contract.status !== CONTRACT_STATUS_ACTIVE) {
return;
}
const active = (contract.status > CONTRACT_STATUS_INACTIVE);
if (contract.status >= CONTRACT_STATUS_COMPLETE) {
return;
}
return (
<Section
key={contract.target}
title={contract.target
? `${contract.target} (${contract.target_rank})`
: "Invalid Target"}
level={active ? 1 : 2}
buttons={(
<Fragment>
<Box
inline
bold
mr={1}>
{contract.payout} (+{contract.payout_bonus}) TC
</Box>
<Button
content={active ? "Abort" : "Accept"}
disabled={contract.extraction_enroute}
color={active && "bad"}
onClick={() => act(
'PRG_contract' + (active ? '_abort' : '-accept'),
{
contract_id: contract.id,
})} />
</Fragment>
)}>
<Grid>
<Grid.Column>
{contract.message}
</Grid.Column>
<Grid.Column size={0.5}>
<Box
bold
mb={1}>
Dropoff Location:
</Box>
<Box>
{contract.dropoff}
</Box>
</Grid.Column>
</Grid>
</Section>
);
})}
</Section>
<Section
title="Dropoff Locator"
textAlign="center"
opacity={data.ongoing_contract ? 100 : 0}>
<Box bold>
{data.dropoff_direction}
</Box>
</Section>
</Fragment>
);
};
const HubTab = (props, context) => {
const { act, data } = useBackend(context);
const contractor_hub_items = data.contractor_hub_items || [];
return (
<Section>
{contractor_hub_items.map(item => {
const repInfo = item.cost ? (item.cost + ' Rep') : 'FREE';
const limited = (item.limited !== -1);
return (
<Section
key={item.name}
title={item.name + ' - ' + repInfo}
level={2}
buttons={(
<Fragment>
{limited && (
<Box
inline
bold
mr={1}>
{item.limited} remaining
</Box>
)}
<Button
content="Purchase"
disabled={data.contract_rep < item.cost
|| (limited && item.limited <= 0)}
onClick={() => act('buy_hub', {
item: item.name,
cost: item.cost,
})} />
</Fragment>
)}>
<Table>
<Table.Row>
<Table.Cell>
<Icon
fontSize="60px"
name={item.item_icon} />
</Table.Cell>
<Table.Cell
verticalAlign="top">
{item.desc}
</Table.Cell>
</Table.Row>
</Table>
</Section>
);
})}
</Section>
);
};
+155 -144
View File
@@ -1,171 +1,182 @@
import { createSearch, decodeHtmlEntities } from 'common/string';
import { Fragment } from 'inferno';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Input, Section, Table, Tabs } from '../components';
import { Box, Button, Flex, Input, Section, Table, Tabs } from '../components';
import { formatMoney } from '../format';
import { Window } from '../layouts';
const MAX_SEARCH_RESULTS = 25;
export const Uplink = (props, context) => {
const { act, data } = useBackend(context);
const {
compact_mode,
lockable,
telecrystals,
categories = [],
} = data;
const [
hoveredItem,
setHoveredItem,
] = useLocalState(context, 'hoveredItem', {});
const [
searchText,
setSearchText,
] = useLocalState(context, 'searchText', '');
const testSearch = createSearch(searchText, item => {
return item.name + item.desc;
});
const { data } = useBackend(context);
const { telecrystals } = data;
return (
<Window
theme="syndicate"
resizable>
<Window.Content scrollable>
<Section
title={(
<Box
inline
color={telecrystals > 0 ? 'good' : 'bad'}>
{telecrystals} TC
</Box>
)}
buttons={(
<Fragment>
Search
<Input
value={searchText}
onInput={(e, value) => setSearchText(value)}
ml={1}
mr={1} />
<Button
icon={compact_mode ? 'list' : 'info'}
content={compact_mode ? 'Compact' : 'Detailed'}
onClick={() => act('compact_toggle')} />
{!!lockable && (
<Button
icon="lock"
content="Lock"
onClick={() => act('lock')} />
)}
</Fragment>
)}>
{searchText.length > 0 ? (
<ItemList
compact
items={categories
.flatMap(category => category.items || [])
.filter(testSearch)
.filter((item, i) => i < MAX_SEARCH_RESULTS)}
hoveredItem={hoveredItem}
onBuyMouseOver={item => setHoveredItem(item)}
onBuyMouseOut={item => setHoveredItem({})}
onBuy={item => act('buy', {
item: item.name,
})} />
) : (
<Tabs vertical>
{categories.map(category => {
const { name, items } = category;
if (items === null) {
return;
}
return (
<Tabs.Tab
key={name}
label={`${name} (${items.length})`}>
{() => (
<ItemList
compact={compact_mode}
items={items}
hoveredItem={hoveredItem}
telecrystals={telecrystals}
onBuyMouseOver={item => setHoveredItem(item)}
onBuyMouseOut={item => setHoveredItem({})}
onBuy={item => act('buy', {
item: item.name,
})} />
)}
</Tabs.Tab>
);
})}
</Tabs>
)}
</Section>
<GenericUplink
currencyAmount={telecrystals}
currencySymbol="TC" />
</Window.Content>
</Window>
);
};
const ItemList = props => {
export const GenericUplink = (props, context) => {
const {
items,
hoveredItem,
telecrystals,
compact,
onBuy,
onBuyMouseOver,
onBuyMouseOut,
currencyAmount = 0,
currencySymbol = 'cr',
} = props;
const { act, data } = useBackend(context);
const {
compactMode,
lockable,
categories = [],
} = data;
const [
searchText,
setSearchText,
] = useLocalState(context, 'searchText', '');
const [
selectedCategory,
setSelectedCategory,
] = useLocalState(context, 'category', categories[0]?.name);
const testSearch = createSearch(searchText, item => {
return item.name + item.desc;
});
const items = searchText.length > 0
// Flatten all categories and apply search to it
&& categories
.flatMap(category => category.items || [])
.filter(testSearch)
.filter((item, i) => i < MAX_SEARCH_RESULTS)
// Select a category and show all items in it
|| categories
.find(category => category.name === selectedCategory)
?.items;
return (
<Section
title={(
<Box
inline
color={currencyAmount > 0 ? 'good' : 'bad'}>
{formatMoney(currencyAmount)} {currencySymbol}
</Box>
)}
buttons={(
<Fragment>
Search
<Input
value={searchText}
onInput={(e, value) => setSearchText(value)}
mx={1} />
<Button
icon={compactMode ? 'list' : 'info'}
content={compactMode ? 'Compact' : 'Detailed'}
onClick={() => act('compact_toggle')} />
{!!lockable && (
<Button
icon="lock"
content="Lock"
onClick={() => act('lock')} />
)}
</Fragment>
)}>
<Flex>
{searchText.length === 0 && (
<Flex.Item>
<Tabs vertical>
{categories.map(category => (
<Tabs.Tab
key={category.name}
selected={category.name === selectedCategory}
onClick={() => setSelectedCategory(category.name)}>
{category.name} ({category.items?.length || 0})
</Tabs.Tab>
))}
</Tabs>
</Flex.Item>
)}
<Flex.Item grow={1} basis={0}>
<ItemList
compactMode={searchText.length > 0 || compactMode}
currencyAmount={currencyAmount}
currencySymbol={currencySymbol}
items={items} />
</Flex.Item>
</Flex>
</Section>
);
};
const ItemList = (props, context) => {
const {
compactMode,
currencyAmount,
currencySymbol,
} = props;
const { act } = useBackend(context);
const [
hoveredItem,
setHoveredItem,
] = useLocalState(context, 'hoveredItem', {});
const hoveredCost = hoveredItem && hoveredItem.cost || 0;
if (compact) {
// Append extra hover data to items
const items = props.items.map(item => {
const notSameItem = hoveredItem && hoveredItem.name !== item.name;
const notEnoughHovered = currencyAmount - hoveredCost < item.cost;
const disabledDueToHovered = notSameItem && notEnoughHovered;
const disabled = currencyAmount < item.cost || disabledDueToHovered;
return {
...item,
disabled,
};
});
if (compactMode) {
return (
<Table>
{items.map(item => {
const notSameItem = hoveredItem && hoveredItem.name !== item.name;
const notEnoughHovered = telecrystals - hoveredCost < item.cost;
const disabledDueToHovered = notSameItem && notEnoughHovered;
return (
<Table.Row
key={item.name}
className="candystripe">
<Table.Cell bold>
{decodeHtmlEntities(item.name)}
</Table.Cell>
<Table.Cell collapsing textAlign="right">
<Button
fluid
content={item.cost + " TC"}
disabled={telecrystals < item.cost || disabledDueToHovered}
tooltip={item.desc}
tooltipPosition="left"
onmouseover={() => onBuyMouseOver(item)}
onmouseout={() => onBuyMouseOut(item)}
onClick={() => onBuy(item)} />
</Table.Cell>
</Table.Row>
);
})}
{items.map(item => (
<Table.Row
key={item.name}
className="candystripe">
<Table.Cell bold>
{decodeHtmlEntities(item.name)}
</Table.Cell>
<Table.Cell collapsing textAlign="right">
<Button
fluid
content={formatMoney(item.cost) + ' ' + currencySymbol}
disabled={item.disabled}
tooltip={item.desc}
tooltipPosition="left"
onmouseover={() => setHoveredItem(item)}
onmouseout={() => setHoveredItem({})}
onClick={() => act('buy', {
item: item.name,
})} />
</Table.Cell>
</Table.Row>
))}
</Table>
);
}
return items.map(item => {
const notSameItem = hoveredItem && hoveredItem.name !== item.name;
const notEnoughHovered = telecrystals - hoveredCost < item.cost;
const disabledDueToHovered = notSameItem && notEnoughHovered;
return (
<Section
key={item.name}
title={item.name}
level={2}
buttons={(
<Button
content={item.cost + ' TC'}
disabled={telecrystals < item.cost || disabledDueToHovered}
onmouseover={() => onBuyMouseOver(item)}
onmouseout={() => onBuyMouseOut(item)}
onClick={() => onBuy(item)} />
)}>
{decodeHtmlEntities(item.desc)}
</Section>
);
});
return items.map(item => (
<Section
key={item.name}
title={item.name}
level={2}
buttons={(
<Button
content={item.cost + ' ' + currencySymbol}
disabled={item.disabled}
onmouseover={() => setHoveredItem(item)}
onmouseout={() => setHoveredItem({})}
onClick={() => act('buy', {
item: item.name,
})} />
)}>
{decodeHtmlEntities(item.desc)}
</Section>
));
};
@@ -1,8 +1,24 @@
import { Tabs, Section, Button, Grid } from "../../components";
import { Fragment } from "inferno";
import { sortBy } from "common/collections";
import { sortBy } from 'common/collections';
import { Fragment } from 'inferno';
import { useLocalState } from '../../backend';
import { Button, Flex, Grid, Section, Tabs } from '../../components';
export const AccessList = props => {
const diffMap = {
0: {
icon: 'times-circle',
color: 'bad',
},
1: {
icon: 'stop-circle',
color: null,
},
2: {
icon: 'check-circle',
color: 'good',
},
};
export const AccessList = (props, context) => {
const {
accesses = [],
selectedList = [],
@@ -12,35 +28,27 @@ export const AccessList = props => {
grantDep,
denyDep,
} = props;
const diffMap = {
0: {
icon: 'times-circle',
color: 'bad',
},
1: {
icon: 'stop-circle',
color: null,
},
2: {
icon: 'check-circle',
color: 'good',
},
};
const [
selectedAccessName,
setSelectedAccessName,
] = useLocalState(context, 'accessName', accesses[0]?.name);
const selectedAccess = accesses
.find(access => access.name === selectedAccessName);
const selectedAccessEntries = sortBy(
entry => entry.desc,
)(selectedAccess?.accesses || []);
const checkAccessIcon = accesses => {
let oneAccess = false;
let oneInaccess = false;
accesses.forEach(element => {
for (let element of accesses) {
if (selectedList.includes(element.ref)) {
oneAccess = true;
}
else {
oneInaccess = true;
}
});
}
if (!oneAccess && oneInaccess) {
return 0;
}
@@ -69,49 +77,56 @@ export const AccessList = props => {
onClick={() => denyAll()} />
</Fragment>
)}>
<Tabs vertical altSelection>
{accesses.map(access => {
const accessEntries = sortBy(
entry => entry.desc,
)(access.accesses || []);
const icon = diffMap[checkAccessIcon(accessEntries)].icon;
const color = diffMap[checkAccessIcon(accessEntries)].color;
return (
<Tabs.Tab
key={access.name}
label={access.name}
color={color}
icon={icon}>
<Grid>
<Grid.Column mr={0}>
<Button
fluid
icon="check"
content="Grant Region"
color="good"
onClick={() => grantDep(access.regid)} />
</Grid.Column>
<Grid.Column ml={0}>
<Button
fluid
icon="times"
content="Deny Region"
color="bad"
onClick={() => denyDep(access.regid)} />
</Grid.Column>
</Grid>
{accessEntries.map(entry => (
<Button.Checkbox
fluid
key={entry.desc}
content={entry.desc}
checked={selectedList.includes(entry.ref)}
onClick={() => accessMod(entry.ref)} />
))}
</Tabs.Tab>
);
})}
</Tabs>
<Flex>
<Flex.Item>
<Tabs vertical>
{accesses.map(access => {
const entries = access.accesses || [];
const icon = diffMap[checkAccessIcon(entries)].icon;
const color = diffMap[checkAccessIcon(entries)].color;
return (
<Tabs.Tab
key={access.name}
altSelection
color={color}
icon={icon}
selected={access.name === selectedAccessName}
onClick={() => setSelectedAccessName(access.name)}>
{access.name}
</Tabs.Tab>
);
})}
</Tabs>
</Flex.Item>
<Flex.Item grow={1}>
<Grid>
<Grid.Column mr={0}>
<Button
fluid
icon="check"
content="Grant Region"
color="good"
onClick={() => grantDep(selectedAccess.regid)} />
</Grid.Column>
<Grid.Column ml={0}>
<Button
fluid
icon="times"
content="Deny Region"
color="bad"
onClick={() => denyDep(selectedAccess.regid)} />
</Grid.Column>
</Grid>
{selectedAccessEntries.map(entry => (
<Button.Checkbox
fluid
key={entry.desc}
content={entry.desc}
checked={selectedList.includes(entry.ref)}
onClick={() => accessMod(entry.ref)} />
))}
</Flex.Item>
</Flex>
</Section>
);
};
@@ -1,8 +1,8 @@
import { Component } from 'inferno';
import { useBackend, useLocalState } from '../backend';
import { BlockQuote, Box, Button, ByondUi, Collapsible, Icon, Input, Knob, LabeledList, NumberInput, ProgressBar, Section, Slider, Tabs, Tooltip, Flex } from '../components';
import { DraggableControl } from '../components/DraggableControl';
import { Window } from '../layouts';
import { Component, Fragment } from 'inferno';
import { useBackend, useLocalState } from '../../backend';
import { BlockQuote, Box, Button, ByondUi, Collapsible, Flex, Icon, Input, Knob, LabeledList, NumberInput, ProgressBar, Section, Slider, Tabs, Tooltip } from '../../components';
import { DraggableControl } from '../../components/DraggableControl';
import { Window } from '../../layouts';
const COLORS_ARBITRARY = [
'red',
@@ -265,7 +265,7 @@ const KitchenSinkTooltip = props => {
'bottom-right',
];
return (
<Box>
<Fragment>
<Box>
<Box inline position="relative" mr={1}>
Box (hover me).
@@ -285,7 +285,7 @@ const KitchenSinkTooltip = props => {
content={position} />
))}
</Box>
</Box>
</Fragment>
);
};
@@ -5,14 +5,15 @@
/* eslint-disable */
(function(Proto) {
function toAttr(prop) {
return prop.replace(/-[a-z]/g, function (bit) {
return bit[1].toUpperCase();
});
}
'use strict';
if (typeof Proto.setAttribute !== 'undefined') {
function toAttr(prop) {
return prop.replace(/-[a-z]/g, function (bit) {
return bit[1].toUpperCase();
});
}
Proto.setProperty = function (prop, value) {
var attr = toAttr(prop);
if (!value) {
@@ -966,4 +966,4 @@
}());
}
}(self));
}(window));
@@ -4,7 +4,7 @@
*/
/* eslint-disable */
;(function(window, document) {
(function(window, document) {
/*jshint evil:true */
/** version */
var version = '3.7.3';
@@ -326,4 +326,4 @@
module.exports = html5;
}
}(typeof window !== "undefined" ? window : this, document));
}(window, document));
@@ -832,4 +832,4 @@
this._start = node;
};
}());
}(this.window || global));
}(window));
+4
View File
@@ -0,0 +1,4 @@
// Inferno needs Int32Array, and it is not covered by core-js.
if (!window.Int32Array) {
window.Int32Array = Array;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-4
View File
@@ -123,10 +123,6 @@ Byond.call('', {
<!--customheadhtml-->
<!-- Scripts -->
<script type="text/javascript" defer src="shim-html5shiv.js"></script>
<script type="text/javascript" defer src="shim-ie8.js"></script>
<script type="text/javascript" defer src="shim-dom4.js"></script>
<script type="text/javascript" defer src="shim-css-om.js"></script>
<script type="text/javascript" defer src="tgui.bundle.js"></script>
</head>
+4 -3
View File
@@ -1,5 +1,7 @@
import { Window } from './layouts';
const requireInterface = require.context('./interfaces', false, /\.js$/);
const routingError = (type, name) => () => {
return (
<Window resizable>
@@ -19,14 +21,13 @@ export const getRoutedComponent = state => {
if (process.env.NODE_ENV !== 'production') {
// Show a kitchen sink
if (state.showKitchenSink) {
const { KitchenSink } = require('./interfaces/KitchenSink');
return KitchenSink;
return require('./interfaces/manually-routed/KitchenSink').KitchenSink;
}
}
const name = state.config?.interface;
let esModule;
try {
esModule = require(`./interfaces/${name}.js`);
esModule = requireInterface(`./${name}.js`);
}
catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
@@ -7,6 +7,14 @@
display: inline-flex;
}
.Flex--ie8 {
display: table !important;
}
.Flex__item--ie8 {
display: table-cell !important;
}
@for $i from 1 through 2 {
.Flex--spacing--#{$i} {
margin: 0 -3px * $i;
@@ -32,7 +32,8 @@ $border-radius: base.$border-radius !default;
.Tabs__tab {
// Force display block because Button theme overrides it via cascading.
display: block !important;
margin-right: 0;
// Override to stop themed buttons from taking priority over it.
margin-right: 0 !important;
margin-bottom: 0;
padding: 1px 9px 0px 6px;
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
+15 -24
View File
@@ -3,19 +3,25 @@ const path = require('path');
const BuildNotifierPlugin = require('webpack-build-notifier');
const ExtractCssChunks = require('extract-css-chunks-webpack-plugin');
const createStats = verbose => ({
assets: verbose,
builtAt: verbose,
cached: false,
children: false,
chunks: false,
colors: true,
hash: false,
timings: verbose,
version: verbose,
modules: false,
});
module.exports = (env = {}, argv) => {
const config = {
mode: argv.mode === 'production' ? 'production' : 'development',
context: __dirname,
entry: {
tgui: [
path.resolve(__dirname, './styles/main.scss'),
path.resolve(__dirname, './styles/themes/cardtable.scss'),
path.resolve(__dirname, './styles/themes/malfunction.scss'),
path.resolve(__dirname, './styles/themes/ntos.scss'),
path.resolve(__dirname, './styles/themes/hackerman.scss'),
path.resolve(__dirname, './styles/themes/retro.scss'),
path.resolve(__dirname, './styles/themes/syndicate.scss'),
path.resolve(__dirname, './index.js'),
],
},
@@ -97,6 +103,7 @@ module.exports = (env = {}, argv) => {
hints: false,
},
devtool: false,
stats: createStats(true),
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: env.NODE_ENV || argv.mode || 'development',
@@ -129,11 +136,8 @@ module.exports = (env = {}, argv) => {
extractComments: false,
terserOptions: {
ie8: true,
// mangle: false,
output: {
ascii_only: true,
// beautify: true,
// indent_level: 2,
},
},
}),
@@ -168,24 +172,11 @@ module.exports = (env = {}, argv) => {
}
config.devtool = 'cheap-module-source-map';
config.devServer = {
// Informational flags
progress: false,
quiet: false,
noInfo: false,
// Fine-grained logging control
clientLogLevel: 'silent',
stats: {
assets: false,
builtAt: false,
cached: false,
children: false,
chunks: false,
colors: true,
hash: false,
timings: false,
version: false,
modules: false,
},
stats: createStats(false),
};
}