Backend?
This commit is contained in:
@@ -57,6 +57,13 @@ const mapColorPropTo = attrName => (style, value) => {
|
||||
const styleMapperByPropName = {
|
||||
// Direct mapping
|
||||
position: mapRawPropTo('position'),
|
||||
overflow: mapRawPropTo('overflow'),
|
||||
overflowX: mapRawPropTo('overflow-x'),
|
||||
overflowY: mapRawPropTo('overflow-y'),
|
||||
top: mapUnitPropTo('top'),
|
||||
bottom: mapUnitPropTo('bottom'),
|
||||
left: mapUnitPropTo('left'),
|
||||
right: mapUnitPropTo('right'),
|
||||
width: mapUnitPropTo('width'),
|
||||
minWidth: mapUnitPropTo('min-width'),
|
||||
maxWidth: mapUnitPropTo('max-width'),
|
||||
@@ -68,6 +75,7 @@ const styleMapperByPropName = {
|
||||
lineHeight: mapUnitPropTo('line-height'),
|
||||
opacity: mapRawPropTo('opacity'),
|
||||
textAlign: mapRawPropTo('text-align'),
|
||||
verticalAlign: mapRawPropTo('vertical-align'),
|
||||
// Boolean props
|
||||
inline: mapBooleanPropTo('display', 'inline-block'),
|
||||
bold: mapBooleanPropTo('font-weight', 'bold'),
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { classes, pureComponentHooks } from 'common/react';
|
||||
import { tridentVersion } from '../byond';
|
||||
import { tridentVersion, act } from '../byond';
|
||||
import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from '../hotkeys';
|
||||
import { createLogger } from '../logging';
|
||||
import { refocusLayout } from '../refocus';
|
||||
import { Box } from './Box';
|
||||
import { Icon } from './Icon';
|
||||
import { Tooltip } from './Tooltip';
|
||||
import { Input } from './Input';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { Grid } from './Grid';
|
||||
|
||||
const logger = createLogger('Button');
|
||||
|
||||
@@ -21,6 +24,8 @@ export const Button = props => {
|
||||
tooltipPosition,
|
||||
ellipsis,
|
||||
content,
|
||||
iconRotation,
|
||||
iconSpin,
|
||||
children,
|
||||
onclick,
|
||||
onClick,
|
||||
@@ -78,7 +83,7 @@ export const Button = props => {
|
||||
}}
|
||||
{...rest}>
|
||||
{icon && (
|
||||
<Icon name={icon} />
|
||||
<Icon name={icon} rotation={iconRotation} spin={iconSpin} />
|
||||
)}
|
||||
{content}
|
||||
{children}
|
||||
@@ -105,3 +110,148 @@ export const ButtonCheckbox = props => {
|
||||
};
|
||||
|
||||
Button.Checkbox = ButtonCheckbox;
|
||||
|
||||
export class ButtonConfirm extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
clickedOnce: false,
|
||||
};
|
||||
this.handleClick = () => {
|
||||
if (this.state.clickedOnce) {
|
||||
this.setClickedOnce(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
setClickedOnce(clickedOnce) {
|
||||
this.setState({
|
||||
clickedOnce,
|
||||
});
|
||||
if (clickedOnce) {
|
||||
setTimeout(() => window.addEventListener('click', this.handleClick));
|
||||
}
|
||||
else {
|
||||
window.removeEventListener('click', this.handleClick);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
confirmMessage = "Confirm?",
|
||||
confirmColor = "bad",
|
||||
color,
|
||||
content,
|
||||
onClick,
|
||||
...rest
|
||||
} = this.props;
|
||||
return (
|
||||
<Button
|
||||
content={this.state.clickedOnce ? confirmMessage : content}
|
||||
color={this.state.clickedOnce ? confirmColor : color}
|
||||
onClick={() => this.state.clickedOnce
|
||||
? onClick()
|
||||
: this.setClickedOnce(true)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Button.Confirm = ButtonConfirm;
|
||||
|
||||
export class ButtonInput extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.inputRef = createRef();
|
||||
this.state = {
|
||||
inInput: false,
|
||||
};
|
||||
}
|
||||
|
||||
setInInput(inInput) {
|
||||
this.setState({
|
||||
inInput,
|
||||
});
|
||||
if (this.inputRef) {
|
||||
const input = this.inputRef.current;
|
||||
if (inInput) {
|
||||
input.value = this.props.currentValue || "";
|
||||
try {
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commitResult(e) {
|
||||
if (this.inputRef) {
|
||||
const input = this.inputRef.current;
|
||||
const hasValue = (input.value !== "");
|
||||
if (hasValue) {
|
||||
this.props.onCommit(e, input.value);
|
||||
return;
|
||||
} else {
|
||||
if (!this.props.defaultValue) {
|
||||
return;
|
||||
}
|
||||
this.props.onCommit(e, this.props.defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
fluid,
|
||||
content,
|
||||
color = 'default',
|
||||
placeholder,
|
||||
maxLength,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={classes([
|
||||
'Button',
|
||||
fluid && 'Button--fluid',
|
||||
'Button--color--' + color,
|
||||
])}
|
||||
{...rest}
|
||||
onClick={() => this.setInInput(true)}>
|
||||
<div>
|
||||
{content}
|
||||
</div>
|
||||
<input
|
||||
ref={this.inputRef}
|
||||
className="NumberInput__input"
|
||||
style={{
|
||||
'display': !this.state.inInput ? 'none' : undefined,
|
||||
'text-align': 'left',
|
||||
}}
|
||||
onBlur={e => {
|
||||
if (!this.state.inInput) {
|
||||
return;
|
||||
}
|
||||
this.setInInput(false);
|
||||
this.commitResult(e);
|
||||
}}
|
||||
onKeyDown={e => {
|
||||
if (e.keyCode === KEY_ENTER) {
|
||||
this.setInInput(false);
|
||||
this.commitResult(e);
|
||||
return;
|
||||
}
|
||||
if (e.keyCode === KEY_ESCAPE) {
|
||||
this.setInInput(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Button.Input = ButtonInput;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { classes } from 'common/react';
|
||||
import { Component } from 'inferno';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { Box } from './Box';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
@@ -25,6 +25,7 @@ export class Dropdown extends Component {
|
||||
this.setState({ open: open });
|
||||
if (open) {
|
||||
setTimeout(() => window.addEventListener('click', this.handleClick));
|
||||
this.menuRef.focus();
|
||||
}
|
||||
else {
|
||||
window.removeEventListener('click', this.handleClick);
|
||||
@@ -61,7 +62,6 @@ export class Dropdown extends Component {
|
||||
over,
|
||||
width,
|
||||
onClick,
|
||||
onSet,
|
||||
selected,
|
||||
...boxProps
|
||||
} = props;
|
||||
@@ -73,14 +73,18 @@ export class Dropdown extends Component {
|
||||
const adjustedOpen = over ? !this.state.open : this.state.open;
|
||||
|
||||
const menu = this.state.open ? (
|
||||
<Box
|
||||
width={width}
|
||||
<div
|
||||
ref={menu => { this.menuRef = menu; }}
|
||||
tabIndex="-1"
|
||||
style={{
|
||||
'width': width,
|
||||
}}
|
||||
className={classes([
|
||||
'Dropdown__menu',
|
||||
over && 'Dropdown__over',
|
||||
])}>
|
||||
{this.buildMenu()}
|
||||
</Box>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -43,7 +43,7 @@ export class Input extends Component {
|
||||
}
|
||||
};
|
||||
this.handleKeyDown = e => {
|
||||
const { onInput, onChange } = this.props;
|
||||
const { onInput, onChange, onEnter } = this.props;
|
||||
if (e.keyCode === 13) {
|
||||
this.setEditing(false);
|
||||
if (onChange) {
|
||||
@@ -52,7 +52,14 @@ export class Input extends Component {
|
||||
if (onInput) {
|
||||
onInput(e, e.target.value);
|
||||
}
|
||||
e.target.blur();
|
||||
if (onEnter) {
|
||||
onEnter(e, e.target.value);
|
||||
}
|
||||
if (this.props.selfClear) {
|
||||
e.target.value = '';
|
||||
} else {
|
||||
e.target.blur();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.keyCode === 27) {
|
||||
@@ -90,8 +97,10 @@ export class Input extends Component {
|
||||
const { props } = this;
|
||||
// Input only props
|
||||
const {
|
||||
selfClear,
|
||||
onInput,
|
||||
onChange,
|
||||
onEnter,
|
||||
value,
|
||||
maxLength,
|
||||
placeholder,
|
||||
|
||||
@@ -144,7 +144,10 @@ export class NumberInput extends Component {
|
||||
unit,
|
||||
minValue,
|
||||
maxValue,
|
||||
height,
|
||||
width,
|
||||
lineHeight,
|
||||
fontSize,
|
||||
format,
|
||||
onChange,
|
||||
onDrag,
|
||||
@@ -178,6 +181,9 @@ export class NumberInput extends Component {
|
||||
className,
|
||||
])}
|
||||
minWidth={width}
|
||||
minHeight={height}
|
||||
lineHeight={lineHeight}
|
||||
fontSize={fontSize}
|
||||
onMouseDown={this.handleDragStart}>
|
||||
<div className="NumberInput__barContainer">
|
||||
<div
|
||||
@@ -194,6 +200,9 @@ export class NumberInput extends Component {
|
||||
className="NumberInput__input"
|
||||
style={{
|
||||
display: !editing ? 'none' : undefined,
|
||||
height: height,
|
||||
'line-height': lineHeight,
|
||||
'font-size': fontSize,
|
||||
}}
|
||||
onBlur={e => {
|
||||
if (!editing) {
|
||||
|
||||
@@ -162,6 +162,8 @@ const Catalog = props => {
|
||||
content={(data.self_paid
|
||||
? Math.round(pack.cost * 1.1)
|
||||
: pack.cost) + ' credits'}
|
||||
tooltip={pack.desc}
|
||||
tooltipPosition="left"
|
||||
onClick={() => act(ref, 'add', {
|
||||
id: pack.id,
|
||||
})} />
|
||||
|
||||
@@ -64,7 +64,7 @@ export const CodexGigas = props => {
|
||||
<Button
|
||||
key={title.toLowerCase()}
|
||||
content={title}
|
||||
disabled={data.currentSection >= 2}
|
||||
disabled={data.currentSection > 2}
|
||||
onClick={() => act(title + ' ')} />
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
@@ -73,7 +73,7 @@ export const CodexGigas = props => {
|
||||
<Button
|
||||
key={name.toLowerCase()}
|
||||
content={name}
|
||||
disabled={data.currentSection >= 4}
|
||||
disabled={data.currentSection > 4}
|
||||
onClick={() => act(name)} />
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
@@ -89,7 +89,7 @@ export const CodexGigas = props => {
|
||||
<LabeledList.Item label="Submit">
|
||||
<Button
|
||||
content="Search"
|
||||
disabled={data.currentSection <= 4}
|
||||
disabled={data.currentSection < 4}
|
||||
onClick={() => act('search')} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Grid, Section, NoticeBox } from '../components';
|
||||
import { toTitleCase } from 'common/string';
|
||||
|
||||
export const EightBallVote = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const {
|
||||
question,
|
||||
shaking,
|
||||
answers = [],
|
||||
} = data;
|
||||
|
||||
if (!shaking) {
|
||||
return (
|
||||
<NoticeBox>
|
||||
No question is currently being asked.
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Box
|
||||
bold
|
||||
textAlign="center"
|
||||
fontSize="16px"
|
||||
m={1}>
|
||||
"{question}"
|
||||
</Box>
|
||||
<Grid>
|
||||
{answers.map(answer => (
|
||||
<Grid.Column key={answer.answer}>
|
||||
<Button
|
||||
fluid
|
||||
bold
|
||||
content={toTitleCase(answer.answer)}
|
||||
selected={answer.selected}
|
||||
fontSize="16px"
|
||||
lineHeight="24px"
|
||||
textAlign="center"
|
||||
mb={1}
|
||||
onClick={() => act('vote', {
|
||||
answer: answer.answer,
|
||||
})} />
|
||||
<Box
|
||||
bold
|
||||
textAlign="center"
|
||||
fontSize="30px">
|
||||
{answer.amount}
|
||||
</Box>
|
||||
</Grid.Column>
|
||||
))}
|
||||
</Grid>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,115 @@
|
||||
import { Box, Section, Button, Grid } from '../components';
|
||||
import { useBackend } from '../backend';
|
||||
|
||||
export const EmergencyShuttleConsole = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const {
|
||||
timer_str,
|
||||
enabled,
|
||||
emagged,
|
||||
engines_started,
|
||||
authorizations_remaining,
|
||||
authorizations = [],
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Box
|
||||
bold
|
||||
fontSize="40px"
|
||||
textAlign="center"
|
||||
fontFamily="monospace">
|
||||
{timer_str}
|
||||
</Box>
|
||||
<Box
|
||||
textAlign="center"
|
||||
fontSize="16px"
|
||||
mb={1}>
|
||||
<Box
|
||||
inline
|
||||
bold>
|
||||
ENGINES:
|
||||
</Box>
|
||||
<Box
|
||||
inline
|
||||
color={engines_started ? 'good' : 'average'}
|
||||
ml={1}>
|
||||
{engines_started ? 'Online' : 'Idle'}
|
||||
</Box>
|
||||
</Box>
|
||||
<Section
|
||||
title="Early Launch Authorization"
|
||||
level={2}
|
||||
buttons={(
|
||||
<Button
|
||||
icon="times"
|
||||
content="Repeal All"
|
||||
color="bad"
|
||||
disabled={!enabled}
|
||||
onClick={() => act('abort')} />
|
||||
)}>
|
||||
<Grid>
|
||||
<Grid.Column>
|
||||
<Button
|
||||
fluid
|
||||
icon="exclamation-triangle"
|
||||
color="good"
|
||||
content="AUTHORIZE"
|
||||
disabled={!enabled}
|
||||
onClick={() => act('authorize')} />
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<Button
|
||||
fluid
|
||||
icon="minus"
|
||||
content="REPEAL"
|
||||
disabled={!enabled}
|
||||
onClick={() => act('repeal')} />
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
<Section
|
||||
title="Authorizations"
|
||||
level={3}
|
||||
minHeight="150px"
|
||||
buttons={(
|
||||
<Box
|
||||
inline
|
||||
bold
|
||||
color={emagged ? 'bad' : 'good'}>
|
||||
{emagged ? 'ERROR' : 'Remaining: ' + authorizations_remaining}
|
||||
</Box>
|
||||
)}>
|
||||
{authorizations.length > 0 ? (
|
||||
authorizations.map(authorization => (
|
||||
<Box
|
||||
key={authorization.name}
|
||||
bold
|
||||
fontSize="16px"
|
||||
className="candystripe">
|
||||
{authorization.name} ({authorization.job})
|
||||
</Box>
|
||||
))
|
||||
) : (
|
||||
<Box
|
||||
bold
|
||||
textAlign="center"
|
||||
fontSize="16px"
|
||||
color="average">
|
||||
No Active Authorizations
|
||||
</Box>
|
||||
)}
|
||||
{authorizations.map(authorization => (
|
||||
<Box
|
||||
key={authorization.name}
|
||||
bold
|
||||
fontSize="16px"
|
||||
className="candystripe">
|
||||
{authorization.name} ({authorization.job})
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
</Section>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,103 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Section, LabeledList, Button, NumberInput } from '../components';
|
||||
import { Fragment } from 'inferno';
|
||||
|
||||
export const GulagTeleporterConsole = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const {
|
||||
teleporter,
|
||||
teleporter_lock,
|
||||
teleporter_state_open,
|
||||
teleporter_location,
|
||||
beacon,
|
||||
beacon_location,
|
||||
id,
|
||||
id_name,
|
||||
can_teleport,
|
||||
goal = 0,
|
||||
prisoner = {},
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Section
|
||||
title="Teleporter Console"
|
||||
buttons={(
|
||||
<Fragment>
|
||||
<Button
|
||||
content={teleporter_state_open ? 'Open' : 'Closed'}
|
||||
disabled={teleporter_lock}
|
||||
selected={teleporter_state_open}
|
||||
onClick={() => act('toggle_open')} />
|
||||
<Button
|
||||
icon={teleporter_lock ? 'lock' : 'unlock'}
|
||||
content={teleporter_lock ? 'Locked' : 'Unlocked'}
|
||||
selected={teleporter_lock}
|
||||
disabled={teleporter_state_open}
|
||||
onClick={() => act('teleporter_lock')}
|
||||
/>
|
||||
</Fragment>
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Teleporter Unit"
|
||||
color={teleporter ? 'good' : 'bad'}
|
||||
buttons={!teleporter && (
|
||||
<Button
|
||||
content="Reconnect"
|
||||
onClick={() => act('scan_teleporter')}
|
||||
/>
|
||||
)}>
|
||||
{teleporter ? teleporter_location : 'Not Connected'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Receiver Beacon"
|
||||
color={beacon ? 'good' : 'bad'}
|
||||
buttons={!beacon && (
|
||||
<Button
|
||||
content="Reconnect"
|
||||
onClick={() => act('scan_beacon')}
|
||||
/>
|
||||
)}>
|
||||
{beacon ? beacon_location : 'Not Connected'}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section title="Prisoner Details">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Prisoner ID">
|
||||
<Button
|
||||
fluid
|
||||
content={id ? id_name : 'No ID'}
|
||||
onClick={() => act('handle_id')}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Point Goal">
|
||||
<NumberInput
|
||||
value={goal}
|
||||
width="48px"
|
||||
minValue={1}
|
||||
maxValue={1000}
|
||||
onChange={(e, value) => act('set_goal', { value: value })}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Occupant">
|
||||
{prisoner.name ? prisoner.name : "No Occupant"}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Criminal Status">
|
||||
{prisoner.crimstat ? prisoner.crimstat : 'No Status'}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Button
|
||||
fluid
|
||||
content="Process Prisoner"
|
||||
disabled={!can_teleport}
|
||||
textAlign="center"
|
||||
color="bad"
|
||||
onClick={() => act('teleport')}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Section, LabeledList, ProgressBar, Button, BlockQuote } from '../components';
|
||||
|
||||
export const Intellicard = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const offline = (isDead || isBraindead);
|
||||
|
||||
const {
|
||||
name,
|
||||
isDead,
|
||||
isBraindead,
|
||||
health,
|
||||
wireless,
|
||||
radio,
|
||||
wiping,
|
||||
laws = [],
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Section
|
||||
title={name || "Empty Card"}
|
||||
buttons={!!name && (
|
||||
<Button
|
||||
icon="trash"
|
||||
content={wiping ? 'Stop Wiping' : 'Wipe'}
|
||||
disabled={isDead}
|
||||
onClick={() => act('wipe')} />
|
||||
)}>
|
||||
{!!name && (
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Status"
|
||||
color={(offline) ? 'bad' : 'good'}>
|
||||
{offline ? 'Offline' : 'Operation'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Software Integrity">
|
||||
<ProgressBar
|
||||
value={health}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
ranges={{
|
||||
good: [70, Infinity],
|
||||
average: [50, 70],
|
||||
bad: [-Infinity, 50],
|
||||
}}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Settings">
|
||||
<Button
|
||||
icon="signal"
|
||||
content="Wireless Activity"
|
||||
selected={wireless}
|
||||
onClick={() => act('wireless')} />
|
||||
<Button
|
||||
icon="microphone"
|
||||
content="Subspace Radio"
|
||||
selected={radio}
|
||||
onClick={() => act('radio')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Laws">
|
||||
{laws.map(law => (
|
||||
<BlockQuote key={law}>
|
||||
{law}
|
||||
</BlockQuote>
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,278 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, LabeledList, Section, Grid, Box, Input, NoticeBox, Table, NumberInput } from '../components';
|
||||
|
||||
export const LaunchpadButtonPad = props => {
|
||||
const { act } = useBackend(props);
|
||||
|
||||
return (
|
||||
<Grid width="1px">
|
||||
<Grid.Column>
|
||||
<Button
|
||||
fluid
|
||||
icon="arrow-left"
|
||||
iconRotation={45}
|
||||
mb={1}
|
||||
onClick={() => act('move_pos', {
|
||||
x: -1,
|
||||
y: 1,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
icon="arrow-left"
|
||||
mb={1}
|
||||
onClick={() => act('move_pos', {
|
||||
x: -1,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
icon="arrow-down"
|
||||
iconRotation={45}
|
||||
mb={1}
|
||||
onClick={() => act('move_pos', {
|
||||
x: -1,
|
||||
y: -1,
|
||||
})} />
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<Button
|
||||
fluid
|
||||
icon="arrow-up"
|
||||
mb={1}
|
||||
onClick={() => act('move_pos', {
|
||||
y: 1,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
content="R"
|
||||
mb={1}
|
||||
onClick={() => act('set_pos', {
|
||||
x: 0,
|
||||
y: 0,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
icon="arrow-down"
|
||||
mb={1}
|
||||
onClick={() => act('move_pos', {
|
||||
y: -1,
|
||||
})} />
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<Button
|
||||
fluid
|
||||
icon="arrow-up"
|
||||
iconRotation={45}
|
||||
mb={1}
|
||||
onClick={() => act('move_pos', {
|
||||
x: 1,
|
||||
y: 1,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
icon="arrow-right"
|
||||
mb={1}
|
||||
onClick={() => act('move_pos', {
|
||||
x: 1,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
icon="arrow-right"
|
||||
iconRotation={45}
|
||||
mb={1}
|
||||
onClick={() => act('move_pos', {
|
||||
x: 1,
|
||||
y: -1,
|
||||
})} />
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export const LaunchpadControl = props => {
|
||||
const { topLevel } = props;
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
pad_name,
|
||||
range,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Section
|
||||
title={(
|
||||
<Input
|
||||
value={pad_name}
|
||||
width="170px"
|
||||
onChange={(e, value) => act('rename', {
|
||||
name: value,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
level={topLevel ? 1 : 2}
|
||||
buttons={(
|
||||
<Button
|
||||
icon="times"
|
||||
content="Remove"
|
||||
color="bad"
|
||||
onClick={() => act('remove')} />
|
||||
)}>
|
||||
<Grid>
|
||||
<Grid.Column>
|
||||
<Section title="Controls" level={2}>
|
||||
<LaunchpadButtonPad state={props.state} />
|
||||
</Section>
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<Section title="Target" level={2}>
|
||||
<Box fontSize="26px">
|
||||
<Box mb={1}>
|
||||
<Box
|
||||
inline
|
||||
bold
|
||||
mr={1}>
|
||||
X:
|
||||
</Box>
|
||||
<NumberInput
|
||||
value={x}
|
||||
minValue={-range}
|
||||
maxValue={range}
|
||||
lineHeight="30px"
|
||||
fontSize="26px"
|
||||
width="90px"
|
||||
height="30px"
|
||||
stepPixelSize={10}
|
||||
onChange={(e, value) => act('set_pos', {
|
||||
x: value,
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Box
|
||||
inline
|
||||
bold
|
||||
mr={1}>
|
||||
Y:
|
||||
</Box>
|
||||
<NumberInput
|
||||
value={y}
|
||||
minValue={-range}
|
||||
maxValue={range}
|
||||
stepPixelSize={10}
|
||||
lineHeight="30px"
|
||||
fontSize="26px"
|
||||
width="90px"
|
||||
height="30px"
|
||||
onChange={(e, value) => act('set_pos', {
|
||||
y: value,
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Section>
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Grid.Column>
|
||||
<Button
|
||||
fluid
|
||||
icon="upload"
|
||||
content="Launch"
|
||||
textAlign="center"
|
||||
onClick={() => act('launch')} />
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<Button
|
||||
fluid
|
||||
icon="download"
|
||||
content="Pull"
|
||||
textAlign="center"
|
||||
onClick={() => act('pull')} />
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
export const LaunchpadRemote = props => {
|
||||
const { data } = useBackend(props);
|
||||
|
||||
const {
|
||||
has_pad,
|
||||
pad_closed,
|
||||
} = data;
|
||||
|
||||
if (!has_pad) {
|
||||
return (
|
||||
<NoticeBox>
|
||||
No Launchpad Connected
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
|
||||
if (pad_closed) {
|
||||
return (
|
||||
<NoticeBox>
|
||||
Launchpad Closed
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LaunchpadControl topLevel state={props.state} />
|
||||
);
|
||||
};
|
||||
|
||||
export const LaunchpadConsole = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const {
|
||||
launchpads = [],
|
||||
selected_id,
|
||||
} = data;
|
||||
|
||||
if (launchpads.length <= 0) {
|
||||
return (
|
||||
<NoticeBox>
|
||||
No Pads Connected
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Grid>
|
||||
<Grid.Column size={0.6}>
|
||||
<Box
|
||||
style={{
|
||||
'border-right': '2px solid rgba(255, 255, 255, 0.1)',
|
||||
}}
|
||||
minHeight="190px"
|
||||
mr={1}>
|
||||
{launchpads.map(launchpad => (
|
||||
<Button
|
||||
fluid
|
||||
key={launchpad.name}
|
||||
content={launchpad.name}
|
||||
selected={selected_id === launchpad.id}
|
||||
color="transparent"
|
||||
onClick={() => act('select_pad', { id: launchpad.id })}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
{selected_id ? (
|
||||
<LaunchpadControl state={props.state} />
|
||||
) : (
|
||||
<Box>
|
||||
Please select a pad
|
||||
</Box>
|
||||
)}
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,147 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Section, LabeledList, Button, NumberInput, ProgressBar, Grid, Input, Dropdown } from '../components';
|
||||
import { Fragment } from 'inferno';
|
||||
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';
|
||||
|
||||
export const Mule = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const locked = data.locked && !data.siliconUser;
|
||||
|
||||
const {
|
||||
siliconUser,
|
||||
on,
|
||||
cell,
|
||||
cellPercent,
|
||||
load,
|
||||
mode,
|
||||
modeStatus,
|
||||
haspai,
|
||||
autoReturn,
|
||||
autoPickup,
|
||||
reportDelivery,
|
||||
destination,
|
||||
home,
|
||||
id,
|
||||
destinations = [],
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<InterfaceLockNoticeBox
|
||||
siliconUser={siliconUser}
|
||||
locked={locked}
|
||||
/>
|
||||
<Section
|
||||
title="Status"
|
||||
minHeight="110px"
|
||||
buttons={!locked && (
|
||||
<Button
|
||||
icon={on ? 'power-off' : 'times'}
|
||||
content={on ? 'On' : 'Off'}
|
||||
selected={on}
|
||||
onClick={() => act('on')}
|
||||
/>
|
||||
)} >
|
||||
<ProgressBar
|
||||
value={cell ? (cellPercent / 100) : 0}
|
||||
color={cell ? 'good' : 'bad'}
|
||||
/>
|
||||
<Grid mt={1}>
|
||||
<Grid.Column>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Mode" color={modeStatus}>
|
||||
{mode}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Load"
|
||||
color={load ? 'good' : 'average'}>
|
||||
{load || 'None'}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
</Section>
|
||||
{!locked && (
|
||||
<Section
|
||||
title="Controls"
|
||||
buttons={(
|
||||
<Fragment>
|
||||
{!!load && (
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Unload"
|
||||
onClick={() => act('unload')} />
|
||||
)}
|
||||
{!!haspai && (
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject PAI"
|
||||
onClick={() => act('ejectpai')} />
|
||||
)}
|
||||
</Fragment>
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="ID">
|
||||
<Input
|
||||
value={id}
|
||||
onChange={(e, value) => act('setid', { value: value })}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Destination">
|
||||
<Dropdown
|
||||
over
|
||||
selected={destination || 'None'}
|
||||
options={destinations}
|
||||
width="150px"
|
||||
onSelected={val => act('destination', { value: val })}
|
||||
/>
|
||||
<Button
|
||||
icon="stop"
|
||||
content="Stop"
|
||||
onClick={() => act('stop')} />
|
||||
<Button
|
||||
icon="play"
|
||||
content="Go"
|
||||
onClick={() => act('go')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Home">
|
||||
<Dropdown
|
||||
over
|
||||
selected={home}
|
||||
options={destinations}
|
||||
width="150px"
|
||||
onSelected={val => act('destination', { value: val })} />
|
||||
<Button
|
||||
icon="home"
|
||||
content="Go Home"
|
||||
onClick={() => act('home')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Settings">
|
||||
<Button.Checkbox
|
||||
checked={autoReturn}
|
||||
content="Auto-Return"
|
||||
onClick={() => act('autored')} />
|
||||
<br />
|
||||
<Button.Checkbox
|
||||
checked={autoPickup}
|
||||
content="Auto-Pickup"
|
||||
onClick={() => act('autopick')}
|
||||
/>
|
||||
<br />
|
||||
<Button.Checkbox
|
||||
checked={reportDelivery}
|
||||
content="Report Delivery"
|
||||
onClick={() => act('report')}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Section, Button } from '../components';
|
||||
|
||||
export const NotificationPreferences = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const ignoresPreSort = data.ignore || [];
|
||||
const ignores = ignoresPreSort.sort((a, b) => {
|
||||
const descA = a.desc.toLowerCase();
|
||||
const descB = b.desc.toLowerCase();
|
||||
if (descA < descB) {
|
||||
return -1;
|
||||
}
|
||||
if (descA > descB) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<Section title="Ghost Role Notifications">
|
||||
{ignores.map(ignore => (
|
||||
<Button
|
||||
fluid
|
||||
key={ignore.key}
|
||||
icon={ignore.enabled ? 'times' : 'check'}
|
||||
content={ignore.desc}
|
||||
color={ignore.enabled ? 'bad' : 'good'}
|
||||
onClick={() => act('toggle_ignore', { key: ignore.key })} />
|
||||
))}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, ProgressBar, Section, AnimatedNumber } from '../components';
|
||||
|
||||
export const NtnetRelay = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const {
|
||||
enabled,
|
||||
dos_capacity,
|
||||
dos_overload,
|
||||
dos_crashed,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Network Buffer"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="power-off"
|
||||
selected={enabled}
|
||||
content={enabled ? 'ENABLED' : 'DISABLED'}
|
||||
onClick={() => act('toggle')}
|
||||
/>
|
||||
)}>
|
||||
{!dos_crashed ? (
|
||||
<ProgressBar
|
||||
value={dos_overload}
|
||||
minValue={0}
|
||||
maxValue={dos_capacity}>
|
||||
<AnimatedNumber value={dos_overload} /> GQ
|
||||
{' / '}
|
||||
{dos_capacity} GQ
|
||||
</ProgressBar>
|
||||
) : (
|
||||
<Box fontFamily="monospace">
|
||||
<Box fontSize="20px">
|
||||
NETWORK BUFFER OVERFLOW
|
||||
</Box>
|
||||
<Box fontSize="16px">
|
||||
OVERLOAD RECOVERY MODE
|
||||
</Box>
|
||||
<Box>
|
||||
This system is suffering temporary outage due to overflow
|
||||
of traffic buffers. Until buffered traffic is processed,
|
||||
all further requests will be dropped. Frequent occurences
|
||||
of this error may indicate insufficient hardware capacity
|
||||
of your network. Please contact your network planning
|
||||
department for instructions on how to resolve this issue.
|
||||
</Box>
|
||||
<Box fontSize="20px" color="bad">
|
||||
ADMINISTRATOR OVERRIDE
|
||||
</Box>
|
||||
<Box fontSize="16px" color="bad">
|
||||
CAUTION - DATA LOSS MAY OCCUR
|
||||
</Box>
|
||||
<Button
|
||||
icon="signal"
|
||||
content="PURGE BUFFER"
|
||||
mt={1}
|
||||
color="bad"
|
||||
onClick={() => act('restart')} />
|
||||
</Box>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Flex, Icon, LabeledList, NoticeBox, ProgressBar, Section, ColorBox } from '../components';
|
||||
|
||||
export const NtosConfiguration = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const {
|
||||
power_usage,
|
||||
battery_exists,
|
||||
battery = {},
|
||||
disk_size,
|
||||
disk_used,
|
||||
hardware = [],
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Section
|
||||
title="Power Supply"
|
||||
buttons={(
|
||||
<Box
|
||||
inline
|
||||
bold
|
||||
mr={1}>
|
||||
Power Draw: {power_usage}W
|
||||
</Box>
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Battery Status"
|
||||
color={!battery_exists && 'average'}>
|
||||
{battery_exists ? (
|
||||
<ProgressBar
|
||||
value={battery.charge}
|
||||
minValue={0}
|
||||
maxValue={battery.max}
|
||||
ranges={{
|
||||
good: [battery.max / 2, Infinity],
|
||||
average: [battery.max / 4, battery.max / 2],
|
||||
bad: [-Infinity, battery.max / 4],
|
||||
}}>
|
||||
{battery.charge} / {battery.max}
|
||||
</ProgressBar>
|
||||
) : 'Not Available'}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section title="File System">
|
||||
<ProgressBar
|
||||
value={disk_used}
|
||||
minValue={0}
|
||||
maxValue={disk_size}
|
||||
color="good">
|
||||
{disk_used} GQ / {disk_size} GQ
|
||||
</ProgressBar>
|
||||
</Section>
|
||||
<Section title="Hardware Components">
|
||||
{hardware.map(component => (
|
||||
<Section
|
||||
key={component.name}
|
||||
title={component.name}
|
||||
level={2}
|
||||
buttons={(
|
||||
<Fragment>
|
||||
{!component.critical && (
|
||||
<Button.Checkbox
|
||||
content="Enabled"
|
||||
checked={component.enabled}
|
||||
mr={1}
|
||||
onClick={() => act('PC_toggle_component', {
|
||||
name: component.name,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
<Box
|
||||
inline
|
||||
bold
|
||||
mr={1}>
|
||||
Power Usage: {component.powerusage}W
|
||||
</Box>
|
||||
</Fragment>
|
||||
)}>
|
||||
{component.desc}
|
||||
</Section>
|
||||
))}
|
||||
</Section>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,170 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, Grid, LabeledList, ProgressBar, Section, Input, Table, Icon, Flex } from '../components';
|
||||
import { Fragment } from 'inferno';
|
||||
import { createLogger } from '../logging';
|
||||
|
||||
const logger = createLogger('ntos chat');
|
||||
|
||||
export const NtosNetChat = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const {
|
||||
can_admin,
|
||||
adminmode,
|
||||
authed,
|
||||
username,
|
||||
active_channel,
|
||||
is_operator,
|
||||
all_channels = [],
|
||||
clients = [],
|
||||
messages = [],
|
||||
} = data;
|
||||
|
||||
const in_channel = (active_channel !== null);
|
||||
const authorized = (authed || adminmode);
|
||||
|
||||
return (
|
||||
<Section
|
||||
height="600px">
|
||||
<Table
|
||||
height="580px">
|
||||
<Table.Row>
|
||||
<Table.Cell
|
||||
verticalAlign="top"
|
||||
style={{
|
||||
width: '200px',
|
||||
}}>
|
||||
<Box
|
||||
height="537px"
|
||||
overflowY="scroll">
|
||||
<Button.Input
|
||||
fluid
|
||||
content="New Channel..."
|
||||
onCommit={(e, value) => act('PRG_newchannel', {
|
||||
new_channel_name: value,
|
||||
})} />
|
||||
{all_channels.map(channel => (
|
||||
<Button
|
||||
fluid
|
||||
key={channel.chan}
|
||||
content={channel.chan}
|
||||
selected={channel.id === active_channel}
|
||||
color="transparent"
|
||||
onClick={() => act('PRG_joinchannel', {
|
||||
id: channel.id,
|
||||
})} />
|
||||
))}
|
||||
</Box>
|
||||
<Button.Input
|
||||
fluid
|
||||
mt={1}
|
||||
content={username + '...'}
|
||||
currentValue={username}
|
||||
onCommit={(e, value) => act('PRG_changename', {
|
||||
new_name: value,
|
||||
})} />
|
||||
{!!can_admin && (
|
||||
<Button
|
||||
fluid
|
||||
bold
|
||||
content={"ADMIN MODE: " + (adminmode ? 'ON' : 'OFF')}
|
||||
color={adminmode ? 'bad' : 'good'}
|
||||
onClick={() => act('PRG_toggleadmin')} />
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Box
|
||||
height="560px"
|
||||
overflowY="scroll">
|
||||
{in_channel && (
|
||||
authorized ? (
|
||||
messages.map(message => (
|
||||
<Box
|
||||
key={message.msg}>
|
||||
{message.msg}
|
||||
</Box>
|
||||
))
|
||||
) : (
|
||||
<Box
|
||||
textAlign="center">
|
||||
<Icon
|
||||
name="exclamation-triangle"
|
||||
mt={4}
|
||||
fontSize="40px" />
|
||||
<Box
|
||||
mt={1}
|
||||
bold
|
||||
fontSize="18px">
|
||||
THIS CHANNEL IS PASSWORD PROTECTED
|
||||
</Box>
|
||||
<Box mt={1}>
|
||||
INPUT PASSWORD TO ACCESS
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Input
|
||||
fluid
|
||||
selfClear
|
||||
mt={1}
|
||||
onEnter={(e, value) => act('PRG_speak', {
|
||||
message: value,
|
||||
})} />
|
||||
</Table.Cell>
|
||||
<Table.Cell
|
||||
verticalAlign="top"
|
||||
style={{
|
||||
width: '150px',
|
||||
}}>
|
||||
<Box
|
||||
height="477px"
|
||||
overflowY="scroll">
|
||||
{clients.map(client => (
|
||||
<Box key={client.name}>
|
||||
{client.name}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
{(in_channel && authorized) && (
|
||||
<Fragment>
|
||||
<Button.Input
|
||||
fluid
|
||||
content="Save log..."
|
||||
defaultValue="new_log"
|
||||
onCommit={(e, value) => act('PRG_savelog', {
|
||||
log_name: value,
|
||||
})} />
|
||||
<Button.Confirm
|
||||
fluid
|
||||
content="Leave Channel"
|
||||
onClick={() => act('PRG_leavechannel')} />
|
||||
</Fragment>
|
||||
)}
|
||||
{!!is_operator && authed && (
|
||||
<Fragment>
|
||||
<Button.Confirm
|
||||
fluid
|
||||
content="Delete Channel"
|
||||
onClick={() => act('PRG_deletechannel')} />
|
||||
<Button.Input
|
||||
fluid
|
||||
content="Rename Channel..."
|
||||
onCommit={(e, value) => act('PRG_renamechannel', {
|
||||
new_name: value,
|
||||
})} />
|
||||
<Button.Input
|
||||
fluid
|
||||
content="Set Password..."
|
||||
onCommit={(e, value) => act('PRG_setpassword', {
|
||||
new_password: value,
|
||||
})} />
|
||||
</Fragment>
|
||||
)}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
import { Grid, NumberInput, Button, Section } from '../components';
|
||||
import { useBackend } from '../backend';
|
||||
import { toFixed } from 'common/math';
|
||||
|
||||
export const Signaler = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
const {
|
||||
code,
|
||||
frequency,
|
||||
minFrequency,
|
||||
maxFrequency,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<Grid>
|
||||
<Grid.Column size={1.4} color="label">
|
||||
Frequency:
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<NumberInput
|
||||
animate
|
||||
unit="kHz"
|
||||
step={0.2}
|
||||
stepPixelSize={6}
|
||||
minValue={minFrequency / 10}
|
||||
maxValue={maxFrequency / 10}
|
||||
value={frequency / 10}
|
||||
format={value => toFixed(value, 1)}
|
||||
width={13}
|
||||
onDrag={(e, value) => act('freq', {
|
||||
freq: value,
|
||||
})} />
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<Button
|
||||
ml={1.3}
|
||||
icon="sync"
|
||||
content="Reset"
|
||||
onClick={() => act('reset', {
|
||||
reset: "freq",
|
||||
})} />
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
<Grid mt={0.6}>
|
||||
<Grid.Column size={1.4} color="label">
|
||||
Code:
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<NumberInput
|
||||
animate
|
||||
step={1}
|
||||
stepPixelSize={6}
|
||||
minValue={1}
|
||||
maxValue={100}
|
||||
value={code}
|
||||
width={13}
|
||||
onDrag={(e, value) => act('code', {
|
||||
code: value,
|
||||
})} />
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<Button
|
||||
ml={1.3}
|
||||
icon="sync"
|
||||
content="Reset"
|
||||
onClick={() => act('reset', {
|
||||
reset: "code",
|
||||
})} />
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
<Grid mt={0.8}>
|
||||
<Grid.Column>
|
||||
<Button
|
||||
mb={-0.1}
|
||||
fluid
|
||||
icon="arrow-up"
|
||||
content="Send Signal"
|
||||
textAlign="center"
|
||||
onClick={() => act('signal')} />
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Section, LabeledList, ProgressBar, Button, BlockQuote, Grid, Box } from '../components';
|
||||
|
||||
export const BodyEntry = props => {
|
||||
const { body, swapFunc } = props;
|
||||
|
||||
const statusMap = {
|
||||
Dead: "bad",
|
||||
Unconscious: "average",
|
||||
Conscious: "good",
|
||||
};
|
||||
|
||||
const occupiedMap = {
|
||||
owner: "You Are Here",
|
||||
stranger: "Occupied",
|
||||
available: "Swap",
|
||||
};
|
||||
|
||||
return (
|
||||
<Section
|
||||
title={(
|
||||
<Box inline color={body.htmlcolor}>
|
||||
{body.name}
|
||||
</Box>
|
||||
)}
|
||||
level={2}
|
||||
buttons={(
|
||||
<Button
|
||||
content={occupiedMap[body.occupied]}
|
||||
selected={body.occupied === 'owner'}
|
||||
color={(body.occupied === 'stranger') && 'bad'}
|
||||
onClick={() => swapFunc()}
|
||||
/>
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Status"
|
||||
bold
|
||||
color={statusMap[body.status]}>
|
||||
{body.status}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Jelly">
|
||||
{body.exoticblood}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Location">
|
||||
{body.area}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
export const SlimeBodySwapper = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const {
|
||||
bodies = [],
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Section>
|
||||
{bodies.map(body => (
|
||||
<BodyEntry
|
||||
key={body.name}
|
||||
body={body}
|
||||
swapFunc={() => act('swap', { ref: body.ref })} />
|
||||
))}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, LabeledList, ProgressBar, Section } from '../components';
|
||||
import { Box, Button, NumberInput, LabeledList, ProgressBar, Section } from '../components';
|
||||
|
||||
export const Smes = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
@@ -75,6 +75,17 @@ export const Smes = props => {
|
||||
onClick={() => act('input', {
|
||||
adjust: -10000,
|
||||
})} />
|
||||
<NumberInput
|
||||
value={Math.round(data.inputLevel/1000)}
|
||||
unit="kW"
|
||||
width="65px"
|
||||
minValue={0}
|
||||
maxValue={data.inputLevelMax/1000}
|
||||
onChange={(e, value) => {
|
||||
return act('input', {
|
||||
target: value*1000,
|
||||
});
|
||||
}} />
|
||||
<Button
|
||||
icon="forward"
|
||||
disabled={data.inputLevel === data.inputLevelMax}
|
||||
@@ -131,6 +142,17 @@ export const Smes = props => {
|
||||
onClick={() => act('output', {
|
||||
adjust: -10000,
|
||||
})} />
|
||||
<NumberInput
|
||||
value={Math.round(data.outputLevel/1000)}
|
||||
unit="kW"
|
||||
width="65px"
|
||||
minValue={0}
|
||||
maxValue={data.outputLevelMax/1000}
|
||||
onChange={(e, value) => {
|
||||
return act('output', {
|
||||
target: value*1000,
|
||||
});
|
||||
}} />
|
||||
<Button
|
||||
icon="forward"
|
||||
disabled={data.outputLevel === data.outputLevelMax}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { toFixed } from 'common/math';
|
||||
import { Fragment } from 'inferno';
|
||||
import { act } from '../byond';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Grid, LabeledList, NumberInput, ProgressBar, Section } from '../components';
|
||||
|
||||
export const SolarControl = props => {
|
||||
const { state } = props;
|
||||
const { config, data } = state;
|
||||
const { ref } = config;
|
||||
const { act, data } = useBackend(props);
|
||||
const {
|
||||
generated,
|
||||
angle,
|
||||
generated_ratio,
|
||||
azimuth_current,
|
||||
azimuth_rate,
|
||||
max_rotation_rate,
|
||||
tracking_state,
|
||||
tracking_rate,
|
||||
connected_panels,
|
||||
connected_tracker,
|
||||
} = data;
|
||||
@@ -23,7 +23,7 @@ export const SolarControl = props => {
|
||||
<Button
|
||||
icon="sync"
|
||||
content="Scan for new hardware"
|
||||
onClick={() => act(ref, 'refresh')} />
|
||||
onClick={() => act('refresh')} />
|
||||
)}>
|
||||
<Grid>
|
||||
<Grid.Column>
|
||||
@@ -45,13 +45,13 @@ export const SolarControl = props => {
|
||||
<LabeledList.Item label="Power output">
|
||||
<ProgressBar
|
||||
ranges={{
|
||||
good: [60000, Infinity],
|
||||
average: [30000, 60000],
|
||||
bad: [-Infinity, 30000],
|
||||
good: [0.66, Infinity],
|
||||
average: [0.33, 0.66],
|
||||
bad: [-Infinity, 0.33],
|
||||
}}
|
||||
minValue={0}
|
||||
maxValue={90000}
|
||||
value={generated}
|
||||
maxValue={1}
|
||||
value={generated_ratio}
|
||||
content={generated + ' W'} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
@@ -65,20 +65,20 @@ export const SolarControl = props => {
|
||||
icon="times"
|
||||
content="Off"
|
||||
selected={tracking_state === 0}
|
||||
onClick={() => act(ref, 'tracking', { mode: 0 })} />
|
||||
onClick={() => act('tracking', { mode: 0 })} />
|
||||
<Button
|
||||
icon="clock-o"
|
||||
content="Timed"
|
||||
selected={tracking_state === 1}
|
||||
onClick={() => act(ref, 'tracking', { mode: 1 })} />
|
||||
onClick={() => act('tracking', { mode: 1 })} />
|
||||
<Button
|
||||
icon="sync"
|
||||
content="Auto"
|
||||
selected={tracking_state === 2}
|
||||
disabled={!connected_tracker}
|
||||
onClick={() => act(ref, 'tracking', { mode: 2 })} />
|
||||
onClick={() => act('tracking', { mode: 2 })} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Angle">
|
||||
<LabeledList.Item label="Azimuth">
|
||||
{(tracking_state === 0 || tracking_state === 1) && (
|
||||
<NumberInput
|
||||
width="52px"
|
||||
@@ -87,28 +87,27 @@ export const SolarControl = props => {
|
||||
stepPixelSize={2}
|
||||
minValue={-360}
|
||||
maxValue={+720}
|
||||
value={angle}
|
||||
format={angle => Math.round(360 + angle) % 360}
|
||||
onDrag={(e, value) => act(ref, 'angle', { value })} />
|
||||
value={azimuth_current}
|
||||
onDrag={(e, value) => act('azimuth', { value })} />
|
||||
)}
|
||||
{tracking_state === 1 && (
|
||||
<NumberInput
|
||||
width="80px"
|
||||
unit="°/h"
|
||||
step={5}
|
||||
stepPixelSize={2}
|
||||
minValue={-7200}
|
||||
maxValue={7200}
|
||||
value={tracking_rate}
|
||||
unit="°/m"
|
||||
step={0.01}
|
||||
stepPixelSize={1}
|
||||
minValue={-max_rotation_rate-0.01}
|
||||
maxValue={max_rotation_rate+0.01}
|
||||
value={azimuth_rate}
|
||||
format={rate => {
|
||||
const sign = Math.sign(rate) > 0 ? '+' : '-';
|
||||
return sign + toFixed(Math.abs(rate));
|
||||
return sign + Math.abs(rate);
|
||||
}}
|
||||
onDrag={(e, value) => act(ref, 'rate', { value })} />
|
||||
onDrag={(e, value) => act('azimuth_rate', { value })} />
|
||||
)}
|
||||
{tracking_state === 2 && (
|
||||
<Box inline color="label" mt="3px">
|
||||
{angle + ' °'} (auto)
|
||||
{azimuth_current + ' °'} (auto)
|
||||
</Box>
|
||||
)}
|
||||
</LabeledList.Item>
|
||||
@@ -116,4 +115,4 @@ export const SolarControl = props => {
|
||||
</Section>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
import { Box, Button, Section, Dimmer, Table, Icon, NoticeBox, Tabs, Grid, LabeledList } from "../components";
|
||||
import { useBackend } from "../backend";
|
||||
import { Fragment, Component } from "inferno";
|
||||
|
||||
export class FakeTerminal extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.timer = null;
|
||||
this.state = {
|
||||
currentIndex: 0,
|
||||
currentDisplay: [],
|
||||
};
|
||||
}
|
||||
|
||||
tick() {
|
||||
const { props, state } = this;
|
||||
if (state.currentIndex <= props.allMessages.length) {
|
||||
this.setState(prevState => {
|
||||
return ({
|
||||
currentIndex: prevState.currentIndex + 1,
|
||||
});
|
||||
});
|
||||
const { currentDisplay } = state;
|
||||
currentDisplay.push(props.allMessages[state.currentIndex]);
|
||||
} else {
|
||||
clearTimeout(this.timer);
|
||||
setTimeout(props.onFinished, props.finishedTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
linesPerSecond = 2.5,
|
||||
} = this.props;
|
||||
this.timer = setInterval(() => this.tick(), 1000 / linesPerSecond);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Box m={1}>
|
||||
{this.state.currentDisplay.map(value => (
|
||||
<Fragment key={value}>
|
||||
{value}
|
||||
<br />
|
||||
</Fragment>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const SyndContractor = props => {
|
||||
const { data, act } = useBackend(props);
|
||||
|
||||
const terminalMessages = [
|
||||
"Recording biometric data...",
|
||||
"Analyzing embedded syndicate info...",
|
||||
"STATUS CONFIRMED",
|
||||
"Contacting syndicate database...",
|
||||
"Awaiting response...",
|
||||
"Awaiting response...",
|
||||
"Awaiting response...",
|
||||
"Awaiting response...",
|
||||
"Awaiting response...",
|
||||
"Awaiting response...",
|
||||
"Response received, ack 4851234...",
|
||||
"CONFIRM ACC " + (Math.round(Math.random() * 20000)),
|
||||
"Setting up private accounts...",
|
||||
"CONTRACTOR ACCOUNT CREATED",
|
||||
"Searching for available contracts...",
|
||||
"Searching for available contracts...",
|
||||
"Searching for available contracts...",
|
||||
"Searching for available contracts...",
|
||||
"CONTRACTS FOUND",
|
||||
"WELCOME, AGENT",
|
||||
];
|
||||
|
||||
const infoEntries = [
|
||||
"SyndTract v2.0",
|
||||
"",
|
||||
"We've identified potentional high-value targets that are",
|
||||
"currently assigned to your mission area. They are believed",
|
||||
"to hold valuable information which could be of immediate",
|
||||
"importance to our organisation.",
|
||||
"",
|
||||
"Listed below are all of the contracts available to you. You",
|
||||
"are to bring the specified target to the designated",
|
||||
"drop-off, and contact us via this uplink. We will send",
|
||||
"a specialised extraction unit to put the body into.",
|
||||
"",
|
||||
"We want targets alive - but we will sometimes pay slight",
|
||||
"amounts if they're not, you just won't recieve the shown",
|
||||
"bonus. You can redeem your payment through this uplink in",
|
||||
"the form of raw telecrystals, which can be put into your",
|
||||
"regular Syndicate uplink to purchase whatever you may need.",
|
||||
"We provide you with these crystals the moment you send the",
|
||||
"target up to us, which can be collected at anytime through",
|
||||
"this system.",
|
||||
"",
|
||||
"Targets extracted will be ransomed back to the station once",
|
||||
"their use to us is fulfilled, with us providing you a small",
|
||||
"percentage cut. You may want to be mindful of them",
|
||||
"identifying you when they come back. We provide you with",
|
||||
"a standard contractor loadout, which will help cover your",
|
||||
"identity.",
|
||||
];
|
||||
|
||||
const errorPane = !!data.error && (
|
||||
<Dimmer>
|
||||
<Box
|
||||
backgroundColor="red"
|
||||
minHeight="150px"
|
||||
mt={30}
|
||||
ml={15}
|
||||
mr={15}>
|
||||
<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>
|
||||
);
|
||||
|
||||
if (!data.logged_in) {
|
||||
return (
|
||||
<Section minHeight="525px">
|
||||
<Box
|
||||
width="100%"
|
||||
textAlign="center">
|
||||
<Button
|
||||
content="REGISTER USER"
|
||||
color="transparent"
|
||||
onClick={() => act('PRG_login')} />
|
||||
</Box>
|
||||
{!!data.error && (
|
||||
<NoticeBox>
|
||||
{data.error}
|
||||
</NoticeBox>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.logged_in && data.first_load) {
|
||||
return (
|
||||
<Box
|
||||
backgroundColor="rgba(0, 0, 0, 0.8)"
|
||||
minHeight="525px">
|
||||
<FakeTerminal
|
||||
allMessages={terminalMessages}
|
||||
finishedTimeout={3000}
|
||||
onFinished={() => act('PRG_set_first_load_finished')} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.info_screen) {
|
||||
return (
|
||||
<Fragment>
|
||||
<Box
|
||||
backgroundColor="rgba(0, 0, 0, 0.8)"
|
||||
minHeight="500px">
|
||||
<FakeTerminal
|
||||
allMessages={infoEntries}
|
||||
linesPerSecond={10} />
|
||||
</Box>
|
||||
<Button
|
||||
fluid
|
||||
content="CONTINUE"
|
||||
color="transparent"
|
||||
textAlign="center"
|
||||
onClick={() => act('PRG_toggle_info')} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{errorPane}
|
||||
<SyndPane state={props.state} />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export const StatusPane = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
return (
|
||||
<Section
|
||||
title={(
|
||||
<Fragment>
|
||||
Contractor Status
|
||||
<Button
|
||||
content="View Information Again"
|
||||
color="transparent"
|
||||
mb={0}
|
||||
ml={1}
|
||||
onClick={() => act('PRG_toggle_info')} />
|
||||
</Fragment>
|
||||
)}
|
||||
buttons={(
|
||||
<Box bold mr={1}>
|
||||
{data.contract_rep} Rep
|
||||
</Box>
|
||||
)}>
|
||||
<Grid>
|
||||
<Grid.Column size={0.85}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="TC Availible"
|
||||
buttons={(
|
||||
<Button
|
||||
content="Claim"
|
||||
disabled={data.redeemable_tc <= 0}
|
||||
onClick={() => act('PRG_redeem_TC')} />
|
||||
)}>
|
||||
{data.redeemable_tc}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="TC Earned">
|
||||
{data.earned_tc}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Grid.Column>
|
||||
<Grid.Column>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Contracts Completed">
|
||||
{data.contracts_completed}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Current Status">
|
||||
ACTIVE
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
export const SyndPane = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
const contractor_hub_items = data.contractor_hub_items || [];
|
||||
const contracts = data.contracts || [];
|
||||
|
||||
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 => {
|
||||
const active = (contract.status > 1);
|
||||
if (contract.status >= 5) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<Section
|
||||
key={contract.target}
|
||||
title={`${contract.target} (${contract.target_rank})`}
|
||||
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>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab label="Uplink">
|
||||
<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.redeemable_tc < 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>
|
||||
</Tabs>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, LabeledList, Section } from '../components';
|
||||
|
||||
export const Teleporter = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
const {
|
||||
calibrated,
|
||||
calibrating,
|
||||
power_station,
|
||||
regime_set,
|
||||
teleporter_hub,
|
||||
target,
|
||||
} = data;
|
||||
return (
|
||||
<Section>
|
||||
{!power_station && (
|
||||
<Box color="bad" textAlign="center">
|
||||
No power station linked.
|
||||
</Box>
|
||||
) || (!teleporter_hub && (
|
||||
<Box color="bad" textAlign="center">
|
||||
No hub linked.
|
||||
</Box>
|
||||
)) || (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Current Regime"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="tools"
|
||||
content="Change Regime"
|
||||
onClick={() => act('regimeset')} />
|
||||
)}>
|
||||
{regime_set}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Current Target"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="tools"
|
||||
content={"Set Target"}
|
||||
onClick={() => act('settarget')} />
|
||||
)}>
|
||||
{target}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Calibration"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="tools"
|
||||
content={"Calibrate Hub"}
|
||||
onClick={() => act('calibrate')} />
|
||||
)}>
|
||||
{calibrating && (
|
||||
<Box color="average">
|
||||
In Progress
|
||||
</Box>
|
||||
) || (calibrated && (
|
||||
<Box color="good">
|
||||
Optimal
|
||||
</Box>
|
||||
) || (
|
||||
<Box color="bad">
|
||||
Sub-Optimal
|
||||
</Box>
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -26,27 +26,30 @@ export const Vending = props => {
|
||||
}
|
||||
return (
|
||||
<Fragment>
|
||||
<Section title="User">
|
||||
{data.user && (
|
||||
<Box>
|
||||
{data.onstation && (
|
||||
<Section title="User">
|
||||
{data.user && (
|
||||
<Box>
|
||||
Welcome, <b>{data.user.name}</b>,
|
||||
{' '}
|
||||
<b>{data.user.job || "Unemployed"}</b>!
|
||||
<br />
|
||||
{' '}
|
||||
<b>{data.user.job || "Unemployed"}</b>!
|
||||
<br />
|
||||
Your balance is <b>{data.user.cash} credits</b>.
|
||||
</Box>
|
||||
) || (
|
||||
<Box color="light-gray">
|
||||
</Box>
|
||||
) || (
|
||||
<Box color="light-gray">
|
||||
No registered ID card!<br />
|
||||
Please contact your local HoP!
|
||||
</Box>
|
||||
)}
|
||||
</Section>
|
||||
</Box>
|
||||
)}
|
||||
</Section>
|
||||
)}
|
||||
<Section title="Products" >
|
||||
<Table>
|
||||
{inventory.map((product => {
|
||||
const free = (
|
||||
product.price === 0
|
||||
!data.onstation
|
||||
|| product.price === 0
|
||||
|| (
|
||||
!product.premium
|
||||
&& data.department
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -24,6 +24,8 @@
|
||||
overflow-y: auto;
|
||||
z-index: 5;
|
||||
width: 100px;
|
||||
max-height: 200px;
|
||||
overflow-y: scroll;
|
||||
border-radius: 0 0 2px 2px;
|
||||
background-color: #000;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
|
||||
@@ -41,3 +41,7 @@
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.action_test {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
@use 'sass:color';
|
||||
@use 'sass:meta';
|
||||
|
||||
@use '../colors.scss' with (
|
||||
$primary: #000000,
|
||||
$fg-map-keys: (),
|
||||
$bg-map-keys: (),
|
||||
);
|
||||
@use '../base.scss' with (
|
||||
$color-bg: #117039,
|
||||
$color-bg-grad-spread: 0%,
|
||||
$border-radius: 0px,
|
||||
);
|
||||
|
||||
//Made for the roulette table, probably requires a bunch of manual hacks to work for anything else
|
||||
.theme-cardtable {
|
||||
// Atomic classes
|
||||
@include meta.load-css('../atomic/color.scss');
|
||||
|
||||
// Components
|
||||
@include meta.load-css('../components/Button.scss', $with: (
|
||||
'color-default': #117039,
|
||||
'color-disabled': #363636,
|
||||
'color-selected': #9d0808,
|
||||
'color-caution': #be6209,
|
||||
'color-danger': #9a9d00,
|
||||
));
|
||||
@include meta.load-css('../components/Layout.scss');
|
||||
@include meta.load-css('../components/NumberInput.scss', $with: (
|
||||
'border-color': #FFFFFF,
|
||||
));
|
||||
|
||||
@include meta.load-css('../components/ProgressBar.scss', $with: (
|
||||
'color-background': rgba(0, 0, 0, 0.5),
|
||||
));
|
||||
@include meta.load-css('../components/Section.scss');
|
||||
@include meta.load-css('../components/TitleBar.scss', $with: (
|
||||
'color-background': #381608,
|
||||
));
|
||||
|
||||
.Button {
|
||||
border-color: #FFFFFF;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
@use 'sass:color';
|
||||
@use 'sass:meta';
|
||||
|
||||
@use '../colors.scss' with (
|
||||
$primary: #00ff00,
|
||||
$fg-map-keys: (),
|
||||
$bg-map-keys: (),
|
||||
);
|
||||
@use '../base.scss' with (
|
||||
$color-bg: #121b12,
|
||||
$color-bg-grad-spread: 0%,
|
||||
$border-radius: 2px,
|
||||
);
|
||||
|
||||
.theme-hackerman {
|
||||
// Atomic classes
|
||||
@include meta.load-css('../atomic/color.scss');
|
||||
|
||||
// Components
|
||||
@include meta.load-css('../components/Button.scss', $with: (
|
||||
'color-default': colors.$primary,
|
||||
'color-disabled': #4A6A4A,
|
||||
'color-selected': #00FF00,
|
||||
));
|
||||
@include meta.load-css('../components/Input.scss', $with: (
|
||||
'border-color': colors.$primary,
|
||||
));
|
||||
@include meta.load-css('../components/Layout.scss');
|
||||
@include meta.load-css('../components/Section.scss');
|
||||
@include meta.load-css('../components/TitleBar.scss', $with: (
|
||||
'color-background': #223d22,
|
||||
));
|
||||
|
||||
.Layout__content {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.Button {
|
||||
font-family: monospace;
|
||||
border-width: 2px;
|
||||
border-style: outset;
|
||||
border-color: #00AA00;
|
||||
outline: 1px solid rgb(0, 122, 0);
|
||||
}
|
||||
|
||||
.candystripe:nth-child(odd) {
|
||||
background-color: rgba(0, 100, 0, 0.50);
|
||||
}
|
||||
}
|
||||
@@ -25,13 +25,16 @@
|
||||
'color-danger': #9a9d00,
|
||||
));
|
||||
@include meta.load-css('../components/Input.scss', $with: (
|
||||
'border-color': colors.$primary,
|
||||
'border-color': #87ce87,
|
||||
));
|
||||
@include meta.load-css('../components/Layout.scss');
|
||||
@include meta.load-css('../components/NoticeBox.scss', $with: (
|
||||
'color-first': #750000,
|
||||
'color-second': #910101,
|
||||
));
|
||||
@include meta.load-css('../components/NumberInput.scss', $with: (
|
||||
'border-color': #87ce87,
|
||||
));
|
||||
@include meta.load-css('../components/ProgressBar.scss', $with: (
|
||||
'color-background': rgba(0, 0, 0, 0.5),
|
||||
));
|
||||
|
||||
@@ -20,9 +20,11 @@ module.exports = (env = {}, argv) => {
|
||||
entry: {
|
||||
tgui: [
|
||||
path.resolve(__dirname, './styles/main.scss'),
|
||||
path.resolve(__dirname, './styles/themes/cardtable.scss'),
|
||||
path.resolve(__dirname, './styles/themes/ntos.scss'),
|
||||
path.resolve(__dirname, './styles/themes/syndicate.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'),
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user