T-G-U-I
This commit is contained in:
+5
-5
@@ -684,11 +684,11 @@ rules:
|
||||
## expressions.
|
||||
# react/jsx-child-element-spacing: error
|
||||
## Validate closing bracket location in JSX (fixable)
|
||||
react/jsx-closing-bracket-location: [error, {
|
||||
## NOTE: Not really sure about enforcing this one
|
||||
selfClosing: false,
|
||||
nonEmpty: after-props,
|
||||
}]
|
||||
# react/jsx-closing-bracket-location: [error, {
|
||||
# ## NOTE: Not really sure about enforcing this one
|
||||
# selfClosing: false,
|
||||
# nonEmpty: after-props,
|
||||
# }]
|
||||
## Validate closing tag location in JSX (fixable)
|
||||
react/jsx-closing-tag-location: error
|
||||
## Enforce or disallow newlines inside of curly braces in JSX attributes and
|
||||
|
||||
@@ -18,3 +18,4 @@ bin/tgui text eol=lf
|
||||
## Treat bundles as binary and ignore them during conflicts
|
||||
*.bundle.* binary merge=tgui-merge-bundle
|
||||
*.chunk.* binary merge=tgui-merge-bundle
|
||||
.yarn/releases/* binary
|
||||
|
||||
Vendored
+55
File diff suppressed because one or more lines are too long
Vendored
-86
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
||||
yarnPath: ".yarn/releases/yarn-berry.js"
|
||||
yarnPath: .yarn/releases/yarn-2.3.3.cjs
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ to translate concepts between old and new tgui, read this
|
||||
You will need these programs to start developing in tgui:
|
||||
|
||||
- [Node v12.18.3+](https://nodejs.org/en/download/)
|
||||
- [Yarn v1.22.4+](https://yarnpkg.com/en/docs/install) (optional)
|
||||
- [Yarn v1.22.4+](https://yarnpkg.com/getting-started/install) (optional)
|
||||
- [Git Bash](https://git-scm.com/downloads)
|
||||
or [MSys2](https://www.msys2.org/) (optional)
|
||||
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
## Copyright (c) 2020 Aleksej Komarov
|
||||
## SPDX-License-Identifier: MIT
|
||||
|
||||
@@ -28,7 +28,7 @@ base_dir="$(tgui-realpath "${base_dir}")"
|
||||
|
||||
## Fall back to running Yarn from the repo
|
||||
if ! hash yarn 2>/dev/null; then
|
||||
alias yarn="node '${base_dir}/.yarn/releases/yarn-berry.js'"
|
||||
alias yarn="node '${base_dir}/.yarn/releases/yarn-2.3.3.cjs'"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ Set-Location $basedir
|
||||
## --------------------------------------------------------
|
||||
|
||||
function yarn {
|
||||
node.exe ".yarn\releases\yarn-berry.js" @Args
|
||||
node.exe ".yarn\releases\yarn-2.3.3.cjs" @Args
|
||||
}
|
||||
|
||||
function Remove-Quiet {
|
||||
|
||||
@@ -28,6 +28,7 @@ Make sure to add new items to this list if you document new components.
|
||||
- [`Grid`](#grid)
|
||||
- [`Grid.Column`](#gridcolumn)
|
||||
- [`Icon`](#icon)
|
||||
- [`Icon.Stack`](#iconstack)
|
||||
- [`Input`](#input)
|
||||
- [`Knob`](#knob)
|
||||
- [`LabeledControls`](#labeledcontrols)
|
||||
@@ -39,6 +40,7 @@ Make sure to add new items to this list if you document new components.
|
||||
- [`NoticeBox`](#noticebox)
|
||||
- [`NumberInput`](#numberinput)
|
||||
- [`ProgressBar`](#progressbar)
|
||||
- [`RoundGauge`](#roundgauge)
|
||||
- [`Section`](#section)
|
||||
- [`Slider`](#slider)
|
||||
- [`Table`](#table)
|
||||
@@ -246,7 +248,7 @@ A button with an extra confirmation step, using native button component.
|
||||
**Props:**
|
||||
|
||||
- See inherited props: [Button](#button)
|
||||
- `confirmMessage: string` - Text to display after first click; defaults to "Confirm?"
|
||||
- `confirmContent: string` - Text to display after first click; defaults to "Confirm?"
|
||||
- `confirmColor: string` - Color to display after first click; defaults to "bad"
|
||||
|
||||
### `Button.Input`
|
||||
@@ -359,12 +361,16 @@ and displays selected entry.
|
||||
**Props:**
|
||||
|
||||
- See inherited props: [Box](#box)
|
||||
- See inherited props: [Icon](#icon)
|
||||
- `options: string[]` - An array of strings which will be displayed in the
|
||||
dropdown when open
|
||||
- `selected: string` - Currently selected entry
|
||||
- `width: number` - Width of dropdown button and resulting menu
|
||||
- `over: boolean` - dropdown renders over instead of below
|
||||
- `color: string` - color of dropdown button
|
||||
- `over: boolean` - Dropdown renders over instead of below
|
||||
- `color: string` - Color of dropdown button
|
||||
- `nochevron: boolean` - Whether or not the arrow on the right hand side of the dropdown button is visible
|
||||
- `noscroll: boolean` - Whether or not the dropdown menu should have a scroll bar
|
||||
- `displayText: string` - Text to always display in place of the selected text
|
||||
- `onClick: (e) => void` - Called when dropdown button is clicked
|
||||
- `onSelected: (value) => void` - Called when a value is picked from the list, `value` is the value that was picked
|
||||
|
||||
@@ -529,6 +535,22 @@ Fractional numbers are supported.
|
||||
- `spin: boolean` - Whether an icon should be spinning. Good for load
|
||||
indicators.
|
||||
|
||||
### `Icon.Stack`
|
||||
|
||||
Renders children icons on top of each other in order to make your own icon.
|
||||
|
||||
```jsx
|
||||
<Icon.Stack>
|
||||
<Icon name="pen" />
|
||||
<Icon name="slash" />
|
||||
</Icon.Stack>
|
||||
```
|
||||
|
||||
**Props:**
|
||||
|
||||
- See inherited props: [Box](#box)
|
||||
- `children: Icon` - Icons to stack.
|
||||
|
||||
### `Input`
|
||||
|
||||
A basic text input, which allow users to enter text into a UI.
|
||||
@@ -754,6 +776,38 @@ based on whether the value lands in the range between `from` and `to`.
|
||||
- `color: string` - Color of the progress bar.
|
||||
- `children: any` - Content to render inside the progress bar.
|
||||
|
||||
### `RoundGauge`
|
||||
|
||||
The RoundGauge component provides a visual representation of a single metric, as well as being capable of showing informational or cautionary boundaries related to that metric.
|
||||
|
||||
```jsx
|
||||
<RoundGauge
|
||||
size={1.75}
|
||||
value={tankPressure}
|
||||
minValue={0}
|
||||
maxValue={pressureLimit}
|
||||
alertAfter={pressureLimit * 0.70}
|
||||
ranges={{
|
||||
"good": [0, pressureLimit * 0.70],
|
||||
"average": [pressureLimit * 0.70, pressureLimit * 0.85],
|
||||
"bad": [pressureLimit * 0.85, pressureLimit],
|
||||
}}
|
||||
format={formatPressure} />
|
||||
```
|
||||
|
||||
The alert on the gauge is optional, and will only be shown if the `alertAfter` prop is defined. When defined, the alert will begin to flash the respective color upon which the needle currently rests, as defined in the `ranges` prop.
|
||||
|
||||
**Props:**
|
||||
|
||||
- See inherited props: [Box](#box)
|
||||
- `value: number` - The current value of the metric.
|
||||
- `minValue: number` (default: 0) - The lower bound of the guage.
|
||||
- `maxValue: number` (default: 1) - The upper bound of the guage.
|
||||
- `ranges: { color: [from, to] }` (default: `{ "good": [0, 1] }`) - Provide regions of the guage to color between two specified values of the metric.
|
||||
- `alertAfter: number` (optional) - When provided, will cause an alert symbol on the gauge to begin flashing in the color upon which the needle currently rest, as defined in `ranges`.
|
||||
- `format: function(value) => string` (optional) - When provided, will be used to format the value of the metric for display.
|
||||
- `size: number` (default: 1) - When provided scales the gauge.
|
||||
|
||||
### `Section`
|
||||
|
||||
Section is a surface that displays content and actions on a single topic.
|
||||
@@ -993,6 +1047,7 @@ Example:
|
||||
- For a list of themes, see `packages/tgui/styles/themes`.
|
||||
- `title: string` - Window title.
|
||||
- `resizable: boolean` - Controls resizability of the window.
|
||||
- `noClose: boolean` - Controls the ability to close the window.
|
||||
- `children: any` - Child elements, which are rendered directly inside the
|
||||
window. If you use a [Dimmer](#dimmer) or [Modal](#modal) in your UI,
|
||||
they should be put as direct childs of a Window, otherwise you should be
|
||||
|
||||
@@ -76,7 +76,8 @@ input. The input's `action` and `params` are passed to the proc.
|
||||
|
||||
```dm
|
||||
/obj/machinery/my_machine/ui_act(action, params)
|
||||
if(..())
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
if(action == "change_color")
|
||||
var/new_color = params["color"]
|
||||
@@ -88,13 +89,20 @@ input. The input's `action` and `params` are passed to the proc.
|
||||
```
|
||||
|
||||
The `..()` (parent call) is very important here, as it is how we check that the
|
||||
user is allowed to use this interface (to avoid so-called href exploits). It is
|
||||
also very important to clamp and sanitize all input here. Always assume the user
|
||||
is attempting to exploit the game.
|
||||
user is allowed to use this interface (to avoid so-called href exploits). When
|
||||
any event has been handled `..()` will return `TRUE`. It is important to clamp
|
||||
and sanitize all input here. Always assume the user is attempting to exploit the
|
||||
game.
|
||||
|
||||
When `..()` has returned `TRUE` your interface can safely assume that the user's
|
||||
action has been handled already by some parent proc and you should not continue
|
||||
to handle this, instead preserving and returning the parent proc's return value.
|
||||
|
||||
Also note the use of `. = TRUE` (or `FALSE`), which is used to notify the UI
|
||||
that this input caused an update. This is especially important for UIs that do
|
||||
not auto-update, as otherwise the user will never see their change.
|
||||
that this input has been handled. When `ui_act` eventually returns, a value of
|
||||
`TRUE` indicates that the input has been handled and that the UI should update.
|
||||
This is important for UIs that do not auto-update, as otherwise the user will
|
||||
not be able to see the interface update based on thier actions.
|
||||
|
||||
### Frontend
|
||||
|
||||
|
||||
+13
-14
@@ -6,31 +6,30 @@
|
||||
"packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.6.2",
|
||||
"@babel/plugin-transform-jscript": "^7.2.0",
|
||||
"@babel/preset-env": "^7.6.2",
|
||||
"@babel/core": "^7.11.1",
|
||||
"@babel/plugin-transform-jscript": "^7.10.4",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"babel-loader": "^8.0.6",
|
||||
"babel-plugin-inferno": "^6.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-inferno": "^6.1.1",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"common": "workspace:*",
|
||||
"css-loader": "^3.2.0",
|
||||
"css-loader": "^4.2.1",
|
||||
"cssnano": "^4.1.10",
|
||||
"eslint": "^7.4.0",
|
||||
"eslint-plugin-react": "^7.17.0",
|
||||
"extract-css-chunks-webpack-plugin": "^4.6.0",
|
||||
"extract-css-chunks-webpack-plugin": "^4.7.5",
|
||||
"file-loader": "^6.0.0",
|
||||
"inferno": "^7.4.2",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"pnp-webpack-plugin": "^1.6.4",
|
||||
"sass": "^1.22.12",
|
||||
"sass-loader": "^9.0.2",
|
||||
"style-loader": "^1.0.0",
|
||||
"terser-webpack-plugin": "^3.0.6",
|
||||
"sass": "^1.26.10",
|
||||
"sass-loader": "^9.0.3",
|
||||
"style-loader": "^1.2.1",
|
||||
"terser-webpack-plugin": "^4.1.0",
|
||||
"url-loader": "^4.1.0",
|
||||
"webpack": "^4.40.2",
|
||||
"webpack-build-notifier": "^2.0.0",
|
||||
"webpack-bundle-analyzer": "^3.5.1",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-bundle-analyzer": "^3.8.0",
|
||||
"webpack-cli": "^3.3.12"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export const toKeyedArray = (obj, keyProp = 'key') => {
|
||||
* @returns {any[]}
|
||||
*/
|
||||
export const filter = iterateeFn => collection => {
|
||||
if (collection === null && collection === undefined) {
|
||||
if (collection === null || collection === undefined) {
|
||||
return collection;
|
||||
}
|
||||
if (Array.isArray(collection)) {
|
||||
@@ -100,7 +100,7 @@ export const filter = iterateeFn => collection => {
|
||||
* @returns {any[]}
|
||||
*/
|
||||
export const map = iterateeFn => collection => {
|
||||
if (collection === null && collection === undefined) {
|
||||
if (collection === null || collection === undefined) {
|
||||
return collection;
|
||||
}
|
||||
if (Array.isArray(collection)) {
|
||||
|
||||
@@ -88,3 +88,13 @@ export const keyOfMatchingRange = (value, ranges) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get number of digits following the decimal point in a number
|
||||
*/
|
||||
export const numberOfDecimalDigits = value => {
|
||||
if (Math.floor(value) !== value) {
|
||||
return value.toString().split('.')[1].length || 0;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
"name": "tgui-dev-server",
|
||||
"version": "4.2.0",
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"axios": "^0.19.2",
|
||||
"common": "workspace:*",
|
||||
"esm": "^3.2.25",
|
||||
"glob": "^7.1.4",
|
||||
"glob": "^7.1.6",
|
||||
"source-map": "^0.7.3",
|
||||
"stacktrace-parser": "^0.1.7",
|
||||
"webpack": "^4.40.2",
|
||||
"ws": "^7.1.2"
|
||||
"stacktrace-parser": "^0.1.10",
|
||||
"webpack": "^4.44.1",
|
||||
"ws": "^7.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export const Panel = (props, context) => {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Pane theme={settings.theme === 'default' ? 'light' : settings.theme}>
|
||||
<Pane theme={settings.theme}>
|
||||
<Flex
|
||||
direction="column"
|
||||
height="100%">
|
||||
@@ -116,7 +116,7 @@ export const Panel = (props, context) => {
|
||||
const HoboPanel = (props, context) => {
|
||||
const settings = useSettings(context);
|
||||
return (
|
||||
<Pane theme={settings.theme === 'default' ? 'light' : settings.theme}>
|
||||
<Pane theme={settings.theme}>
|
||||
<Pane.Content scrollable>
|
||||
<Button
|
||||
style={{
|
||||
|
||||
@@ -7,12 +7,11 @@
|
||||
import { storage } from 'common/storage';
|
||||
import { loadSettings, updateSettings } from '../settings/actions';
|
||||
import { selectSettings } from '../settings/selectors';
|
||||
import { addChatPage, changeChatPage, changeScrollTracking, loadChat, rebuildChat, toggleAcceptedType, updateMessageCount, removeChatPage, saveChatToDisk } from './actions';
|
||||
import { addChatPage, changeChatPage, changeScrollTracking, loadChat, rebuildChat, removeChatPage, saveChatToDisk, toggleAcceptedType, updateMessageCount } from './actions';
|
||||
import { MAX_PERSISTED_MESSAGES, MESSAGE_SAVE_INTERVAL } from './constants';
|
||||
import { createMessage, serializeMessage } from './model';
|
||||
import { chatRenderer } from './renderer';
|
||||
import { selectChat, selectCurrentChatPage } from './selectors';
|
||||
import { logger } from 'tgui/logging';
|
||||
|
||||
const saveChatToStorage = async store => {
|
||||
const state = selectChat(store.getState());
|
||||
|
||||
@@ -313,7 +313,10 @@ class ChatRenderer {
|
||||
}
|
||||
}
|
||||
// Linkify text
|
||||
linkifyNode(node);
|
||||
const linkifyNodes = node.querySelectorAll('.linkify');
|
||||
for (let i = 0; i < linkifyNodes.length; ++i) {
|
||||
linkifyNode(linkifyNodes[i]);
|
||||
}
|
||||
// Assign an image error handler
|
||||
if (now < message.createdAt + IMAGE_RETRY_MESSAGE_AGE) {
|
||||
const imgNodes = node.querySelectorAll('img');
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
em {
|
||||
em {
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -255,7 +255,7 @@ a.popt {
|
||||
/* ADD HERE FOR ITALIC */
|
||||
|
||||
|
||||
.italic, .italics {
|
||||
.italic, .italics, .emote {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -418,7 +418,7 @@ em {
|
||||
}
|
||||
|
||||
.emote {
|
||||
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.userdanger {
|
||||
@@ -475,12 +475,22 @@ em {
|
||||
}
|
||||
|
||||
.tinynotice {
|
||||
color: #6685f5;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.tinynoticeital {
|
||||
color: #6685f5;
|
||||
font-style: italic;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.smallnotice {
|
||||
color: #6685f5;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.smallnoticeital {
|
||||
color: #6685f5;
|
||||
font-style: italic;
|
||||
font-size: 90%;
|
||||
@@ -545,40 +555,40 @@ em {
|
||||
}
|
||||
|
||||
.cult {
|
||||
color: #aa1c1c;
|
||||
color: #973e3b;
|
||||
}
|
||||
|
||||
.cultitalic {
|
||||
color: #aa1c1c;
|
||||
color: #973e3b;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.cultbold {
|
||||
color: #aa1c1c;
|
||||
color: #973e3b;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cultboldtalic {
|
||||
color: #aa1c1c;
|
||||
color: #973e3b;
|
||||
font-weight: bold;
|
||||
font-size: 185%;
|
||||
}
|
||||
|
||||
.cultlarge {
|
||||
color: #aa1c1c;
|
||||
color: #973e3b;
|
||||
font-weight: bold;
|
||||
font-size: 185%;
|
||||
}
|
||||
|
||||
.narsie {
|
||||
color: #aa1c1c;
|
||||
color: #973e3b;
|
||||
font-weight: bold;
|
||||
font-size: 925%;
|
||||
}
|
||||
|
||||
.narsiesmall {
|
||||
color: #aa1c1c;
|
||||
color: #973e3b;
|
||||
font-weight: bold;
|
||||
font-size: 370%;
|
||||
}
|
||||
@@ -655,7 +665,7 @@ em {
|
||||
}
|
||||
|
||||
.deconversion_message {
|
||||
color: #5000A0;
|
||||
color: #a947ff;
|
||||
font-size: 185%;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -273,7 +273,8 @@ a.popt {
|
||||
|
||||
|
||||
.italic,
|
||||
.italics {
|
||||
.italics,
|
||||
.emote {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -442,6 +443,10 @@ h1.alert, h2.alert {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.emote {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.userdanger {
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
@@ -511,6 +516,11 @@ h1.alert, h2.alert {
|
||||
}
|
||||
|
||||
.tinynotice {
|
||||
color: #000099;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.tinynoticeital {
|
||||
color: #000099;
|
||||
font-style: italic;
|
||||
font-size: 85%;
|
||||
@@ -586,40 +596,40 @@ h1.alert, h2.alert {
|
||||
}
|
||||
|
||||
.cult {
|
||||
color: #960000;
|
||||
color: #973e3b;
|
||||
}
|
||||
|
||||
.cultitalic {
|
||||
color: #960000;
|
||||
color: #973e3b;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.cultbold {
|
||||
color: #960000;
|
||||
color: #973e3b;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cultboldtalic {
|
||||
color: #960000;
|
||||
color: #973e3b;
|
||||
font-weight: bold;
|
||||
font-size: 185%;
|
||||
}
|
||||
|
||||
.cultlarge {
|
||||
color: #960000;
|
||||
color: #973e3b;
|
||||
font-weight: bold;
|
||||
font-size: 185%;
|
||||
}
|
||||
|
||||
.narsie {
|
||||
color: #960000;
|
||||
color: #973e3b;
|
||||
font-weight: bold;
|
||||
font-size: 925%;
|
||||
}
|
||||
|
||||
.narsiesmall {
|
||||
color: #960000;
|
||||
color: #973e3b;
|
||||
font-weight: bold;
|
||||
font-size: 370%;
|
||||
}
|
||||
@@ -678,6 +688,7 @@ h1.alert, h2.alert {
|
||||
font-size: 185%;
|
||||
}
|
||||
|
||||
/* compatability */
|
||||
.umbra {
|
||||
color: #5000A0;
|
||||
}
|
||||
@@ -881,7 +892,7 @@ h1.alert, h2.alert {
|
||||
|
||||
.command_headset {
|
||||
font-weight: bold;
|
||||
font-size: 125%;
|
||||
font-size: 125%; /* 160% default, changed so command can scream less */
|
||||
}
|
||||
|
||||
.small {
|
||||
@@ -917,7 +928,7 @@ h1.alert, h2.alert {
|
||||
|
||||
.clown {
|
||||
color: #FF69Bf;
|
||||
font-size: 125%;
|
||||
font-size: 125%; /* 160% default, changed so "command" can scream less */
|
||||
font-family: "Comic Sans MS", cursive, sans-serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -933,7 +944,7 @@ h1.alert, h2.alert {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.spooky {
|
||||
.spooky { /* cit specific, used by jacq */
|
||||
color: #FF9100;
|
||||
}
|
||||
|
||||
@@ -966,7 +977,7 @@ h1.alert, h2.alert {
|
||||
}
|
||||
}
|
||||
|
||||
.velvet {
|
||||
.velvet { /* cit specific, used by MKU */
|
||||
color: #660015;
|
||||
font-weight: bold;
|
||||
animation: velvet 5000ms infinite;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Component } from 'inferno';
|
||||
|
||||
const DEFAULT_BLINKING_INTERVAL = 1000;
|
||||
const DEFAULT_BLINKING_TIME = 1000;
|
||||
|
||||
export class Blink extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
hidden: false,
|
||||
};
|
||||
}
|
||||
|
||||
createTimer() {
|
||||
const {
|
||||
interval = DEFAULT_BLINKING_INTERVAL,
|
||||
time = DEFAULT_BLINKING_TIME,
|
||||
} = this.props;
|
||||
|
||||
clearInterval(this.interval);
|
||||
clearTimeout(this.timer);
|
||||
|
||||
this.setState({
|
||||
hidden: false,
|
||||
});
|
||||
|
||||
this.interval = setInterval(() => {
|
||||
this.setState({
|
||||
hidden: true,
|
||||
});
|
||||
|
||||
this.timer = setTimeout(() => {
|
||||
this.setState({
|
||||
hidden: false,
|
||||
});
|
||||
}, time);
|
||||
}, interval + time);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.createTimer();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.interval !== this.props.interval
|
||||
|| prevProps.time !== this.props.time) {
|
||||
this.createTimer();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.interval);
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
|
||||
render(props) {
|
||||
return (
|
||||
<span style={{
|
||||
visibility: this.state.hidden ? "hidden" : "visible",
|
||||
}}>
|
||||
{props.children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from 'common/keycodes';
|
||||
import { classes, pureComponentHooks } from 'common/react';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from '../hotkeys';
|
||||
import { createLogger } from '../logging';
|
||||
import { Box } from './Box';
|
||||
import { Icon } from './Icon';
|
||||
@@ -21,11 +21,14 @@ export const Button = props => {
|
||||
icon,
|
||||
iconRotation,
|
||||
iconSpin,
|
||||
iconColor,
|
||||
iconPosition,
|
||||
color,
|
||||
disabled,
|
||||
selected,
|
||||
tooltip,
|
||||
tooltipPosition,
|
||||
tooltipOverrideLong,
|
||||
ellipsis,
|
||||
compact,
|
||||
circular,
|
||||
@@ -57,6 +60,7 @@ export const Button = props => {
|
||||
ellipsis && 'Button--ellipsis',
|
||||
circular && 'Button--circular',
|
||||
compact && 'Button--compact',
|
||||
iconPosition && 'Button--iconPosition--' + iconPosition,
|
||||
(color && typeof color === 'string')
|
||||
? 'Button--color--' + color
|
||||
: 'Button--color--default',
|
||||
@@ -86,17 +90,26 @@ export const Button = props => {
|
||||
}
|
||||
}}
|
||||
{...rest}>
|
||||
{icon && (
|
||||
{(icon && iconPosition !== 'right') && (
|
||||
<Icon
|
||||
name={icon}
|
||||
color={iconColor}
|
||||
rotation={iconRotation}
|
||||
spin={iconSpin} />
|
||||
)}
|
||||
{content}
|
||||
{children}
|
||||
{(icon && iconPosition === 'right') && (
|
||||
<Icon
|
||||
name={icon}
|
||||
color={iconColor}
|
||||
rotation={iconRotation}
|
||||
spin={iconSpin} />
|
||||
)}
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
content={tooltip}
|
||||
overrideLong={tooltipOverrideLong}
|
||||
position={tooltipPosition} />
|
||||
)}
|
||||
</Box>
|
||||
@@ -221,6 +234,7 @@ export class ButtonInput extends Component {
|
||||
iconSpin,
|
||||
tooltip,
|
||||
tooltipPosition,
|
||||
tooltipOverrideLong,
|
||||
color = 'default',
|
||||
placeholder,
|
||||
maxLength,
|
||||
@@ -270,6 +284,7 @@ export class ButtonInput extends Component {
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
content={tooltip}
|
||||
overrideLong={tooltipOverrideLong}
|
||||
position={tooltipPosition}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -173,6 +173,7 @@ export class DraggableControl extends Component {
|
||||
unit,
|
||||
minValue,
|
||||
maxValue,
|
||||
unclamped,
|
||||
format,
|
||||
onChange,
|
||||
onDrag,
|
||||
@@ -221,10 +222,16 @@ export class DraggableControl extends Component {
|
||||
if (!editing) {
|
||||
return;
|
||||
}
|
||||
const value = clamp(
|
||||
parseFloat(e.target.value),
|
||||
minValue,
|
||||
maxValue);
|
||||
let value;
|
||||
if (unclamped) {
|
||||
value = parseFloat(e.target.value);
|
||||
}
|
||||
else {
|
||||
value = clamp(
|
||||
parseFloat(e.target.value),
|
||||
minValue,
|
||||
maxValue);
|
||||
}
|
||||
if (Number.isNaN(value)) {
|
||||
this.setState({
|
||||
editing: false,
|
||||
@@ -245,10 +252,16 @@ export class DraggableControl extends Component {
|
||||
}}
|
||||
onKeyDown={e => {
|
||||
if (e.keyCode === 13) {
|
||||
const value = clamp(
|
||||
parseFloat(e.target.value),
|
||||
minValue,
|
||||
maxValue);
|
||||
let value;
|
||||
if (unclamped) {
|
||||
value = parseFloat(e.target.value);
|
||||
}
|
||||
else {
|
||||
value = clamp(
|
||||
parseFloat(e.target.value),
|
||||
minValue,
|
||||
maxValue);
|
||||
}
|
||||
if (Number.isNaN(value)) {
|
||||
this.setState({
|
||||
editing: false,
|
||||
|
||||
@@ -64,6 +64,9 @@ export class Dropdown extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
const {
|
||||
icon,
|
||||
iconRotation,
|
||||
iconSpin,
|
||||
color = 'default',
|
||||
over,
|
||||
noscroll,
|
||||
@@ -72,6 +75,7 @@ export class Dropdown extends Component {
|
||||
onClick,
|
||||
selected,
|
||||
disabled,
|
||||
displayText,
|
||||
...boxProps
|
||||
} = props;
|
||||
const {
|
||||
@@ -114,8 +118,15 @@ export class Dropdown extends Component {
|
||||
}
|
||||
this.setOpen(!this.state.open);
|
||||
}}>
|
||||
{icon && (
|
||||
<Icon
|
||||
name={icon}
|
||||
rotation={iconRotation}
|
||||
spin={iconSpin}
|
||||
mr={1} />
|
||||
)}
|
||||
<span className="Dropdown__selected-text">
|
||||
{this.state.selected}
|
||||
{displayText ? displayText : this.state.selected}
|
||||
</span>
|
||||
{!!nochevron || (
|
||||
<span className="Dropdown__arrow-button">
|
||||
|
||||
@@ -33,7 +33,7 @@ export const computeFlexProps = props => {
|
||||
style: {
|
||||
...rest.style,
|
||||
'flex-direction': direction,
|
||||
'flex-wrap': wrap,
|
||||
'flex-wrap': wrap === true ? 'wrap' : wrap,
|
||||
'align-items': align,
|
||||
'justify-content': justify,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @author Original Aleksej Komarov
|
||||
* @author Changes ThePotato97
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
@@ -10,7 +12,16 @@ import { Box } from './Box';
|
||||
const FA_OUTLINE_REGEX = /-o$/;
|
||||
|
||||
export const Icon = props => {
|
||||
const { name, size, spin, className, style = {}, rotation, ...rest } = props;
|
||||
const {
|
||||
name,
|
||||
size,
|
||||
spin,
|
||||
className,
|
||||
style = {},
|
||||
rotation,
|
||||
inverse,
|
||||
...rest
|
||||
} = props;
|
||||
if (size) {
|
||||
style['font-size'] = (size * 100) + '%';
|
||||
}
|
||||
@@ -23,6 +34,7 @@ export const Icon = props => {
|
||||
<Box
|
||||
as="i"
|
||||
className={classes([
|
||||
'Icon',
|
||||
className,
|
||||
faRegular ? 'far' : 'fas',
|
||||
'fa-' + faName,
|
||||
@@ -34,3 +46,26 @@ export const Icon = props => {
|
||||
};
|
||||
|
||||
Icon.defaultHooks = pureComponentHooks;
|
||||
|
||||
export const IconStack = props => {
|
||||
const {
|
||||
className,
|
||||
style = {},
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<Box
|
||||
as="span"
|
||||
class={classes([
|
||||
'IconStack',
|
||||
className,
|
||||
])}
|
||||
style={style}
|
||||
{...rest}>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Icon.Stack = IconStack;
|
||||
|
||||
@@ -24,6 +24,7 @@ export const Knob = props => {
|
||||
format,
|
||||
maxValue,
|
||||
minValue,
|
||||
unclamped,
|
||||
onChange,
|
||||
onDrag,
|
||||
step,
|
||||
@@ -50,6 +51,7 @@ export const Knob = props => {
|
||||
format,
|
||||
maxValue,
|
||||
minValue,
|
||||
unclamped,
|
||||
onChange,
|
||||
onDrag,
|
||||
step,
|
||||
@@ -79,7 +81,7 @@ export const Knob = props => {
|
||||
const effectiveColor = color
|
||||
|| keyOfMatchingRange(fillValue ?? value, ranges)
|
||||
|| 'default';
|
||||
const rotation = (scaledDisplayValue - 0.5) * 270;
|
||||
const rotation = Math.min((scaledDisplayValue - 0.5) * 270, 225);
|
||||
return (
|
||||
<div
|
||||
className={classes([
|
||||
@@ -127,8 +129,8 @@ export const Knob = props => {
|
||||
className="Knob__ringFill"
|
||||
style={{
|
||||
'stroke-dashoffset': (
|
||||
((bipolar ? 2.75 : 2.00) - scaledFillValue * 1.5)
|
||||
* Math.PI * 50
|
||||
Math.max(((bipolar ? 2.75 : 2.00) - scaledFillValue * 1.5)
|
||||
* Math.PI * 50, 0)
|
||||
),
|
||||
}}
|
||||
cx="50"
|
||||
|
||||
@@ -9,11 +9,13 @@ import { Flex } from './Flex';
|
||||
export const LabeledControls = props => {
|
||||
const {
|
||||
children,
|
||||
wrap,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<Flex
|
||||
mx={-0.5}
|
||||
wrap={wrap}
|
||||
align="stretch"
|
||||
justify="space-between"
|
||||
{...rest}>
|
||||
@@ -26,12 +28,12 @@ const LabeledControlsItem = props => {
|
||||
const {
|
||||
label,
|
||||
children,
|
||||
mx = 1,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<Flex.Item mx={1}>
|
||||
<Flex.Item mx={mx}>
|
||||
<Flex
|
||||
minWidth="52px"
|
||||
height="100%"
|
||||
direction="column"
|
||||
align="center"
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 bobbahbrown (https://github.com/bobbahbrown)
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { clamp01, keyOfMatchingRange, scale } from 'common/math';
|
||||
import { classes } from 'common/react';
|
||||
import { AnimatedNumber } from './AnimatedNumber';
|
||||
import { Box, computeBoxClassName, computeBoxProps } from './Box';
|
||||
|
||||
export const RoundGauge = props => {
|
||||
// Support for IE8 is for losers sorry B)
|
||||
if (Byond.IS_LTE_IE8) {
|
||||
return (
|
||||
<AnimatedNumber {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
value,
|
||||
minValue = 1,
|
||||
maxValue = 1,
|
||||
ranges,
|
||||
alertAfter,
|
||||
format,
|
||||
size = 1,
|
||||
className,
|
||||
style,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const scaledValue = scale(
|
||||
value,
|
||||
minValue,
|
||||
maxValue);
|
||||
const clampedValue = clamp01(scaledValue);
|
||||
let scaledRanges = ranges ? {} : { "primary": [0, 1] };
|
||||
if (ranges)
|
||||
{ Object.keys(ranges).forEach(x => {
|
||||
const range = ranges[x];
|
||||
scaledRanges[x] = [
|
||||
scale(range[0], minValue, maxValue),
|
||||
scale(range[1], minValue, maxValue),
|
||||
];
|
||||
}); }
|
||||
|
||||
let alertColor = null;
|
||||
if (alertAfter < value) {
|
||||
alertColor = keyOfMatchingRange(clampedValue, scaledRanges);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box inline>
|
||||
<div
|
||||
className={classes([
|
||||
'RoundGauge',
|
||||
className,
|
||||
computeBoxClassName(rest),
|
||||
])}
|
||||
{...computeBoxProps({
|
||||
style: {
|
||||
'font-size': size + 'em',
|
||||
...style,
|
||||
},
|
||||
...rest,
|
||||
})}>
|
||||
<svg
|
||||
viewBox="0 0 100 50">
|
||||
{alertAfter && (
|
||||
<g className={classes([
|
||||
'RoundGauge__alert',
|
||||
alertColor ? `active RoundGauge__alert--${alertColor}` : '',
|
||||
])}>
|
||||
<path d="M48.211,14.578C48.55,13.9 49.242,13.472 50,13.472C50.758,13.472 51.45,13.9 51.789,14.578C54.793,20.587 60.795,32.589 63.553,38.106C63.863,38.726 63.83,39.462 63.465,40.051C63.101,40.641 62.457,41 61.764,41C55.996,41 44.004,41 38.236,41C37.543,41 36.899,40.641 36.535,40.051C36.17,39.462 36.137,38.726 36.447,38.106C39.205,32.589 45.207,20.587 48.211,14.578ZM50,34.417C51.426,34.417 52.583,35.574 52.583,37C52.583,38.426 51.426,39.583 50,39.583C48.574,39.583 47.417,38.426 47.417,37C47.417,35.574 48.574,34.417 50,34.417ZM50,32.75C50,32.75 53,31.805 53,22.25C53,20.594 51.656,19.25 50,19.25C48.344,19.25 47,20.594 47,22.25C47,31.805 50,32.75 50,32.75Z" />
|
||||
</g>
|
||||
)}
|
||||
<g>
|
||||
<circle
|
||||
className="RoundGauge__ringTrack"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="45" />
|
||||
</g>
|
||||
<g>
|
||||
{Object.keys(scaledRanges).map((x, i) => {
|
||||
const col_ranges = scaledRanges[x];
|
||||
return (
|
||||
<circle
|
||||
className={`RoundGauge__ringFill RoundGauge--color--${x}`}
|
||||
key={i}
|
||||
style={{
|
||||
'stroke-dashoffset': (
|
||||
Math.max((2.0 - (col_ranges[1] - col_ranges[0]))
|
||||
* Math.PI * 50, 0)
|
||||
),
|
||||
}}
|
||||
transform={`rotate(${180 + 180 * col_ranges[0]} 50 50)`}
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="45" />
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
<g
|
||||
className="RoundGauge__needle"
|
||||
transform={`rotate(${clampedValue * 180 - 90} 50 50)`}>
|
||||
<polygon
|
||||
className="RoundGauge__needleLine"
|
||||
points="46,50 50,0 54,50" />
|
||||
<circle
|
||||
className="RoundGauge__needleMiddle"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="8" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<AnimatedNumber
|
||||
value={value}
|
||||
format={format}
|
||||
size={size} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -41,10 +41,18 @@ export class Section extends Component {
|
||||
...rest
|
||||
} = this.props;
|
||||
const hasTitle = canRender(title) || canRender(buttons);
|
||||
const hasContent = canRender(children);
|
||||
const content = fitted
|
||||
? children
|
||||
: (
|
||||
<div
|
||||
ref={this.ref}
|
||||
className="Section__content">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
ref={this.ref}
|
||||
ref={fitted ? this.ref : undefined}
|
||||
className={classes([
|
||||
'Section',
|
||||
'Section--level--' + level,
|
||||
@@ -66,12 +74,7 @@ export class Section extends Component {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{fitted && children
|
||||
|| hasContent && (
|
||||
<div className="Section__content">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@ export class TextArea extends Component {
|
||||
onBlur,
|
||||
onEnter,
|
||||
value,
|
||||
maxLength,
|
||||
placeholder,
|
||||
...boxProps
|
||||
} = this.props;
|
||||
@@ -161,7 +162,8 @@ export class TextArea extends Component {
|
||||
onKeyPress={this.handleKeyPress}
|
||||
onInput={this.handleOnInput}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur} />
|
||||
onBlur={this.handleBlur}
|
||||
maxLength={maxLength} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,11 +9,13 @@ import { classes } from 'common/react';
|
||||
export const Tooltip = props => {
|
||||
const {
|
||||
content,
|
||||
overrideLong = false,
|
||||
position = 'bottom',
|
||||
} = props;
|
||||
// Empirically calculated length of the string,
|
||||
// at which tooltip text starts to overflow.
|
||||
const long = typeof content === 'string' && content.length > 35;
|
||||
const long = typeof content === 'string'
|
||||
&& (content.length > 35 && !overrideLong);
|
||||
return (
|
||||
<div
|
||||
className={classes([
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
export { AnimatedNumber } from './AnimatedNumber';
|
||||
export { Blink } from './Blink';
|
||||
export { BlockQuote } from './BlockQuote';
|
||||
export { Box } from './Box';
|
||||
export { Button } from './Button';
|
||||
@@ -27,6 +28,7 @@ export { Modal } from './Modal';
|
||||
export { NoticeBox } from './NoticeBox';
|
||||
export { NumberInput } from './NumberInput';
|
||||
export { ProgressBar } from './ProgressBar';
|
||||
export { RoundGauge } from './RoundGauge';
|
||||
export { Section } from './Section';
|
||||
export { Slider } from './Slider';
|
||||
export { Table } from './Table';
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { BlockQuote, Box, Button, ByondUi, Collapsible, DraggableControl, Flex, Icon, Input, Knob, LabeledList, NoticeBox, NumberInput, ProgressBar, Section, Slider, Tabs, Tooltip } from '../components';
|
||||
import { Blink, BlockQuote, Box, Button, ByondUi, Collapsible, DraggableControl, Flex, Icon, Input, Knob, LabeledList, NoticeBox, NumberInput, ProgressBar, Section, Slider, Tabs, Tooltip } from '../components';
|
||||
import { formatSiUnit } from '../format';
|
||||
import { Pane, Window } from '../layouts';
|
||||
import { createLogger } from '../logging';
|
||||
@@ -41,6 +41,10 @@ const PAGES = [
|
||||
title: 'Button',
|
||||
component: () => KitchenSinkButton,
|
||||
},
|
||||
{
|
||||
title: 'Blink',
|
||||
component: () => KitchenSinkBlinker,
|
||||
},
|
||||
{
|
||||
title: 'Box',
|
||||
component: () => KitchenSinkBox,
|
||||
@@ -175,6 +179,16 @@ const KitchenSinkButton = props => {
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkBlinker = props => {
|
||||
return (
|
||||
<Section>
|
||||
<Blink>
|
||||
Blink
|
||||
</Blink>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkBox = props => {
|
||||
return (
|
||||
<Section>
|
||||
|
||||
@@ -178,6 +178,8 @@ export const dragStartHandler = event => {
|
||||
window.screenLeft - event.screenX,
|
||||
window.screenTop - event.screenY,
|
||||
];
|
||||
// Focus click target
|
||||
event.target?.focus();
|
||||
document.addEventListener('mousemove', dragMoveHandler);
|
||||
document.addEventListener('mouseup', dragEndHandler);
|
||||
dragMoveHandler(event);
|
||||
@@ -214,6 +216,8 @@ export const resizeStartHandler = (x, y) => event => {
|
||||
window.innerWidth,
|
||||
window.innerHeight,
|
||||
];
|
||||
// Focus click target
|
||||
event.target?.focus();
|
||||
document.addEventListener('mousemove', resizeMoveHandler);
|
||||
document.addEventListener('mouseup', resizeEndHandler);
|
||||
resizeMoveHandler(event);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import { EventEmitter } from 'common/events';
|
||||
import { KEY_ALT, KEY_CTRL, KEY_F1, KEY_F12, KEY_SHIFT } from 'common/keycodes';
|
||||
import { logger } from './logging';
|
||||
|
||||
export const globalEvents = new EventEmitter();
|
||||
let ignoreWindowFocus = false;
|
||||
|
||||
@@ -25,6 +25,11 @@ const SI_SYMBOLS = [
|
||||
'E', // exa
|
||||
'Z', // zetta
|
||||
'Y', // yotta
|
||||
'R', // ronna
|
||||
'Q', // quecca
|
||||
'F',
|
||||
'N',
|
||||
'H',
|
||||
];
|
||||
|
||||
const SI_BASE_INDEX = SI_SYMBOLS.indexOf(' ');
|
||||
@@ -110,3 +115,57 @@ export const formatDb = value => {
|
||||
}
|
||||
return sign + formatted + ' dB';
|
||||
};
|
||||
|
||||
const SI_BASE_TEN_UNIT = [
|
||||
'',
|
||||
'· 10³', // kilo
|
||||
'· 10⁶', // mega
|
||||
'· 10⁹', // giga
|
||||
'· 10¹²', // tera
|
||||
'· 10¹⁵', // peta
|
||||
'· 10¹⁸', // exa
|
||||
'· 10²¹', // zetta
|
||||
'· 10²⁴', // yotta
|
||||
'· 10²⁷', // ronna
|
||||
'· 10³⁰', // quecca
|
||||
'· 10³³',
|
||||
'· 10³⁶',
|
||||
'· 10³⁹',
|
||||
];
|
||||
|
||||
const SI_BASE_TEN_INDEX = SI_BASE_TEN_UNIT.indexOf(' ');
|
||||
|
||||
|
||||
/**
|
||||
* Formats a number to a human readable form, by reducing it to SI units.
|
||||
* TODO: This is quite a shit code and shit math, needs optimization.
|
||||
*/
|
||||
export const formatSiBaseTenUnit = (
|
||||
value,
|
||||
minBase1000 = -SI_BASE_TEN_INDEX,
|
||||
unit = ''
|
||||
) => {
|
||||
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
const realBase10 = Math.floor(Math.log10(value));
|
||||
const base10 = Math.floor(Math.max(minBase1000 * 3, realBase10));
|
||||
const realBase1000 = Math.floor(realBase10 / 3);
|
||||
const base1000 = Math.floor(base10 / 3);
|
||||
const symbolIndex = clamp(
|
||||
SI_BASE_TEN_INDEX + base1000,
|
||||
0,
|
||||
SI_BASE_TEN_UNIT.length);
|
||||
const symbol = SI_BASE_TEN_UNIT[symbolIndex];
|
||||
const scaledNumber = value / Math.pow(1000, base1000);
|
||||
const scaledPrecision = realBase1000 > minBase1000
|
||||
? (2 + base1000 * 3 - base10)
|
||||
: 0;
|
||||
// TODO: Make numbers bigger than precision value show
|
||||
// up to 2 decimal numbers.
|
||||
const finalString = (
|
||||
toFixed(scaledNumber, scaledPrecision)
|
||||
+ ' ' + symbol + ' ' + unit
|
||||
);
|
||||
return finalString.trim();
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import './styles/main.scss';
|
||||
import './styles/themes/abductor.scss';
|
||||
import './styles/themes/cardtable.scss';
|
||||
import './styles/themes/clockcult.scss';
|
||||
import './styles/themes/hackerman.scss';
|
||||
import './styles/themes/malfunction.scss';
|
||||
import './styles/themes/neutral.scss';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { GenericUplink } from './Uplink';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useSharedState } from '../backend';
|
||||
import { Button, LabeledList, NoticeBox, Section, Tabs } from '../components';
|
||||
import { Fragment } from 'inferno';
|
||||
import { Window } from '../layouts';
|
||||
import { GenericUplink } from './Uplink';
|
||||
|
||||
export const AbductorConsole = (props, context) => {
|
||||
const [tab, setTab] = useSharedState(context, 'tab', 1);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { toFixed } from 'common/math';
|
||||
import { decodeHtmlEntities } from 'common/string';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, LabeledList, NumberInput, Section } from '../components';
|
||||
import { getGasLabel } from '../constants';
|
||||
import { Box, Button, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { Scrubber, Vent } from './common/AtmosControls';
|
||||
import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox';
|
||||
import { Vent, Scrubber } from './common/AtmosControls';
|
||||
|
||||
export const AirAlarm = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 bobbahbrown (https://github.com/bobbahbrown)
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { clamp01 } from 'common/math';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Flex } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const AlertModal = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
title,
|
||||
message,
|
||||
buttons,
|
||||
timeout,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Window
|
||||
title={title}
|
||||
width={350}
|
||||
height={150}
|
||||
resizable>
|
||||
{timeout !== undefined && <Loader value={timeout} />}
|
||||
<Window.Content>
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex.Item grow={1}>
|
||||
<Flex
|
||||
direction="column"
|
||||
className="AlertModal__Message"
|
||||
height="100%">
|
||||
<Flex.Item>
|
||||
<Box m={1}>
|
||||
{message}
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
<Flex.Item my={2}>
|
||||
<Flex className="AlertModal__Buttons">
|
||||
{buttons.map(button => (
|
||||
<Flex.Item key={button} mx={1}>
|
||||
<Button
|
||||
px={3}
|
||||
onClick={() => act("choose", { choice: button })}>
|
||||
{button}
|
||||
</Button>
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export const Loader = props => {
|
||||
const { value } = props;
|
||||
return (
|
||||
<div
|
||||
className="AlertModal__Loader">
|
||||
<Box
|
||||
className="AlertModal__LoaderProgress"
|
||||
style={{
|
||||
width: clamp01(value) * 100 + '%',
|
||||
}} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,10 @@
|
||||
import { map, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { pureComponentHooks } from 'common/react';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, Dimmer, Flex, Icon, Table, Tabs } from '../components';
|
||||
import { Fragment, Window } from '../layouts';
|
||||
import { Window } from '../layouts';
|
||||
import { AreaCharge, powerRank } from './PowerMonitor';
|
||||
|
||||
export const ApcControl = (props, context) => {
|
||||
|
||||
@@ -17,7 +17,7 @@ export const Biogenerator = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={550}
|
||||
height={380}
|
||||
height={420}
|
||||
resizable>
|
||||
{!!processing && (
|
||||
<Dimmer fontSize="32px">
|
||||
@@ -135,16 +135,16 @@ const ItemList = (props, context) => {
|
||||
hoveredItem,
|
||||
setHoveredItem,
|
||||
] = useLocalState(context, 'hoveredItem', {});
|
||||
const hoveredCost = hoveredItem && hoveredItem.cost || 0;
|
||||
const hoveredCost = hoveredItem.cost || 0;
|
||||
// Append extra hover data to items
|
||||
const items = props.items.map(item => {
|
||||
const [
|
||||
amount,
|
||||
setAmount,
|
||||
] = useLocalState(context, "amount" + item.name, 1);
|
||||
const notSameItem = hoveredItem && hoveredItem.name !== item.name;
|
||||
const notSameItem = hoveredItem.name !== item.name;
|
||||
const notEnoughHovered = props.biomass - hoveredCost
|
||||
* hoveredItem.amount < item.cost * amount;
|
||||
* hoveredItem.amount < item.cost * amount;
|
||||
const disabledDueToHovered = notSameItem && notEnoughHovered;
|
||||
const disabled = props.biomass < item.cost * amount || disabledDueToHovered;
|
||||
return {
|
||||
|
||||
@@ -2,16 +2,15 @@ import { filter, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { classes } from 'common/react';
|
||||
import { createSearch } from 'common/string';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Button, ByondUi, Input, Section } from '../components';
|
||||
import { Button, ByondUi, Flex, Input, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
/**
|
||||
* Returns previous and next camera names relative to the currently
|
||||
* active camera.
|
||||
*/
|
||||
const prevNextCamera = (cameras, activeCamera) => {
|
||||
export const prevNextCamera = (cameras, activeCamera) => {
|
||||
if (!activeCamera) {
|
||||
return [];
|
||||
}
|
||||
@@ -29,7 +28,7 @@ const prevNextCamera = (cameras, activeCamera) => {
|
||||
*
|
||||
* Filters cameras, applies search terms and sorts the alphabetically.
|
||||
*/
|
||||
const selectCameras = (cameras, searchText = '') => {
|
||||
export const selectCameras = (cameras, searchText = '') => {
|
||||
const testSearch = createSearch(searchText, camera => camera.name);
|
||||
return flow([
|
||||
// Null camera filter
|
||||
@@ -42,7 +41,7 @@ const selectCameras = (cameras, searchText = '') => {
|
||||
};
|
||||
|
||||
export const CameraConsole = (props, context) => {
|
||||
const { act, data, config } = useBackend(context);
|
||||
const { act, data } = useBackend(context);
|
||||
const { mapRef, activeCamera } = data;
|
||||
const cameras = selectCameras(data.cameras);
|
||||
const [
|
||||
@@ -100,36 +99,45 @@ export const CameraConsoleContent = (props, context) => {
|
||||
const { activeCamera } = data;
|
||||
const cameras = selectCameras(data.cameras, searchText);
|
||||
return (
|
||||
<Fragment>
|
||||
<Input
|
||||
autoFocus
|
||||
fluid
|
||||
mb={1}
|
||||
placeholder="Search for a camera"
|
||||
onInput={(e, value) => setSearchText(value)} />
|
||||
<Section>
|
||||
{cameras.map(camera => (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
height="100%">
|
||||
<Flex.Item>
|
||||
<Input
|
||||
autoFocus
|
||||
fluid
|
||||
mt={1}
|
||||
placeholder="Search for a camera"
|
||||
onInput={(e, value) => setSearchText(value)} />
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
height="100%">
|
||||
<Section
|
||||
fill
|
||||
scrollable>
|
||||
{cameras.map(camera => (
|
||||
// We're not using the component here because performance
|
||||
// would be absolutely abysmal (50+ ms for each re-render).
|
||||
<div
|
||||
key={camera.name}
|
||||
title={camera.name}
|
||||
className={classes([
|
||||
'Button',
|
||||
'Button--fluid',
|
||||
'Button--color--transparent',
|
||||
'Button--ellipsis',
|
||||
activeCamera
|
||||
<div
|
||||
key={camera.name}
|
||||
title={camera.name}
|
||||
className={classes([
|
||||
'Button',
|
||||
'Button--fluid',
|
||||
'Button--color--transparent',
|
||||
'Button--ellipsis',
|
||||
activeCamera
|
||||
&& camera.name === activeCamera.name
|
||||
&& 'Button--selected',
|
||||
])}
|
||||
onClick={() => act('switch_camera', {
|
||||
name: camera.name,
|
||||
})}>
|
||||
{camera.name}
|
||||
</div>
|
||||
))}
|
||||
</Section>
|
||||
</Fragment>
|
||||
])}
|
||||
onClick={() => act('switch_camera', {
|
||||
name: camera.name,
|
||||
})}>
|
||||
{camera.name}
|
||||
</div>
|
||||
))}
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { toFixed } from 'common/math';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, Icon, Knob, LabeledControls, LabeledList, Section, Tooltip } from '../components';
|
||||
import { Box, Button, Flex, Icon, Knob, LabeledControls, LabeledList, RoundGauge, Section, Tooltip } from '../components';
|
||||
import { formatSiUnit } from '../format';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const formatPressure = value => {
|
||||
if (value < 10000) {
|
||||
return toFixed(value) + ' kPa';
|
||||
}
|
||||
return formatSiUnit(value * 1000, 1, 'Pa');
|
||||
};
|
||||
|
||||
export const Canister = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
@@ -14,142 +21,167 @@ export const Canister = (props, context) => {
|
||||
defaultReleasePressure,
|
||||
minReleasePressure,
|
||||
maxReleasePressure,
|
||||
pressureLimit,
|
||||
valveOpen,
|
||||
isPrototype,
|
||||
hasHoldingTank,
|
||||
holdingTank,
|
||||
holdingTankLeakPressure,
|
||||
holdingTankFragPressure,
|
||||
restricted,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={300}
|
||||
height={232}>
|
||||
height={275}>
|
||||
<Window.Content>
|
||||
<Section
|
||||
title="Canister"
|
||||
buttons={(
|
||||
<Fragment>
|
||||
{!!isPrototype && (
|
||||
<Button
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex.Item mb={1}>
|
||||
<Section
|
||||
title="Canister"
|
||||
buttons={(
|
||||
<Fragment>
|
||||
{!!isPrototype && (
|
||||
<Button
|
||||
mr={1}
|
||||
icon={restricted ? 'lock' : 'unlock'}
|
||||
color="caution"
|
||||
content={restricted
|
||||
? 'Engineering'
|
||||
: 'Public'}
|
||||
onClick={() => act('restricted')} />
|
||||
)}
|
||||
<Button
|
||||
icon="pencil-alt"
|
||||
content="Relabel"
|
||||
onClick={() => act('relabel')} />
|
||||
</Fragment>
|
||||
)}>
|
||||
<LabeledControls>
|
||||
<LabeledControls.Item
|
||||
minWidth="66px"
|
||||
label="Pressure">
|
||||
<RoundGauge
|
||||
size={1.75}
|
||||
value={tankPressure}
|
||||
minValue={0}
|
||||
maxValue={pressureLimit}
|
||||
alertAfter={pressureLimit * 0.70}
|
||||
ranges={{
|
||||
"good": [0, pressureLimit * 0.70],
|
||||
"average": [pressureLimit * 0.70, pressureLimit * 0.85],
|
||||
"bad": [pressureLimit * 0.85, pressureLimit],
|
||||
}}
|
||||
format={formatPressure} />
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item label="Regulator">
|
||||
<Box
|
||||
position="relative"
|
||||
left="-8px">
|
||||
<Knob
|
||||
size={1.25}
|
||||
color={!!valveOpen && 'yellow'}
|
||||
value={releasePressure}
|
||||
unit="kPa"
|
||||
minValue={minReleasePressure}
|
||||
maxValue={maxReleasePressure}
|
||||
step={5}
|
||||
stepPixelSize={1}
|
||||
onDrag={(e, value) => act('pressure', {
|
||||
pressure: value,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="-2px"
|
||||
right="-20px"
|
||||
color="transparent"
|
||||
icon="fast-forward"
|
||||
onClick={() => act('pressure', {
|
||||
pressure: maxReleasePressure,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="16px"
|
||||
right="-20px"
|
||||
color="transparent"
|
||||
icon="undo"
|
||||
onClick={() => act('pressure', {
|
||||
pressure: defaultReleasePressure,
|
||||
})} />
|
||||
</Box>
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item label="Valve">
|
||||
<Button
|
||||
my={0.5}
|
||||
width="50px"
|
||||
lineHeight={2}
|
||||
fontSize="11px"
|
||||
color={valveOpen
|
||||
? (hasHoldingTank ? 'caution' : 'danger')
|
||||
: null}
|
||||
content={valveOpen ? 'Open' : 'Closed'}
|
||||
onClick={() => act('valve')} />
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item
|
||||
mr={1}
|
||||
icon={restricted ? 'lock' : 'unlock'}
|
||||
color="caution"
|
||||
content={restricted
|
||||
? 'Engineering'
|
||||
: 'Public'}
|
||||
onClick={() => act('restricted')} />
|
||||
label="Port">
|
||||
<Box position="relative">
|
||||
<Icon
|
||||
size={1.25}
|
||||
name={portConnected ? 'plug' : 'times'}
|
||||
color={portConnected ? 'good' : 'bad'} />
|
||||
<Tooltip
|
||||
content={portConnected
|
||||
? 'Connected'
|
||||
: 'Disconnected'}
|
||||
position="top" />
|
||||
</Box>
|
||||
</LabeledControls.Item>
|
||||
</LabeledControls>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Section
|
||||
height="100%"
|
||||
title="Holding Tank"
|
||||
buttons={!!hasHoldingTank && (
|
||||
<Button
|
||||
icon="eject"
|
||||
color={valveOpen && 'danger'}
|
||||
content="Eject"
|
||||
onClick={() => act('eject')} />
|
||||
)}>
|
||||
{!!hasHoldingTank && (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Label">
|
||||
{holdingTank.name}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Pressure">
|
||||
<RoundGauge
|
||||
value={holdingTank.tankPressure}
|
||||
minValue={0}
|
||||
maxValue={holdingTankFragPressure * 1.15}
|
||||
alertAfter={holdingTankLeakPressure}
|
||||
ranges={{
|
||||
"good": [0, holdingTankLeakPressure],
|
||||
"average": [holdingTankLeakPressure, holdingTankFragPressure],
|
||||
"bad": [holdingTankFragPressure, holdingTankFragPressure * 1.15],
|
||||
}}
|
||||
format={formatPressure}
|
||||
size={1.75} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
)}
|
||||
<Button
|
||||
icon="pencil-alt"
|
||||
content="Relabel"
|
||||
onClick={() => act('relabel')} />
|
||||
</Fragment>
|
||||
)}>
|
||||
<LabeledControls>
|
||||
<LabeledControls.Item
|
||||
minWidth="66px"
|
||||
label="Pressure">
|
||||
<AnimatedNumber
|
||||
value={tankPressure}
|
||||
format={value => {
|
||||
if (value < 10000) {
|
||||
return toFixed(value) + ' kPa';
|
||||
}
|
||||
return formatSiUnit(value * 1000, 1, 'Pa');
|
||||
}} />
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item label="Regulator">
|
||||
<Box
|
||||
position="relative"
|
||||
left="-8px">
|
||||
<Knob
|
||||
size={1.25}
|
||||
color={!!valveOpen && 'yellow'}
|
||||
value={releasePressure}
|
||||
unit="kPa"
|
||||
minValue={minReleasePressure}
|
||||
maxValue={maxReleasePressure}
|
||||
step={5}
|
||||
stepPixelSize={1}
|
||||
onDrag={(e, value) => act('pressure', {
|
||||
pressure: value,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="-2px"
|
||||
right="-20px"
|
||||
color="transparent"
|
||||
icon="fast-forward"
|
||||
onClick={() => act('pressure', {
|
||||
pressure: maxReleasePressure,
|
||||
})} />
|
||||
<Button
|
||||
fluid
|
||||
position="absolute"
|
||||
top="16px"
|
||||
right="-20px"
|
||||
color="transparent"
|
||||
icon="undo"
|
||||
onClick={() => act('pressure', {
|
||||
pressure: defaultReleasePressure,
|
||||
})} />
|
||||
</Box>
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item label="Valve">
|
||||
<Button
|
||||
my={0.5}
|
||||
width="50px"
|
||||
lineHeight={2}
|
||||
fontSize="11px"
|
||||
color={valveOpen
|
||||
? (hasHoldingTank ? 'caution' : 'danger')
|
||||
: null}
|
||||
content={valveOpen ? 'Open' : 'Closed'}
|
||||
onClick={() => act('valve')} />
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item
|
||||
mr={1}
|
||||
label="Port">
|
||||
<Box position="relative">
|
||||
<Icon
|
||||
size={1.25}
|
||||
name={portConnected ? 'plug' : 'times'}
|
||||
color={portConnected ? 'good' : 'bad'} />
|
||||
<Tooltip
|
||||
content={portConnected
|
||||
? 'Connected'
|
||||
: 'Disconnected'}
|
||||
position="top" />
|
||||
</Box>
|
||||
</LabeledControls.Item>
|
||||
</LabeledControls>
|
||||
</Section>
|
||||
<Section
|
||||
title="Holding Tank"
|
||||
buttons={!!hasHoldingTank && (
|
||||
<Button
|
||||
icon="eject"
|
||||
color={valveOpen && 'danger'}
|
||||
content="Eject"
|
||||
onClick={() => act('eject')} />
|
||||
)}>
|
||||
{!!hasHoldingTank && (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Label">
|
||||
{holdingTank.name}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Pressure">
|
||||
<AnimatedNumber value={holdingTank.tankPressure} /> kPa
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
)}
|
||||
{!hasHoldingTank && (
|
||||
<Box color="average">
|
||||
No Holding Tank
|
||||
</Box>
|
||||
)}
|
||||
</Section>
|
||||
{!hasHoldingTank && (
|
||||
<Box color="average">
|
||||
No Holding Tank
|
||||
</Box>
|
||||
)}
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useBackend } from '../backend';
|
||||
import { Box, Button } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const PX_PER_UNIT = 28;
|
||||
const PX_PER_UNIT = 24;
|
||||
|
||||
class PaintCanvas extends Component {
|
||||
constructor(props) {
|
||||
@@ -60,14 +60,15 @@ class PaintCanvas extends Component {
|
||||
const {
|
||||
res = 1,
|
||||
value,
|
||||
dotsize = PX_PER_UNIT,
|
||||
...rest
|
||||
} = this.props;
|
||||
const [width, height] = getImageSize(value);
|
||||
return (
|
||||
<canvas
|
||||
ref={this.canvasRef}
|
||||
width={(width * PX_PER_UNIT) || 300}
|
||||
height={(height * PX_PER_UNIT) || 300}
|
||||
width={(width * dotsize) || 300}
|
||||
height={(height * dotsize) || 300}
|
||||
{...rest}
|
||||
onClick={e => this.clickwrapper(e)}>
|
||||
Canvas failed to render.
|
||||
@@ -84,16 +85,18 @@ const getImageSize = value => {
|
||||
|
||||
export const Canvas = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const dotsize = PX_PER_UNIT;
|
||||
const [width, height] = getImageSize(data.grid);
|
||||
return (
|
||||
<Window
|
||||
width={Math.min(400, width * PX_PER_UNIT * 32 + 24)}
|
||||
height={Math.min(400, height * PX_PER_UNIT * 32 + 24)}
|
||||
width={Math.min(700, width * dotsize + 72)}
|
||||
height={Math.min(700, height * dotsize + 72)}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
<Window.Content>
|
||||
<Box textAlign="center">
|
||||
<PaintCanvas
|
||||
value={data.grid}
|
||||
dotsize={dotsize}
|
||||
onCanvasClick={(x, y) => act("paint", { x, y })} />
|
||||
<Box>
|
||||
{!data.finalized && (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, Flex, LabeledList, NoticeBox, Section } from '../components';
|
||||
import { Button, Flex, LabeledList, NoticeBox, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const CivCargoHoldTerminal = (props, context) => {
|
||||
|
||||
@@ -0,0 +1,721 @@
|
||||
import { sortBy } from "common/collections";
|
||||
import { capitalize } from "common/string";
|
||||
import { useBackend, useLocalState } from "../backend";
|
||||
import { Blink, Box, Button, Dimmer, Flex, Icon, Input, Modal, Section, TextArea } from "../components";
|
||||
import { Window } from "../layouts";
|
||||
import { sanitizeText } from "../sanitize";
|
||||
|
||||
const STATE_BUYING_SHUTTLE = "buying_shuttle";
|
||||
const STATE_CHANGING_STATUS = "changing_status";
|
||||
const STATE_MAIN = "main";
|
||||
const STATE_MESSAGES = "messages";
|
||||
|
||||
// Used for whether or not you need to swipe to confirm an alert level change
|
||||
const SWIPE_NEEDED = "SWIPE_NEEDED";
|
||||
|
||||
const sortByCreditCost = sortBy(shuttle => shuttle.creditCost);
|
||||
|
||||
const AlertButton = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { alertLevelTick, canSetAlertLevel } = data;
|
||||
const { alertLevel, setShowAlertLevelConfirm } = props;
|
||||
|
||||
const thisIsCurrent = data.alertLevel === alertLevel;
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon="exclamation-triangle"
|
||||
color={thisIsCurrent && "good"}
|
||||
content={capitalize(alertLevel)}
|
||||
onClick={() => {
|
||||
if (thisIsCurrent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (canSetAlertLevel === SWIPE_NEEDED) {
|
||||
setShowAlertLevelConfirm([alertLevel, alertLevelTick]);
|
||||
} else {
|
||||
act("changeSecurityLevel", {
|
||||
newSecurityLevel: alertLevel,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const MessageModal = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const { maxMessageLength } = data;
|
||||
|
||||
const [input, setInput] = useLocalState(context, props.label, "");
|
||||
|
||||
const longEnough = props.minLength === undefined
|
||||
|| input.length >= props.minLength;
|
||||
|
||||
return (
|
||||
<Modal>
|
||||
<Flex direction="column">
|
||||
<Flex.Item fontSize="16px" maxWidth="90vw" mb={1}>
|
||||
{props.label}:
|
||||
</Flex.Item>
|
||||
|
||||
<Flex.Item mr={2} mb={1}>
|
||||
<TextArea
|
||||
fluid
|
||||
height="20vh"
|
||||
width="80vw"
|
||||
backgroundColor="black"
|
||||
textColor="white"
|
||||
onInput={(_, value) => {
|
||||
setInput(value.substring(0, maxMessageLength));
|
||||
}}
|
||||
value={input}
|
||||
/>
|
||||
</Flex.Item>
|
||||
|
||||
<Flex.Item>
|
||||
<Button
|
||||
icon={props.icon}
|
||||
content={props.buttonText}
|
||||
color="good"
|
||||
disabled={!longEnough}
|
||||
tooltip={!longEnough ? "You need a longer reason." : ""}
|
||||
tooltipPosition="right"
|
||||
onClick={() => {
|
||||
if (longEnough) {
|
||||
setInput("");
|
||||
props.onSubmit(input);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon="times"
|
||||
content="Cancel"
|
||||
color="bad"
|
||||
onClick={props.onBack}
|
||||
/>
|
||||
</Flex.Item>
|
||||
|
||||
{!!props.notice && (
|
||||
<Flex.Item maxWidth="90vw">{props.notice}</Flex.Item>
|
||||
)}
|
||||
</Flex>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const NoConnectionModal = () => {
|
||||
return (
|
||||
<Dimmer>
|
||||
<Flex direction="column" textAlign="center" width="300px">
|
||||
<Flex.Item>
|
||||
<Icon
|
||||
color="red"
|
||||
name="wifi"
|
||||
size={10}
|
||||
/>
|
||||
|
||||
<Blink>
|
||||
<div
|
||||
style={{
|
||||
background: "#db2828",
|
||||
bottom: "60%",
|
||||
left: "25%",
|
||||
height: "10px",
|
||||
position: "relative",
|
||||
transform: "rotate(45deg)",
|
||||
width: "150px",
|
||||
}}
|
||||
/>
|
||||
</Blink>
|
||||
</Flex.Item>
|
||||
|
||||
<Flex.Item fontSize="16px">
|
||||
A connection to the station cannot be established.
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Dimmer>
|
||||
);
|
||||
};
|
||||
|
||||
const PageBuyingShuttle = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Section>
|
||||
<Button
|
||||
icon="chevron-left"
|
||||
content="Back"
|
||||
onClick={() => act("setState", { state: STATE_MAIN })}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
Budget: <b>{data.budget.toLocaleString()}</b> credits
|
||||
</Section>
|
||||
|
||||
{sortByCreditCost(data.shuttles).map(shuttle => (
|
||||
<Section
|
||||
title={(
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: "70%",
|
||||
}}>
|
||||
{shuttle.name}
|
||||
</span>
|
||||
)}
|
||||
key={shuttle.ref}
|
||||
buttons={(
|
||||
<Button
|
||||
content={`${shuttle.creditCost.toLocaleString()} credits`}
|
||||
disabled={data.budget < shuttle.creditCost}
|
||||
onClick={() => act("purchaseShuttle", {
|
||||
shuttle: shuttle.ref,
|
||||
})}
|
||||
tooltip={
|
||||
data.budget < shuttle.creditCost
|
||||
? `You need ${shuttle.creditCost - data.budget} more credits.`
|
||||
: undefined
|
||||
}
|
||||
tooltipPosition="left"
|
||||
/>
|
||||
)}>
|
||||
<Box>{shuttle.description}</Box>
|
||||
{
|
||||
shuttle.prerequisites
|
||||
? <b>Prerequisites: {shuttle.prerequisites}</b>
|
||||
: null
|
||||
}
|
||||
</Section>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const PageChangingStatus = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { maxStatusLineLength } = data;
|
||||
|
||||
const [lineOne, setLineOne] = useLocalState(context, "lineOne", data.lineOne);
|
||||
const [lineTwo, setLineTwo] = useLocalState(context, "lineTwo", data.lineTwo);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Section>
|
||||
<Button
|
||||
icon="chevron-left"
|
||||
content="Back"
|
||||
onClick={() => act("setState", { state: STATE_MAIN })}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<Flex direction="column">
|
||||
<Flex.Item>
|
||||
<Button
|
||||
icon="times"
|
||||
content="Clear Alert"
|
||||
color="bad"
|
||||
onClick={() => act("setStatusPicture", { picture: "blank" })}
|
||||
/>
|
||||
</Flex.Item>
|
||||
|
||||
<Flex.Item mt={1}>
|
||||
<Button
|
||||
icon="check-square-o"
|
||||
content="Default"
|
||||
onClick={() => act("setStatusPicture", { picture: "default" })}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon="bell-o"
|
||||
content="Red Alert"
|
||||
onClick={() => act("setStatusPicture", { picture: "redalert" })}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon="exclamation-triangle"
|
||||
content="Lockdown"
|
||||
onClick={() => act("setStatusPicture", { picture: "lockdown" })}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon="exclamation-circle"
|
||||
content="Biohazard"
|
||||
onClick={() => act("setStatusPicture", { picture: "biohazard" })}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon="space-shuttle"
|
||||
content="Shuttle ETA"
|
||||
onClick={() => act("setStatusPicture", { picture: "shuttle" })}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
|
||||
<Section title="Message">
|
||||
<Flex direction="column">
|
||||
<Flex.Item mb={1}>
|
||||
<Input
|
||||
maxLength={maxStatusLineLength}
|
||||
value={lineOne}
|
||||
width="200px"
|
||||
onChange={(_, value) => setLineOne(value)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
|
||||
<Flex.Item mb={1}>
|
||||
<Input
|
||||
maxLength={maxStatusLineLength}
|
||||
value={lineTwo}
|
||||
width="200px"
|
||||
onChange={(_, value) => setLineTwo(value)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
|
||||
<Flex.Item>
|
||||
<Button
|
||||
icon="comment-o"
|
||||
content="Message"
|
||||
onClick={() => act("setStatusMessage", {
|
||||
lineOne,
|
||||
lineTwo,
|
||||
})}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const PageMain = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
alertLevel,
|
||||
alertLevelTick,
|
||||
callShuttleReasonMinLength,
|
||||
canBuyShuttles,
|
||||
canMakeAnnouncement,
|
||||
canMessageAssociates,
|
||||
canRecallShuttles,
|
||||
canRequestNuke,
|
||||
canSendToSectors,
|
||||
canSetAlertLevel,
|
||||
canToggleEmergencyAccess,
|
||||
emagged,
|
||||
emergencyAccess,
|
||||
importantActionReady,
|
||||
sectors,
|
||||
shuttleCalled,
|
||||
shuttleCalledPreviously,
|
||||
shuttleCanEvacOrFailReason,
|
||||
shuttleLastCalled,
|
||||
shuttleRecallable,
|
||||
} = data;
|
||||
|
||||
const [callingShuttle, setCallingShuttle] = useLocalState(
|
||||
context, "calling_shuttle", false);
|
||||
const [messagingAssociates, setMessagingAssociates] = useLocalState(
|
||||
context, "messaging_associates", false);
|
||||
const [messagingSector, setMessagingSector] = useLocalState(
|
||||
context, "messaing_sector", null);
|
||||
const [requestingNukeCodes, setRequestingNukeCodes] = useLocalState(
|
||||
context, "requesting_nuke_codes", false);
|
||||
|
||||
const [
|
||||
[showAlertLevelConfirm, confirmingAlertLevelTick],
|
||||
setShowAlertLevelConfirm,
|
||||
] = useLocalState(context, "showConfirmPrompt", [null, null]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Section title="Emergency Shuttle">
|
||||
{
|
||||
shuttleCalled
|
||||
? <Button.Confirm
|
||||
icon="space-shuttle"
|
||||
content="Recall Emergency Shuttle"
|
||||
color="bad"
|
||||
disabled={!canRecallShuttles || !shuttleRecallable}
|
||||
tooltip={(
|
||||
canRecallShuttles && (
|
||||
!shuttleRecallable && "It's too late for the emergency shuttle to be recalled."
|
||||
) || (
|
||||
"You do not have permission to recall the emergency shuttle."
|
||||
)
|
||||
)}
|
||||
tooltipPosition="bottom-right"
|
||||
onClick={() => act("recallShuttle")}
|
||||
/>
|
||||
: <Button
|
||||
icon="space-shuttle"
|
||||
content="Call Emergency Shuttle"
|
||||
disabled={shuttleCanEvacOrFailReason !== 1}
|
||||
tooltip={
|
||||
shuttleCanEvacOrFailReason !== 1
|
||||
? shuttleCanEvacOrFailReason
|
||||
: undefined
|
||||
}
|
||||
tooltipPosition="bottom-right"
|
||||
onClick={() => setCallingShuttle(true)}
|
||||
/>
|
||||
}
|
||||
|
||||
{!!shuttleCalledPreviously && (
|
||||
shuttleLastCalled && (
|
||||
<Box>
|
||||
Most recent shuttle call/recall traced to:
|
||||
{" "}<b>{shuttleLastCalled}</b>
|
||||
</Box>
|
||||
) || (
|
||||
<Box>Unable to trace most recent shuttle/recall signal.</Box>
|
||||
)
|
||||
)}
|
||||
</Section>
|
||||
|
||||
{!!canSetAlertLevel && (
|
||||
<Section title="Alert Level">
|
||||
<Flex justify="space-between">
|
||||
<Flex.Item>
|
||||
<Box>
|
||||
Currently on <b>{capitalize(alertLevel)}</b> Alert
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
|
||||
<Flex.Item>
|
||||
<AlertButton
|
||||
alertLevel="green"
|
||||
showAlertLevelConfirm={showAlertLevelConfirm}
|
||||
setShowAlertLevelConfirm={setShowAlertLevelConfirm}
|
||||
/>
|
||||
|
||||
<AlertButton
|
||||
alertLevel="blue"
|
||||
showAlertLevelConfirm={showAlertLevelConfirm}
|
||||
setShowAlertLevelConfirm={setShowAlertLevelConfirm}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
<Section title="Functions">
|
||||
<Flex
|
||||
direction="column">
|
||||
{!!canMakeAnnouncement && <Button
|
||||
icon="bullhorn"
|
||||
content="Make Priority Announcement"
|
||||
onClick={() => act("makePriorityAnnouncement")}
|
||||
/>}
|
||||
|
||||
{!!canToggleEmergencyAccess && <Button.Confirm
|
||||
icon="id-card-o"
|
||||
content={`${emergencyAccess ? "Disable" : "Enable"} Emergency Maintenance Access`}
|
||||
color={emergencyAccess ? "bad" : undefined}
|
||||
onClick={() => act("toggleEmergencyAccess")}
|
||||
/>}
|
||||
|
||||
<Button
|
||||
icon="desktop"
|
||||
content="Set Status Display"
|
||||
onClick={() => act("setState", { state: STATE_CHANGING_STATUS })}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon="envelope-o"
|
||||
content="Message List"
|
||||
onClick={() => act("setState", { state: STATE_MESSAGES })}
|
||||
/>
|
||||
|
||||
{(canBuyShuttles !== 0) && <Button
|
||||
icon="shopping-cart"
|
||||
content="Purchase Shuttle"
|
||||
disabled={canBuyShuttles !== 1}
|
||||
// canBuyShuttles is a string detailing the fail reason
|
||||
// if one can be given
|
||||
tooltip={canBuyShuttles !== 1 ? canBuyShuttles : undefined}
|
||||
tooltipPosition="right"
|
||||
onClick={() => act("setState", { state: STATE_BUYING_SHUTTLE })}
|
||||
/>}
|
||||
|
||||
{!!canMessageAssociates && <Button
|
||||
icon="comment-o"
|
||||
content={`Send message to ${emagged ? "[UNKNOWN]" : "CentCom"}`}
|
||||
disabled={!importantActionReady}
|
||||
onClick={() => setMessagingAssociates(true)}
|
||||
/>}
|
||||
|
||||
{!!canRequestNuke && <Button
|
||||
icon="radiation"
|
||||
content="Request Nuclear Authentication Codes"
|
||||
disabled={!importantActionReady}
|
||||
onClick={() => setRequestingNukeCodes(true)}
|
||||
/>}
|
||||
|
||||
{!!emagged && <Button
|
||||
icon="undo"
|
||||
content="Restore Backup Routing Data"
|
||||
onClick={() => act("restoreBackupRoutingData")}
|
||||
/>}
|
||||
</Flex>
|
||||
</Section>
|
||||
|
||||
{!!canMessageAssociates && messagingAssociates && <MessageModal
|
||||
label={`Message to transmit to ${emagged ? "[ABNORMAL ROUTING COORDINATES]" : "CentCom"} via quantum entanglement`}
|
||||
notice="Please be aware that this process is very expensive, and abuse will lead to...termination. Transmission does not guarantee a response."
|
||||
icon="bullhorn"
|
||||
buttonText="Send"
|
||||
onBack={() => setMessagingAssociates(false)}
|
||||
onSubmit={message => {
|
||||
setMessagingAssociates(false);
|
||||
act("messageAssociates", {
|
||||
message,
|
||||
});
|
||||
}}
|
||||
/>}
|
||||
|
||||
{!!canRequestNuke && requestingNukeCodes && <MessageModal
|
||||
label="Reason for requesting nuclear self-destruct codes"
|
||||
notice="Misuse of the nuclear request system will not be tolerated under any circumstances. Transmission does not guarantee a response."
|
||||
icon="bomb"
|
||||
buttonText="Request Codes"
|
||||
onBack={() => setRequestingNukeCodes(false)}
|
||||
onSubmit={reason => {
|
||||
setRequestingNukeCodes(false);
|
||||
act("requestNukeCodes", {
|
||||
reason,
|
||||
});
|
||||
}}
|
||||
/>}
|
||||
|
||||
{!!callingShuttle && <MessageModal
|
||||
label="Nature of emergency"
|
||||
icon="space-shuttle"
|
||||
buttonText="Call Shuttle"
|
||||
minLength={callShuttleReasonMinLength}
|
||||
onBack={() => setCallingShuttle(false)}
|
||||
onSubmit={reason => {
|
||||
setCallingShuttle(false);
|
||||
act("callShuttle", {
|
||||
reason,
|
||||
});
|
||||
}}
|
||||
/>}
|
||||
|
||||
{
|
||||
!!canSetAlertLevel
|
||||
&& showAlertLevelConfirm
|
||||
&& confirmingAlertLevelTick === alertLevelTick
|
||||
&& (
|
||||
<Modal>
|
||||
<Flex
|
||||
direction="column"
|
||||
textAlign="center"
|
||||
width="300px">
|
||||
<Flex.Item fontSize="16px" mb={2}>
|
||||
Swipe ID to confirm change
|
||||
</Flex.Item>
|
||||
|
||||
<Flex.Item mr={2} mb={1}>
|
||||
<Button
|
||||
icon="id-card-o"
|
||||
content="Swipe ID"
|
||||
color="good"
|
||||
fontSize="16px"
|
||||
onClick={() => act("changeSecurityLevel", {
|
||||
newSecurityLevel: showAlertLevelConfirm,
|
||||
})}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon="times"
|
||||
content="Cancel"
|
||||
color="bad"
|
||||
fontSize="16px"
|
||||
onClick={() => setShowAlertLevelConfirm(false)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
!!canSendToSectors
|
||||
&& sectors.length > 0
|
||||
&& (
|
||||
<Section title="Allied Sectors">
|
||||
<Flex
|
||||
direction="column">
|
||||
{
|
||||
sectors.map(sectorName => (
|
||||
<Flex.Item key={sectorName}>
|
||||
<Button
|
||||
content={
|
||||
`Send a message to station in ${sectorName} sector`
|
||||
}
|
||||
disabled={!importantActionReady}
|
||||
onClick={() => setMessagingSector(sectorName)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
))
|
||||
}
|
||||
|
||||
{sectors.length > 2 && (
|
||||
<Flex.Item>
|
||||
<Button
|
||||
content="Send a message to all allied stations"
|
||||
disabled={!importantActionReady}
|
||||
onClick={() => setMessagingSector("all")}
|
||||
/>
|
||||
</Flex.Item>
|
||||
)}
|
||||
</Flex>
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
!!canSendToSectors
|
||||
&& sectors.length > 0
|
||||
&& messagingSector
|
||||
&& <MessageModal
|
||||
label="Message to send to allied station"
|
||||
notice="Please be aware that this process is very expensive, and abuse will lead to...termination."
|
||||
icon="bullhorn"
|
||||
buttonText="Send"
|
||||
onBack={() => setMessagingSector(null)}
|
||||
onSubmit={message => {
|
||||
act("sendToOtherSector", {
|
||||
destination: messagingSector,
|
||||
message,
|
||||
});
|
||||
|
||||
setMessagingSector(null);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const PageMessages = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const messages = data.messages || [];
|
||||
|
||||
const children = [];
|
||||
|
||||
children.push((
|
||||
<Section>
|
||||
<Button
|
||||
icon="chevron-left"
|
||||
content="Back"
|
||||
onClick={() => act("setState", { state: STATE_MAIN })}
|
||||
/>
|
||||
</Section>
|
||||
));
|
||||
|
||||
const messageElements = [];
|
||||
|
||||
for (const [messageIndex, message] of Object.entries(messages)) {
|
||||
let answers = null;
|
||||
|
||||
if (message.possibleAnswers.length > 0) {
|
||||
answers = (
|
||||
<Box mt={1}>
|
||||
{message.possibleAnswers.map((answer, answerIndex) => (
|
||||
<Button
|
||||
content={answer}
|
||||
color={message.answered === answerIndex + 1 ? "good" : undefined}
|
||||
key={answerIndex}
|
||||
onClick={message.answered ? undefined : () => act("answerMessage", {
|
||||
message: messageIndex + 1,
|
||||
answer: answerIndex + 1,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const textHtml = {
|
||||
__html: sanitizeText(message.content),
|
||||
};
|
||||
|
||||
messageElements.push((
|
||||
<Section
|
||||
title={message.title}
|
||||
key={messageIndex}
|
||||
buttons={(
|
||||
<Button.Confirm
|
||||
icon="trash"
|
||||
content="Delete"
|
||||
color="red"
|
||||
onClick={() => act("deleteMessage", {
|
||||
message: messageIndex + 1,
|
||||
})}
|
||||
/>
|
||||
)}>
|
||||
<Box
|
||||
dangerouslySetInnerHTML={textHtml} />
|
||||
|
||||
{answers}
|
||||
</Section>
|
||||
));
|
||||
}
|
||||
|
||||
children.push(messageElements.reverse());
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export const CommunicationsConsole = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
authenticated,
|
||||
authorizeName,
|
||||
canLogOut,
|
||||
emagged,
|
||||
hasConnection,
|
||||
page,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Window
|
||||
width={400}
|
||||
height={650}
|
||||
theme={emagged ? "syndicate" : undefined}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
{!hasConnection && <NoConnectionModal />}
|
||||
|
||||
{(canLogOut || !authenticated)
|
||||
? (
|
||||
<Section title="Authentication">
|
||||
<Button
|
||||
icon={authenticated ? "sign-out-alt" : "sign-in-alt"}
|
||||
content={authenticated ? `Log Out${authorizeName ? ` (${authorizeName})` : ""}` : "Log In"}
|
||||
color={authenticated ? "bad" : "good"}
|
||||
onClick={() => act("toggleAuthentication")}
|
||||
/>
|
||||
</Section>
|
||||
)
|
||||
: null}
|
||||
|
||||
{!!authenticated && (
|
||||
page === STATE_BUYING_SHUTTLE && <PageBuyingShuttle />
|
||||
|| page === STATE_CHANGING_STATUS && <PageChangingStatus />
|
||||
|| page === STATE_MAIN && <PageMain />
|
||||
|| page === STATE_MESSAGES && <PageMessages />
|
||||
|| <Box>Page not implemented: {page}</Box>
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { sortBy } from 'common/collections';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, ColorBox, Section, Table } from '../components';
|
||||
import { COLORS } from '../constants';
|
||||
@@ -62,7 +63,7 @@ export const CrewConsole = () => {
|
||||
return (
|
||||
<Window
|
||||
title="Crew Monitor"
|
||||
width={800}
|
||||
width={600}
|
||||
height={600}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
@@ -76,7 +77,9 @@ export const CrewConsole = () => {
|
||||
|
||||
const CrewTable = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const sensors = data.sensors || [];
|
||||
const sensors = sortBy(
|
||||
s => s.ijob
|
||||
)(data.sensors ?? []);
|
||||
return (
|
||||
<Table>
|
||||
<Table.Row>
|
||||
@@ -97,50 +100,72 @@ const CrewTable = (props, context) => {
|
||||
)}
|
||||
</Table.Row>
|
||||
{sensors.map(sensor => (
|
||||
<Table.Row key={sensor.name}>
|
||||
<Table.Cell
|
||||
bold={jobIsHead(sensor.ijob)}
|
||||
color={jobToColor(sensor.ijob)}>
|
||||
{sensor.name} ({sensor.assignment})
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing textAlign="center">
|
||||
<ColorBox
|
||||
color={healthToColor(
|
||||
sensor.oxydam,
|
||||
sensor.toxdam,
|
||||
sensor.burndam,
|
||||
sensor.brutedam)} />
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing textAlign="center">
|
||||
{sensor.oxydam !== null ? (
|
||||
<Box inline>
|
||||
<HealthStat type="oxy" value={sensor.oxydam} />
|
||||
{'/'}
|
||||
<HealthStat type="toxin" value={sensor.toxdam} />
|
||||
{'/'}
|
||||
<HealthStat type="burn" value={sensor.burndam} />
|
||||
{'/'}
|
||||
<HealthStat type="brute" value={sensor.brutedam} />
|
||||
</Box>
|
||||
) : (
|
||||
sensor.life_status ? 'Alive' : 'Dead'
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{sensor.pos_x !== null ? sensor.area : 'N/A'}
|
||||
</Table.Cell>
|
||||
{!!data.link_allowed && (
|
||||
<Table.Cell collapsing>
|
||||
<Button
|
||||
content="Track"
|
||||
disabled={!sensor.can_track}
|
||||
onClick={() => act('select_person', {
|
||||
name: sensor.name,
|
||||
})} />
|
||||
</Table.Cell>
|
||||
)}
|
||||
</Table.Row>
|
||||
<CrewTableEntry sensor_data={sensor} key={sensor.ref} />
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
const CrewTableEntry = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { link_allowed } = data;
|
||||
const { sensor_data } = props;
|
||||
const {
|
||||
name,
|
||||
assignment,
|
||||
ijob,
|
||||
life_status,
|
||||
oxydam,
|
||||
toxdam,
|
||||
burndam,
|
||||
brutedam,
|
||||
area,
|
||||
can_track,
|
||||
} = sensor_data;
|
||||
|
||||
return (
|
||||
<Table.Row>
|
||||
<Table.Cell
|
||||
bold={jobIsHead(ijob)}
|
||||
color={jobToColor(ijob)}>
|
||||
{name}{assignment !== undefined ? ` (${assignment})` : ""}
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing textAlign="center">
|
||||
<ColorBox
|
||||
color={healthToColor(
|
||||
oxydam,
|
||||
toxdam,
|
||||
burndam,
|
||||
brutedam)} />
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing textAlign="center">
|
||||
{oxydam !== undefined ? (
|
||||
<Box inline>
|
||||
<HealthStat type="oxy" value={oxydam} />
|
||||
{'/'}
|
||||
<HealthStat type="toxin" value={toxdam} />
|
||||
{'/'}
|
||||
<HealthStat type="burn" value={burndam} />
|
||||
{'/'}
|
||||
<HealthStat type="brute" value={brutedam} />
|
||||
</Box>
|
||||
) : (
|
||||
life_status ? 'Alive' : 'Dead'
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{area !== undefined ? area : 'N/A'}
|
||||
</Table.Cell>
|
||||
{!!link_allowed && (
|
||||
<Table.Cell collapsing>
|
||||
<Button
|
||||
content="Track"
|
||||
disabled={!can_track}
|
||||
onClick={() => act('select_person', {
|
||||
name: name,
|
||||
})} />
|
||||
</Table.Cell>
|
||||
)}
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import { multiline } from 'common/string';
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, Input, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const TOOLTIP_NAME = multiline`
|
||||
Enter a new name for this unit. Set to blank to reset to default,
|
||||
which means unit will be able to choose it's own name.
|
||||
`;
|
||||
|
||||
const TOOLTIP_LOCOMOTION = multiline`
|
||||
If restricted, unit will be
|
||||
under lockdown until released.
|
||||
`;
|
||||
|
||||
const TOOLTIP_PANEL = multiline`
|
||||
If unlocked, unit's cover panel will be
|
||||
accessible even without proper authorization.
|
||||
`;
|
||||
|
||||
const TOOLTIP_AISYNC = multiline`
|
||||
If closed, this unit will
|
||||
not be paired with any AI.
|
||||
`;
|
||||
|
||||
const TOOLTIP_AI = multiline`
|
||||
Controls who will be the
|
||||
master AI of this unit.
|
||||
`;
|
||||
|
||||
const TOOLTIP_LAWSYNC = multiline`
|
||||
If closed, this unit will not synchronize
|
||||
it's laws with it's master AI.
|
||||
`;
|
||||
|
||||
export const CyborgBootDebug = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
designation,
|
||||
master,
|
||||
lawsync,
|
||||
aisync,
|
||||
locomotion,
|
||||
panel,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={master?.length > 26 ? 537 : 440}
|
||||
height={289}>
|
||||
<Window.Content>
|
||||
<Section title="Basic Settings">
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Designation"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="info"
|
||||
tooltip={TOOLTIP_NAME}
|
||||
tooltipPosition="left" />
|
||||
)}>
|
||||
<Input
|
||||
fluid
|
||||
value={designation || "Default Cyborg"}
|
||||
onChange={(e, value) => act('rename', {
|
||||
new_name: value,
|
||||
})} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Servo Motor Functions"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="info"
|
||||
tooltip={TOOLTIP_LOCOMOTION}
|
||||
tooltipPosition="left" />
|
||||
)}>
|
||||
<Button
|
||||
icon={locomotion ? "unlock" : "lock"}
|
||||
content={locomotion ? "Free" : "Restricted"}
|
||||
color={locomotion ? "good" : "bad"}
|
||||
onClick={() => act('locomotion')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Cover Panel"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="info"
|
||||
tooltip={TOOLTIP_PANEL}
|
||||
tooltipPosition="left" />
|
||||
)}>
|
||||
<Button
|
||||
icon={panel ? "lock" : "unlock"}
|
||||
content={panel ? "Locked" : "Unlocked"}
|
||||
onClick={() => act('panel')} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section title="AI Settings">
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="AI Connection Port"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="info"
|
||||
tooltip={TOOLTIP_AISYNC}
|
||||
tooltipPosition="left" />
|
||||
)}>
|
||||
<Button
|
||||
icon={aisync ? "unlock" : "lock"}
|
||||
content={aisync ? "Open" : "Closed"}
|
||||
onClick={() => act('aisync')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Master AI"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="info"
|
||||
tooltip={TOOLTIP_AI}
|
||||
tooltipPosition="left" />
|
||||
)}>
|
||||
<Button
|
||||
icon={!aisync ? "times" : master ? "edit" : "sync"}
|
||||
content={!aisync ? "None" : master || "Automatic"}
|
||||
color={master ? "default" : "good"}
|
||||
disabled={!aisync}
|
||||
onClick={() => act('set_ai')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="LawSync Port"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="info"
|
||||
tooltip={TOOLTIP_LAWSYNC}
|
||||
tooltipPosition="top-left" />
|
||||
)}>
|
||||
<Button
|
||||
icon={!aisync ? "lock" : lawsync ? "unlock" : "lock"}
|
||||
content={!aisync ? "Closed" : lawsync ? "Open" : "Closed"}
|
||||
disabled={!aisync}
|
||||
onClick={() => act('lawsync')} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -37,7 +37,7 @@ export const DnaVault = (props, context) => {
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Animal DNA">
|
||||
<ProgressBar
|
||||
value={animals / animals}>
|
||||
value={animals / animals_max}>
|
||||
{animals + ' / ' + animals_max + ' Samples'}
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
|
||||
@@ -28,7 +28,7 @@ export const ExosuitControlConsole = (props, context) => {
|
||||
<Fragment>
|
||||
<Button
|
||||
icon="envelope"
|
||||
content="Send Message"
|
||||
content="Message"
|
||||
disabled={!mech.pilot}
|
||||
onClick={() => act('send_message', {
|
||||
tracker_ref: mech.tracker_ref,
|
||||
@@ -76,7 +76,14 @@ export const ExosuitControlConsole = (props, context) => {
|
||||
) || 'Not Equipped'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Pilot">
|
||||
{mech.pilot || 'None'}
|
||||
{mech.pilot.length > 0 && (
|
||||
mech.pilot.map(pilot => (
|
||||
<Box
|
||||
key={pilot}
|
||||
inline>
|
||||
{pilot}{mech.pilot.length > 1 ? '|' : ''}
|
||||
</Box>
|
||||
))) || 'None'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Location">
|
||||
{mech.location || 'Unknown'}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { classes } from 'common/react';
|
||||
import { uniqBy } from 'common/collections';
|
||||
import { useBackend, useSharedState } from '../backend';
|
||||
import { formatSiUnit, formatMoney } from '../format';
|
||||
import { Flex, Section, Tabs, Box, Button, Fragment, ProgressBar, NumberInput, Icon, Input } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { classes } from 'common/react';
|
||||
import { createSearch } from 'common/string';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useSharedState } from '../backend';
|
||||
import { Box, Button, Flex, Icon, Input, NumberInput, ProgressBar, Section, Tabs } from '../components';
|
||||
import { formatMoney, formatSiUnit } from '../format';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const MATERIAL_KEYS = {
|
||||
"iron": "sheet-metal_3",
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
import { useBackend, useLocalState } from "../backend";
|
||||
import { Fragment } from 'inferno';
|
||||
import { Box, Button, Collapsible, ColorBox, Dropdown, Input, LabeledList, NoticeBox, NumberInput, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { map } from 'common/collections';
|
||||
import { toFixed } from 'common/math';
|
||||
import { numberOfDecimalDigits } from "../../common/math";
|
||||
|
||||
const FilterIntegerEntry = (props, context) => {
|
||||
const { value, name, filterName } = props;
|
||||
const { act } = useBackend(context);
|
||||
return (
|
||||
<NumberInput
|
||||
value={value}
|
||||
minValue={-500}
|
||||
maxValue={500}
|
||||
stepPixelSize={5}
|
||||
width="39px"
|
||||
onDrag={(e, value) => act('modify_filter_value', {
|
||||
name: filterName,
|
||||
new_data: {
|
||||
[name]: value,
|
||||
},
|
||||
})} />
|
||||
);
|
||||
};
|
||||
|
||||
const FilterFloatEntry = (props, context) => {
|
||||
const { value, name, filterName } = props;
|
||||
const { act } = useBackend(context);
|
||||
const [step, setStep] = useLocalState(context, `${filterName}-${name}`, 0.01);
|
||||
return (
|
||||
<Fragment>
|
||||
<NumberInput
|
||||
value={value}
|
||||
minValue={-500}
|
||||
maxValue={500}
|
||||
stepPixelSize={4}
|
||||
step={step}
|
||||
format={value => toFixed(value, numberOfDecimalDigits(step))}
|
||||
width="80px"
|
||||
onDrag={(e, value) => act('transition_filter_value', {
|
||||
name: filterName,
|
||||
new_data: {
|
||||
[name]: value,
|
||||
},
|
||||
})} />
|
||||
<Box
|
||||
inline
|
||||
ml={2}
|
||||
mr={1}>
|
||||
Step:
|
||||
</Box>
|
||||
<NumberInput
|
||||
value={step}
|
||||
step={0.001}
|
||||
format={value => toFixed(value, 4)}
|
||||
width="70px"
|
||||
onChange={(e, value) => setStep(value)} />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const FilterTextEntry = (props, context) => {
|
||||
const { value, name, filterName } = props;
|
||||
const { act } = useBackend(context);
|
||||
|
||||
return (
|
||||
<Input
|
||||
value={value}
|
||||
width="250px"
|
||||
onInput={(e, value) => act('modify_filter_value', {
|
||||
name: filterName,
|
||||
new_data: {
|
||||
[name]: value,
|
||||
},
|
||||
})} />
|
||||
);
|
||||
};
|
||||
|
||||
const FilterColorEntry = (props, context) => {
|
||||
const { value, filterName, name } = props;
|
||||
const { act } = useBackend(context);
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
icon="pencil-alt"
|
||||
onClick={() => act('modify_color_value', {
|
||||
name: filterName,
|
||||
})} />
|
||||
<ColorBox
|
||||
color={value}
|
||||
mr={0.5} />
|
||||
<Input
|
||||
value={value}
|
||||
width="90px"
|
||||
onInput={(e, value) => act('transition_filter_value', {
|
||||
name: filterName,
|
||||
new_data: {
|
||||
[name]: value,
|
||||
},
|
||||
})} />
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const FilterIconEntry = (props, context) => {
|
||||
const { value, filterName } = props;
|
||||
const { act } = useBackend(context);
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
icon="pencil-alt"
|
||||
onClick={() => act('modify_icon_value', {
|
||||
name: filterName,
|
||||
})} />
|
||||
<Box inline ml={1}>
|
||||
{value}
|
||||
</Box>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const FilterFlagsEntry = (props, context) => {
|
||||
const { name, value, filterName, filterType } = props;
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const filterInfo = data.filter_info;
|
||||
const flags = filterInfo[filterType]['flags'];
|
||||
return (
|
||||
map((bitField, flagName) => (
|
||||
<Button.Checkbox
|
||||
checked={value & bitField}
|
||||
content={flagName}
|
||||
onClick={() => act('modify_filter_value', {
|
||||
name: filterName,
|
||||
new_data: {
|
||||
[name]: value ^ bitField,
|
||||
},
|
||||
})} />
|
||||
))(flags)
|
||||
);
|
||||
};
|
||||
|
||||
const FilterDataEntry = (props, context) => {
|
||||
const { name, value, hasValue, filterName } = props;
|
||||
|
||||
const filterEntryTypes = {
|
||||
int: <FilterIntegerEntry {...props} />,
|
||||
float: <FilterFloatEntry {...props} />,
|
||||
string: <FilterTextEntry {...props} />,
|
||||
color: <FilterColorEntry {...props} />,
|
||||
icon: <FilterIconEntry {...props} />,
|
||||
flags: <FilterFlagsEntry {...props} />,
|
||||
};
|
||||
|
||||
const filterEntryMap = {
|
||||
x: 'float',
|
||||
y: 'float',
|
||||
icon: 'icon',
|
||||
render_source: 'string',
|
||||
flags: 'flags',
|
||||
size: 'float',
|
||||
color: 'color',
|
||||
offset: 'float',
|
||||
radius: 'float',
|
||||
falloff: 'float',
|
||||
density: 'int',
|
||||
threshold: 'float',
|
||||
factor: 'float',
|
||||
repeat: 'int',
|
||||
};
|
||||
|
||||
return (
|
||||
<LabeledList.Item label={name}>
|
||||
{filterEntryTypes[filterEntryMap[name]] || "Not Found (This is an error)"}
|
||||
{' '}
|
||||
{!hasValue && <Box inline color="average">(Default)</Box>}
|
||||
</LabeledList.Item>
|
||||
);
|
||||
};
|
||||
|
||||
const FilterEntry = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { name, filterDataEntry } = props;
|
||||
const { type, priority, ...restOfProps } = filterDataEntry;
|
||||
|
||||
const filterDefaults = data["filter_info"];
|
||||
|
||||
const targetFilterPossibleKeys = Object.keys(filterDefaults[type]['defaults']);
|
||||
|
||||
return (
|
||||
<Collapsible
|
||||
title={name + " (" + type + ")"}
|
||||
buttons={(
|
||||
<Fragment>
|
||||
<NumberInput
|
||||
value={priority}
|
||||
stepPixelSize={10}
|
||||
width="60px"
|
||||
onChange={(e, value) => act('change_priority', {
|
||||
name: name,
|
||||
new_priority: value,
|
||||
})}
|
||||
/>
|
||||
<Button.Input
|
||||
content="Rename"
|
||||
placeholder={name}
|
||||
onCommit={(e, new_name) => act('rename_filter', {
|
||||
name: name,
|
||||
new_name: new_name,
|
||||
})}
|
||||
width="90px" />
|
||||
<Button.Confirm
|
||||
icon="minus"
|
||||
onClick={() => act("remove_filter", { name: name })} />
|
||||
</Fragment>
|
||||
)}>
|
||||
<Section level={2}>
|
||||
<LabeledList>
|
||||
{targetFilterPossibleKeys.map(entryName => {
|
||||
const defaults = filterDefaults[type]['defaults'];
|
||||
const value = restOfProps[entryName] || defaults[entryName];
|
||||
const hasValue = value !== defaults[entryName];
|
||||
return (
|
||||
<FilterDataEntry
|
||||
key={entryName}
|
||||
filterName={name}
|
||||
filterType={type}
|
||||
name={entryName}
|
||||
value={value}
|
||||
hasValue={hasValue} />
|
||||
);
|
||||
})}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Collapsible>
|
||||
);
|
||||
};
|
||||
|
||||
export const Filteriffic = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const name = data.target_name || "Unknown Object";
|
||||
const filters = data.target_filter_data || {};
|
||||
const hasFilters = filters !== {};
|
||||
const filterDefaults = data["filter_info"];
|
||||
const [massApplyPath, setMassApplyPath] = useLocalState(context, 'massApplyPath', '');
|
||||
const [hiddenSecret, setHiddenSecret] = useLocalState(context, 'hidden', false);
|
||||
return (
|
||||
<Window
|
||||
width={500}
|
||||
height={500}
|
||||
title="Filteriffic"
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
<NoticeBox danger>
|
||||
DO NOT MESS WITH EXISTING FILTERS IF YOU DO NOT KNOW THE CONSEQUENCES.
|
||||
YOU HAVE BEEN WARNED.
|
||||
</NoticeBox>
|
||||
<Section
|
||||
title={hiddenSecret ? (
|
||||
<Fragment>
|
||||
<Box mr={0.5} inline>
|
||||
MASS EDIT:
|
||||
</Box>
|
||||
<Input
|
||||
value={massApplyPath}
|
||||
width="100px"
|
||||
onInput={(e, value) => setMassApplyPath(value)} />
|
||||
<Button.Confirm
|
||||
content="Apply"
|
||||
confirmContent="ARE YOU SURE?"
|
||||
onClick={() => act('mass_apply', { path: massApplyPath })} />
|
||||
</Fragment>
|
||||
) : (
|
||||
<Box
|
||||
inline
|
||||
onDblClick={() => setHiddenSecret(true)}>
|
||||
{name}
|
||||
</Box>
|
||||
)}
|
||||
buttons={(
|
||||
<Dropdown
|
||||
icon="plus"
|
||||
displayText="Add Filter"
|
||||
nochevron
|
||||
options={Object.keys(filterDefaults)}
|
||||
onSelected={value => act('add_filter', {
|
||||
name: 'default',
|
||||
priority: 10,
|
||||
type: value,
|
||||
})} />
|
||||
)} >
|
||||
{!hasFilters ? (
|
||||
<Box>
|
||||
No filters
|
||||
</Box>
|
||||
) : (
|
||||
map((entry, key) => (
|
||||
<FilterEntry filterDataEntry={entry} name={key} key={key} />
|
||||
))(filters)
|
||||
)}
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, Flex, Fragment, Section, NoticeBox } from '../components';
|
||||
import { Button, Flex, NoticeBox, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const GhostPoolProtection = (props, context) => {
|
||||
|
||||
@@ -6,11 +6,11 @@ export const Holodeck = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
can_toggle_safety,
|
||||
default_programs = [],
|
||||
emag_programs = [],
|
||||
emagged,
|
||||
program,
|
||||
} = data;
|
||||
const default_programs = data.default_programs || [];
|
||||
const emag_programs = data.emag_programs || [];
|
||||
return (
|
||||
<Window
|
||||
width={400}
|
||||
|
||||
@@ -68,8 +68,8 @@ const HolopadContent = (props, context) => {
|
||||
<Button
|
||||
icon="bell"
|
||||
content={on_cooldown
|
||||
? "AI Presence Requested"
|
||||
: "Request AI Presence"}
|
||||
? "AI's Presence Requested"
|
||||
: "Request AI's Presence"}
|
||||
disabled={!on_network || on_cooldown}
|
||||
onClick={() => act('AIrequest')} />
|
||||
)} >
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Section, LabeledList, NumberInput } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const MassDriverControl = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
connected,
|
||||
minutes,
|
||||
seconds,
|
||||
timing,
|
||||
power,
|
||||
poddoor,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={300}
|
||||
height={connected ? 215 : 107}>
|
||||
<Window.Content>
|
||||
<Fragment>
|
||||
{!!connected && (
|
||||
<Section
|
||||
title="Auto Launch"
|
||||
buttons={(
|
||||
<Button
|
||||
icon={"clock-o"}
|
||||
content={timing ? 'Stop' : 'Start'}
|
||||
selected={timing}
|
||||
onClick={() => act('time')} />
|
||||
)}>
|
||||
<Button
|
||||
icon="fast-backward"
|
||||
disabled={timing}
|
||||
onClick={() => act('input', { adjust: -30 })} />
|
||||
<Button
|
||||
icon="backward"
|
||||
disabled={timing}
|
||||
onClick={() => act('input', { adjust: -1 })} />
|
||||
{' '}
|
||||
{String(minutes).padStart(2, '0')}:
|
||||
{String(seconds).padStart(2, '0')}
|
||||
{' '}
|
||||
<Button
|
||||
icon="forward"
|
||||
disabled={timing}
|
||||
onClick={() => act('input', { adjust: 1 })} />
|
||||
<Button
|
||||
icon="fast-forward"
|
||||
disabled={timing}
|
||||
onClick={() => act('input', { adjust: 30 })} />
|
||||
</Section>
|
||||
)}
|
||||
<Section
|
||||
title="Controls"
|
||||
buttons={(
|
||||
<Button
|
||||
icon={"toggle-on"}
|
||||
content="Toggle Outer Door"
|
||||
disabled={timing || !poddoor}
|
||||
onClick={() => act('door')} />
|
||||
)}>
|
||||
{!!connected && (
|
||||
<Fragment>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Power Level"
|
||||
buttons={
|
||||
<Button
|
||||
icon={"bomb"}
|
||||
content="Test Fire"
|
||||
disabled={timing}
|
||||
onClick={() => act('driver_test')} />
|
||||
}>
|
||||
<NumberInput
|
||||
value={power}
|
||||
width="40px"
|
||||
minValue={0.25}
|
||||
maxValue={16}
|
||||
onChange={(e, value) => {
|
||||
return act('set_power', {
|
||||
power: value,
|
||||
});
|
||||
}} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
<Button
|
||||
fluid
|
||||
content="Launch"
|
||||
disabled={timing}
|
||||
mt={1.5}
|
||||
icon="arrow-up"
|
||||
textAlign="center"
|
||||
onClick={() => act('launch')} />
|
||||
</Fragment>
|
||||
) || (
|
||||
<Box color="bad">
|
||||
No connected mass driver
|
||||
</Box>
|
||||
)}
|
||||
</Section>
|
||||
</Fragment>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* THIS FILE WILL SOON BE DEPRICATED!
|
||||
*/
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { CargoContent } from './Cargo.js';
|
||||
import { NtosWindow } from '../layouts';
|
||||
|
||||
export const NtosCargo = (props, context) => {
|
||||
return (
|
||||
<NtosWindow
|
||||
width={800}
|
||||
height={500}
|
||||
resizable>
|
||||
<NtosWindow.Content scrollable>
|
||||
<CargoContent />
|
||||
</NtosWindow.Content>
|
||||
</NtosWindow>
|
||||
);
|
||||
};
|
||||
@@ -24,7 +24,8 @@ export const NtosFileManager = (props, context) => {
|
||||
name: file,
|
||||
new_name: newName,
|
||||
})}
|
||||
onDuplicate={file => act('PRG_clone', { file: file })} />
|
||||
onDuplicate={file => act('PRG_clone', { file: file })}
|
||||
onToggleSilence={file => act('PRG_togglesilence', { name: file })} />
|
||||
</Section>
|
||||
{usbconnected && (
|
||||
<Section title="Data Disk">
|
||||
@@ -54,6 +55,7 @@ const FileTable = props => {
|
||||
onUpload,
|
||||
onDelete,
|
||||
onRename,
|
||||
onToggleSilence,
|
||||
} = props;
|
||||
return (
|
||||
<Table>
|
||||
@@ -89,6 +91,13 @@ const FileTable = props => {
|
||||
{file.size}
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing>
|
||||
{!!file.alert_able && (
|
||||
<Button
|
||||
icon={file.alert_silenced ? 'bell-slash' : 'bell'}
|
||||
color={file.alert_silenced ? 'red' : 'default'}
|
||||
tooltip={file.alert_silenced ? 'Unmute Alerts' : 'Mute Alerts'}
|
||||
onClick={() => onToggleSilence(file.name)} />
|
||||
)}
|
||||
{!file.undeletable && (
|
||||
<Fragment>
|
||||
<Button.Confirm
|
||||
|
||||
@@ -2,24 +2,6 @@ import { useBackend } from '../backend';
|
||||
import { Button, ColorBox, Section, Table } from '../components';
|
||||
import { NtosWindow } from '../layouts';
|
||||
|
||||
const PROGRAM_ICONS = {
|
||||
compconfig: 'cog',
|
||||
ntndownloader: 'download',
|
||||
filemanager: 'folder',
|
||||
smmonitor: 'radiation',
|
||||
alarmmonitor: 'bell',
|
||||
cardmod: 'id-card',
|
||||
arcade: 'gamepad',
|
||||
ntnrc_client: 'comment-alt',
|
||||
nttransfer: 'exchange-alt',
|
||||
powermonitor: 'plug',
|
||||
job_manage: 'address-book',
|
||||
crewmani: 'clipboard-list',
|
||||
robocontrol: 'robot',
|
||||
atmosscan: 'thermometer-half',
|
||||
shipping: 'tags',
|
||||
};
|
||||
|
||||
export const NtosMain = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
@@ -29,6 +11,7 @@ export const NtosMain = (props, context) => {
|
||||
light_on,
|
||||
comp_light_color,
|
||||
removable_media = [],
|
||||
cardholder,
|
||||
login = [],
|
||||
} = data;
|
||||
return (
|
||||
@@ -58,25 +41,27 @@ export const NtosMain = (props, context) => {
|
||||
</Button>
|
||||
</Section>
|
||||
)}
|
||||
<Section
|
||||
title="User Login"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject ID"
|
||||
disabled={!login.IDName}
|
||||
onClick={() => act('PC_Eject_Disk', { name: "ID" })}
|
||||
/>
|
||||
)}>
|
||||
<Table>
|
||||
<Table.Row>
|
||||
ID Name: {login.IDName}
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
Assignment: {login.IDJob}
|
||||
</Table.Row>
|
||||
</Table>
|
||||
</Section>
|
||||
{!!cardholder && (
|
||||
<Section
|
||||
title="User Login"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Eject ID"
|
||||
disabled={!login.IDName}
|
||||
onClick={() => act('PC_Eject_Disk', { name: "ID" })}
|
||||
/>
|
||||
)}>
|
||||
<Table>
|
||||
<Table.Row>
|
||||
ID Name: {login.IDName}
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
Assignment: {login.IDJob}
|
||||
</Table.Row>
|
||||
</Table>
|
||||
</Section>
|
||||
)}
|
||||
{!!removable_media.length && (
|
||||
<Section title="Media Eject">
|
||||
<Table>
|
||||
@@ -103,9 +88,8 @@ export const NtosMain = (props, context) => {
|
||||
<Table.Cell>
|
||||
<Button
|
||||
fluid
|
||||
color="transparent"
|
||||
icon={PROGRAM_ICONS[program.name]
|
||||
|| 'window-maximize-o'}
|
||||
color={program.alert ? 'yellow' : 'transparent'}
|
||||
icon={program.icon}
|
||||
content={program.desc}
|
||||
onClick={() => act('PC_runprogram', {
|
||||
name: program.name,
|
||||
|
||||
@@ -43,11 +43,20 @@ export const NtosNetDownloader = (props, context) => {
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section>
|
||||
{downloadable_programs.map(program => (
|
||||
<Program
|
||||
key={program.filename}
|
||||
program={program} />
|
||||
))}
|
||||
{downloadable_programs
|
||||
.filter(program => program.access)
|
||||
.map(program => (
|
||||
<Program
|
||||
key={program.filename}
|
||||
program={program} />
|
||||
))}
|
||||
{downloadable_programs
|
||||
.filter(program => !program.access)
|
||||
.map(program => (
|
||||
<Program
|
||||
key={program.filename}
|
||||
program={program} />
|
||||
))}
|
||||
</Section>
|
||||
{!!hackedavailable && (
|
||||
<Section title="UNKNOWN Software Repository">
|
||||
@@ -100,7 +109,9 @@ const Program = (props, context) => {
|
||||
fluid
|
||||
icon="download"
|
||||
content="Download"
|
||||
disabled={downloading || program.size > disk_free}
|
||||
disabled={
|
||||
downloading || program.size > disk_free || !program.access
|
||||
}
|
||||
onClick={() => act('PRG_downloadfile', {
|
||||
filename: program.filename,
|
||||
})} />
|
||||
@@ -113,6 +124,12 @@ const Program = (props, context) => {
|
||||
Incompatible!
|
||||
</Box>
|
||||
)}
|
||||
{!(program.access) && (
|
||||
<Box mt={1} italic fontSize="12px" position="relative">
|
||||
<Icon mx={1} color="red" name="times" />
|
||||
Invalid credentials loaded!
|
||||
</Box>
|
||||
)}
|
||||
{program.size > disk_free && (
|
||||
<Box mt={1} italic fontSize="12px" position="relative">
|
||||
<Icon mx={1} color="red" name="times" />
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useSharedState } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, Flex, LabeledList, ProgressBar, Section, Slider, Tabs } from '../components';
|
||||
import { NtosWindow } from '../layouts';
|
||||
|
||||
export const NtosRobotact = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { PC_device_theme } = data;
|
||||
return (
|
||||
<NtosWindow
|
||||
width={800}
|
||||
height={600}
|
||||
theme={PC_device_theme}>
|
||||
<NtosWindow.Content>
|
||||
<NtosRobotactContent />
|
||||
</NtosWindow.Content>
|
||||
</NtosWindow>
|
||||
);
|
||||
};
|
||||
|
||||
export const NtosRobotactContent = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const [tab_main, setTab_main] = useSharedState(context, 'tab_main', 1);
|
||||
const [tab_sub, setTab_sub] = useSharedState(context, 'tab_sub', 1);
|
||||
const {
|
||||
charge,
|
||||
maxcharge,
|
||||
integrity,
|
||||
lampIntensity,
|
||||
cover,
|
||||
locomotion,
|
||||
wireModule,
|
||||
wireCamera,
|
||||
wireAI,
|
||||
wireLaw,
|
||||
sensors,
|
||||
printerPictures,
|
||||
printerToner,
|
||||
printerTonerMax,
|
||||
thrustersInstalled,
|
||||
thrustersStatus,
|
||||
} = data;
|
||||
const borgName = data.name || [];
|
||||
const borgType = data.designation || [];
|
||||
const masterAI = data.masterAI || [];
|
||||
const laws = data.Laws || [];
|
||||
const borgLog = data.borgLog || [];
|
||||
const borgUpgrades = data.borgUpgrades || [];
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}>
|
||||
<Flex.Item
|
||||
position="relative"
|
||||
mb={1}>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
lineHeight="23px"
|
||||
selected={tab_main === 1}
|
||||
onClick={() => setTab_main(1)}>
|
||||
Status
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
lineHeight="23px"
|
||||
selected={tab_main === 2}
|
||||
onClick={() => setTab_main(2)}>
|
||||
Logs
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
</Flex.Item>
|
||||
{tab_main === 1 && (
|
||||
<Fragment>
|
||||
<Flex
|
||||
direction={"row"}>
|
||||
<Flex.Item
|
||||
width="30%">
|
||||
<Section
|
||||
title="Configuration"
|
||||
fill>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Unit">
|
||||
{borgName.slice(0, 17)}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Type">
|
||||
{borgType}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="AI">
|
||||
{masterAI.slice(0, 17)}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
grow={1}
|
||||
ml={1}>
|
||||
<Section
|
||||
title="Status">
|
||||
Charge:
|
||||
<Button
|
||||
content="Power Alert"
|
||||
disabled={charge}
|
||||
onClick={() => act('alertPower')} />
|
||||
<ProgressBar
|
||||
value={charge / maxcharge}
|
||||
ranges={{
|
||||
good: [0.5, Infinity],
|
||||
average: [0.1, 0.5],
|
||||
bad: [-Infinity, 0.1],
|
||||
}}>
|
||||
<AnimatedNumber value={charge} />
|
||||
</ProgressBar>
|
||||
Chassis Integrity:
|
||||
<ProgressBar
|
||||
value={integrity}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
ranges={{
|
||||
bad: [-Infinity, 25],
|
||||
average: [25, 75],
|
||||
good: [75, Infinity],
|
||||
}} />
|
||||
</Section>
|
||||
<Section
|
||||
title="Lamp Power">
|
||||
<Slider
|
||||
value={lampIntensity}
|
||||
step={1}
|
||||
stepPixelSize={25}
|
||||
maxValue={5}
|
||||
minValue={1}
|
||||
onChange={(e, value) => act('lampIntensity', {
|
||||
ref: value,
|
||||
})} />
|
||||
Lamp power usage: {lampIntensity/2} watts
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
width="50%"
|
||||
ml={1}>
|
||||
<Section
|
||||
fitted>
|
||||
<Tabs
|
||||
fluid={1}
|
||||
textAlign="center">
|
||||
<Tabs.Tab
|
||||
icon=""
|
||||
lineHeight="23px"
|
||||
selected={tab_sub === 1}
|
||||
onClick={() => setTab_sub(1)}>
|
||||
Actions
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
icon=""
|
||||
lineHeight="23px"
|
||||
selected={tab_sub === 2}
|
||||
onClick={() => setTab_sub(2)}>
|
||||
Upgrades
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
icon=""
|
||||
lineHeight="23px"
|
||||
selected={tab_sub === 3}
|
||||
onClick={() => setTab_sub(3)}>
|
||||
Diagnostics
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
</Section>
|
||||
{tab_sub === 1 && (
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Maintenance Cover">
|
||||
<Button.Confirm
|
||||
content="Unlock"
|
||||
disabled={cover==="UNLOCKED"}
|
||||
onClick={() => act('coverunlock')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Sensor Overlay">
|
||||
<Button
|
||||
content={sensors}
|
||||
onClick={() => act('toggleSensors')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label={"Stored Photos (" + printerPictures + ")"}>
|
||||
<Button
|
||||
content="View"
|
||||
disabled={!printerPictures}
|
||||
onClick={() => act('viewImage')} />
|
||||
<Button
|
||||
content="Print"
|
||||
disabled={!printerPictures}
|
||||
onClick={() => act('printImage')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Printer Toner">
|
||||
<ProgressBar
|
||||
value={printerToner / printerTonerMax} />
|
||||
</LabeledList.Item>
|
||||
{!!thrustersInstalled && (
|
||||
<LabeledList.Item
|
||||
label="Toggle Thrusters">
|
||||
<Button
|
||||
content={thrustersStatus}
|
||||
onClick={() => act('toggleThrusters')} />
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
)}
|
||||
{tab_sub === 2 && (
|
||||
<Section>
|
||||
{borgUpgrades.map(upgrade => (
|
||||
<Box
|
||||
mb={1}
|
||||
key={upgrade}>
|
||||
{upgrade}
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
)}
|
||||
{tab_sub === 3 && (
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="AI Connection"
|
||||
color={wireAI==="FAULT"?'red': wireAI==="READY"?'yellow': 'green'}>
|
||||
{wireAI}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="LawSync"
|
||||
color={wireLaw==="FAULT"?"red":"green"}>
|
||||
{wireLaw}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Camera"
|
||||
color={wireCamera==="FAULT"?'red': wireCamera==="DISABLED"?'yellow': 'green'}>
|
||||
{wireCamera}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Module Controller"
|
||||
color={wireModule==="FAULT"?"red":"green"}>
|
||||
{wireModule}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Motor Controller"
|
||||
color={locomotion==="FAULT"?'red': locomotion==="DISABLED"?'yellow': 'green'}>
|
||||
{locomotion}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Maintenance Cover"
|
||||
color={cover==="UNLOCKED"?"red":"green"}>
|
||||
{cover}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
)}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex.Item
|
||||
height={21}
|
||||
mt={1}>
|
||||
<Section
|
||||
title="Laws"
|
||||
fill
|
||||
scrollable
|
||||
buttons={(
|
||||
<Fragment>
|
||||
<Button
|
||||
content="State Laws"
|
||||
onClick={() => act('lawstate')} />
|
||||
<Button
|
||||
icon="volume-off"
|
||||
onClick={() => act('lawchannel')} />
|
||||
</Fragment>
|
||||
)}>
|
||||
{laws.map(law => (
|
||||
<Box
|
||||
mb={1}
|
||||
key={law}>
|
||||
{law}
|
||||
</Box>
|
||||
))}
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Fragment>
|
||||
)}
|
||||
{tab_main === 2 && (
|
||||
<Flex.Item>
|
||||
<Section
|
||||
backgroundColor="black"
|
||||
height={40}>
|
||||
<NtosWindow.Content scrollable>
|
||||
{borgLog.map(log => (
|
||||
<Box
|
||||
mb={1}
|
||||
key={log}>
|
||||
<font color="green">{log}</font>
|
||||
</Box>
|
||||
))}
|
||||
</NtosWindow.Content>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, ByondUi } from '../components';
|
||||
import { NtosWindow } from '../layouts';
|
||||
import { prevNextCamera, selectCameras, CameraConsoleContent } from './CameraConsole';
|
||||
|
||||
export const NtosSecurEye = (props, context) => {
|
||||
const { act, data, config } = useBackend(context);
|
||||
const { PC_device_theme, mapRef, activeCamera } = data;
|
||||
const cameras = selectCameras(data.cameras);
|
||||
const [
|
||||
prevCameraName,
|
||||
nextCameraName,
|
||||
] = prevNextCamera(cameras, activeCamera);
|
||||
return (
|
||||
<NtosWindow
|
||||
width={800}
|
||||
height={600}
|
||||
theme={PC_device_theme}>
|
||||
<NtosWindow.Content>
|
||||
<div className="CameraConsole__left">
|
||||
<CameraConsoleContent />
|
||||
</div>
|
||||
<div className="CameraConsole__right">
|
||||
<div className="CameraConsole__toolbar">
|
||||
<b>Camera: </b>
|
||||
{activeCamera
|
||||
&& activeCamera.name
|
||||
|| '—'}
|
||||
</div>
|
||||
<div className="CameraConsole__toolbarRight">
|
||||
<Button
|
||||
icon="chevron-left"
|
||||
disabled={!prevCameraName}
|
||||
onClick={() => act('switch_camera', {
|
||||
name: prevCameraName,
|
||||
})} />
|
||||
<Button
|
||||
icon="chevron-right"
|
||||
disabled={!nextCameraName}
|
||||
onClick={() => act('switch_camera', {
|
||||
name: nextCameraName,
|
||||
})} />
|
||||
</div>
|
||||
<ByondUi
|
||||
className="CameraConsole__map"
|
||||
params={{
|
||||
id: mapRef,
|
||||
type: 'map',
|
||||
}} />
|
||||
</div>
|
||||
</NtosWindow.Content>
|
||||
</NtosWindow>
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
import { createSearch } from 'common/string';
|
||||
import { multiline } from 'common/string';
|
||||
import { resolveAsset } from '../assets';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, Flex, Icon, Input, Section } from '../components';
|
||||
import { Box, Button, Divider, Flex, Icon, Input, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const PATTERN_DESCRIPTOR = / \[(?:ghost|dead)\]$/;
|
||||
const PATTERN_NUMBER = / \(([0-9]+)\)$/;
|
||||
|
||||
const searchFor = searchText => createSearch(searchText, thing => thing.name);
|
||||
@@ -43,7 +43,7 @@ const BasicSection = (props, context) => {
|
||||
{things.map(thing => (
|
||||
<Button
|
||||
key={thing.name}
|
||||
content={thing.name.replace(PATTERN_DESCRIPTOR, "")}
|
||||
content={thing.name}
|
||||
onClick={() => act("orbit", {
|
||||
ref: thing.ref,
|
||||
})} />
|
||||
@@ -82,6 +82,7 @@ export const Orbit = (props, context) => {
|
||||
const {
|
||||
alive,
|
||||
antagonists,
|
||||
auto_observe,
|
||||
dead,
|
||||
ghosts,
|
||||
misc,
|
||||
@@ -140,6 +141,27 @@ export const Orbit = (props, context) => {
|
||||
onInput={(_, value) => setSearchText(value)}
|
||||
onEnter={(_, value) => orbitMostRelevant(value)} />
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Divider vertical />
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Button
|
||||
inline
|
||||
color="transparent"
|
||||
tooltip={multiline`Toggle Auto-Observe. When active, you'll
|
||||
see the UI / full inventory of whoever you're orbiting. Neat!`}
|
||||
tooltipPosition="bottom-left"
|
||||
selected={auto_observe}
|
||||
icon={auto_observe ? "toggle-on" : "toggle-off"}
|
||||
onClick={() => act("toggle_observe")} />
|
||||
<Button
|
||||
inline
|
||||
color="transparent"
|
||||
tooltip="Refresh"
|
||||
tooltipPosition="bottom-left"
|
||||
icon="sync-alt"
|
||||
onClick={() => act("refresh")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
{antagonists.length > 0 && (
|
||||
@@ -161,7 +183,7 @@ export const Orbit = (props, context) => {
|
||||
</Section>
|
||||
)}
|
||||
|
||||
<Section title="Alive">
|
||||
<Section title={`Alive - (${alive.length})`}>
|
||||
{alive
|
||||
.filter(searchFor(searchText))
|
||||
.sort(compareNumberedText)
|
||||
@@ -173,11 +195,17 @@ export const Orbit = (props, context) => {
|
||||
))}
|
||||
</Section>
|
||||
|
||||
<BasicSection
|
||||
title="Ghosts"
|
||||
source={ghosts}
|
||||
searchText={searchText}
|
||||
/>
|
||||
<Section title={`Ghosts - (${ghosts.length})`}>
|
||||
{ghosts
|
||||
.filter(searchFor(searchText))
|
||||
.sort(compareNumberedText)
|
||||
.map(thing => (
|
||||
<OrbitedButton
|
||||
key={thing.name}
|
||||
color="grey"
|
||||
thing={thing} />
|
||||
))}
|
||||
</Section>
|
||||
|
||||
<BasicSection
|
||||
title="Dead"
|
||||
|
||||
@@ -3,37 +3,21 @@
|
||||
* @copyright 2020 WarlockD (https://github.com/warlockd)
|
||||
* @author Original WarlockD (https://github.com/warlockd)
|
||||
* @author Changes stylemistake
|
||||
* @author Changes ThePotato97
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { classes } from 'common/react';
|
||||
import { vecScale, vecSubtract } from 'common/vector';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { Fragment } from 'inferno';
|
||||
import { Component } from 'inferno';
|
||||
import marked from 'marked';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Flex, Tabs, TextArea } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
import { clamp } from 'common/math';
|
||||
import { sanitizeText } from '../sanitize';
|
||||
const MAX_PAPER_LENGTH = 5000; // Question, should we send this with ui_data?
|
||||
|
||||
const sanatize_text = value => {
|
||||
// This is VERY important to think first if you NEED
|
||||
// the tag you put in here. We are pushing all this
|
||||
// though dangerouslySetInnerHTML and even though
|
||||
// the default DOMPurify kills javascript, it dosn't
|
||||
// kill href links or such
|
||||
return DOMPurify.sanitize(value, {
|
||||
FORBID_ATTR: ['class', 'style'],
|
||||
ALLOWED_TAGS: [
|
||||
'br', 'code', 'li', 'p', 'pre',
|
||||
'span', 'table', 'td', 'tr',
|
||||
'th', 'ul', 'ol', 'menu', 'font', 'b',
|
||||
'center', 'table', 'tr', 'th',
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
// Hacky, yes, works?...yes
|
||||
const textWidth = (text, font, fontsize) => {
|
||||
// default font height is 12 in tgui
|
||||
@@ -59,11 +43,11 @@ const createIDHeader = index => {
|
||||
return "paperfield_" + index;
|
||||
};
|
||||
// To make a field you do a [_______] or however long the field is
|
||||
// we will then output a TEXT input for it that hopefuly covers
|
||||
// we will then output a TEXT input for it that hopefully covers
|
||||
// the exact amount of spaces
|
||||
const field_regex = /\[(_+)\]/g;
|
||||
const field_tag_regex = /\[<input\s+(.*?)id="(?<id>paperfield_\d+)"(.*?)\/>\]/gm;
|
||||
const sign_regex = /%s(?:ign)?(?=\\s|$)/igm;
|
||||
const field_tag_regex = /\[<input\s+(?!disabled)(.*?)\s+id="(?<id>paperfield_\d+)"(.*?)\/>\]/gm;
|
||||
const sign_regex = /%s(?:ign)?(?=\\s|$)?/igm;
|
||||
|
||||
const createInputField = (length, width, font,
|
||||
fontsize, color, id) => {
|
||||
@@ -156,13 +140,11 @@ const checkAllFields = (txt, font, color, user_name, bold=false) => {
|
||||
if (dom_text.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const sanitized_text = DOMPurify.sanitize(dom.value.trim(), {
|
||||
ALLOWED_TAGS: [],
|
||||
});
|
||||
const sanitized_text = sanitizeText(dom.value.trim(), []);
|
||||
if (sanitized_text.length === 0) {
|
||||
continue;
|
||||
}
|
||||
// this is easyer than doing a bunch of text manipulations
|
||||
// this is easier than doing a bunch of text manipulations
|
||||
const target = dom.cloneNode(true);
|
||||
// in case they sign in a field
|
||||
if (sanitized_text.match(sign_regex)) {
|
||||
@@ -206,27 +188,25 @@ const Stamp = (props, context) => {
|
||||
const {
|
||||
image,
|
||||
opacity,
|
||||
...rest
|
||||
} = props;
|
||||
const matrix_trasform = 'rotate(' + image.rotate
|
||||
+ 'deg) translate(' + image.x + 'px,' + image.y + 'px)';
|
||||
const stamp_trasform = {
|
||||
'transform': matrix_trasform,
|
||||
'-ms-transform': matrix_trasform,
|
||||
'-webkit-transform': matrix_trasform,
|
||||
const stamp_transform = {
|
||||
'left': image.x + 'px',
|
||||
'top': image.y + 'px',
|
||||
'transform': 'rotate(' + image.rotate + 'deg)',
|
||||
'opacity': opacity || 1.0,
|
||||
'position': 'absolute',
|
||||
};
|
||||
return (
|
||||
<div
|
||||
id="stamp"
|
||||
className={classes([
|
||||
'paper121x54',
|
||||
'Paper__Stamp',
|
||||
image.sprite,
|
||||
])}
|
||||
style={stamp_trasform} />
|
||||
style={stamp_transform} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const setInputReadonly = (text, readonly) => {
|
||||
return readonly
|
||||
? text.replace(/<input\s[^d]/g, '<input disabled ')
|
||||
@@ -237,12 +217,12 @@ const setInputReadonly = (text, readonly) => {
|
||||
// want to control updates
|
||||
const PaperSheetView = (props, context) => {
|
||||
const {
|
||||
value,
|
||||
stamps,
|
||||
value = "",
|
||||
stamps = [],
|
||||
backgroundColor,
|
||||
readOnly,
|
||||
} = props;
|
||||
const stamp_list = stamps || [];
|
||||
const stamp_list = stamps;
|
||||
const text_html = {
|
||||
__html: '<span class="paper-text">'
|
||||
+ setInputReadonly(value, readOnly)
|
||||
@@ -255,6 +235,8 @@ const PaperSheetView = (props, context) => {
|
||||
width="100%"
|
||||
height="100%" >
|
||||
<Box
|
||||
className="Paper__Page"
|
||||
color="black"
|
||||
fillPositionedParent
|
||||
width="100%"
|
||||
height="100%"
|
||||
@@ -277,70 +259,76 @@ class PaperSheetStamper extends Component {
|
||||
y: 0,
|
||||
rotate: 0,
|
||||
};
|
||||
this.style = null;
|
||||
this.handleMouseMove = e => {
|
||||
const pos = this.findStampPosition(e);
|
||||
if (!pos) { return; }
|
||||
// center offset of stamp & rotate
|
||||
pauseEvent(e);
|
||||
this.setState({ x: pos[0], y: pos[1], rotate: pos[2] });
|
||||
};
|
||||
this.handleMouseClick = e => {
|
||||
if (e.pageY <= 30) { return; }
|
||||
const { act, data } = useBackend(this.context);
|
||||
const stamp_obj = {
|
||||
x: this.state.x, y: this.state.y, r: this.state.rotate,
|
||||
stamp_class: this.props.stamp_class,
|
||||
stamp_icon_state: data.stamp_icon_state,
|
||||
};
|
||||
act("stamp", stamp_obj);
|
||||
};
|
||||
}
|
||||
|
||||
findStampPosition(e) {
|
||||
const position = {
|
||||
x: event.pageX,
|
||||
y: event.pageY,
|
||||
};
|
||||
|
||||
const offset = {
|
||||
left: e.target.offsetLeft,
|
||||
top: e.target.offsetTop,
|
||||
};
|
||||
|
||||
let reference = e.target.offsetParent;
|
||||
|
||||
while (reference) {
|
||||
offset.left += reference.offsetLeft;
|
||||
offset.top += reference.offsetTop;
|
||||
reference = reference.offsetParent;
|
||||
let rotating;
|
||||
const windowRef = document.querySelector('.Layout__content');
|
||||
if (e.shiftKey) {
|
||||
rotating = true;
|
||||
}
|
||||
|
||||
const pos = [
|
||||
position.x - offset.left,
|
||||
position.y - offset.top,
|
||||
];
|
||||
const centerOffset = vecScale([121, 51], 0.5);
|
||||
const center = vecSubtract(pos, centerOffset);
|
||||
return center;
|
||||
if (document.getElementById("stamp"))
|
||||
{
|
||||
const stamp = document.getElementById("stamp");
|
||||
const stampHeight = stamp.clientHeight;
|
||||
const stampWidth = stamp.clientWidth;
|
||||
|
||||
const currentHeight = rotating ? this.state.y : e.pageY
|
||||
- windowRef.scrollTop - stampHeight;
|
||||
const currentWidth = rotating ? this.state.x : e.pageX - (stampWidth / 2);
|
||||
|
||||
const widthMin = 0;
|
||||
const heightMin = 0;
|
||||
|
||||
const widthMax = (windowRef.clientWidth) - (
|
||||
stampWidth);
|
||||
const heightMax = (windowRef.clientHeight - windowRef.scrollTop) - (
|
||||
stampHeight);
|
||||
|
||||
const radians = Math.atan2(
|
||||
e.pageX - currentWidth,
|
||||
e.pageY - currentHeight
|
||||
);
|
||||
|
||||
const rotate = rotating ? (radians * (180 / Math.PI) * -1)
|
||||
: this.state.rotate;
|
||||
|
||||
const pos = [
|
||||
clamp(currentWidth, widthMin, widthMax),
|
||||
clamp(currentHeight, heightMin, heightMax),
|
||||
rotate,
|
||||
];
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.onwheel = this.handleWheel.bind(this);
|
||||
document.addEventListener("mousemove", this.handleMouseMove);
|
||||
document.addEventListener("click", this.handleMouseClick);
|
||||
}
|
||||
|
||||
handleMouseMove(e) {
|
||||
const pos = this.findStampPosition(e);
|
||||
// center offset of stamp
|
||||
pauseEvent(e);
|
||||
this.setState({ x: pos[0], y: pos[1] });
|
||||
}
|
||||
|
||||
handleMouseClick(e) {
|
||||
const pos = this.findStampPosition(e);
|
||||
const { act, data } = useBackend(this.context);
|
||||
const stamp_obj = {
|
||||
x: pos[0], y: pos[1], r: this.state.rotate,
|
||||
stamp_class: this.props.stamp_class,
|
||||
stamp_icon_state: data.stamp_icon_state,
|
||||
};
|
||||
act("stamp", stamp_obj);
|
||||
this.setState({ x: pos[0], y: pos[1] });
|
||||
}
|
||||
|
||||
handleWheel(e) {
|
||||
const rotate_amount = e.deltaY > 0 ? 15 : -15;
|
||||
if (e.deltaY < 0 && this.state.rotate === 0) {
|
||||
this.setState({ rotate: (360+rotate_amount) });
|
||||
} else if (e.deltaY > 0 && this.state.rotate === 360) {
|
||||
this.setState({ rotate: rotate_amount });
|
||||
} else {
|
||||
const rotate = { rotate: rotate_amount + this.state.rotate };
|
||||
this.setState(() => rotate);
|
||||
}
|
||||
pauseEvent(e);
|
||||
componentWillUnmount() {
|
||||
document.removeEventListener("mousemove", this.handleMouseMove);
|
||||
document.removeEventListener("click", this.handleMouseClick);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -348,7 +336,6 @@ class PaperSheetStamper extends Component {
|
||||
value,
|
||||
stamp_class,
|
||||
stamps,
|
||||
...rest
|
||||
} = this.props;
|
||||
const stamp_list = stamps || [];
|
||||
const current_pos = {
|
||||
@@ -358,24 +345,22 @@ class PaperSheetStamper extends Component {
|
||||
rotate: this.state.rotate,
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
onClick={this.handleMouseClick.bind(this)}
|
||||
onMouseMove={this.handleMouseMove.bind(this)}
|
||||
onwheel={this.handleWheel.bind(this)} {...rest}>
|
||||
<Fragment>
|
||||
<PaperSheetView
|
||||
readOnly
|
||||
value={value}
|
||||
stamps={stamp_list} />
|
||||
<Stamp
|
||||
active_stamp
|
||||
opacity={0.5} image={current_pos} />
|
||||
</Box>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ugh. So have to turn this into a full
|
||||
// component too if I want to keep updates
|
||||
// low and keep the wierd flashing down
|
||||
// low and keep the weird flashing down
|
||||
class PaperSheetEdit extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
@@ -401,29 +386,29 @@ class PaperSheetEdit extends Component {
|
||||
} = data;
|
||||
const out = { text: text };
|
||||
// check if we are adding to paper, if not
|
||||
// we still have to check if somone entered something
|
||||
// we still have to check if someone entered something
|
||||
// into the fields
|
||||
value = value.trim();
|
||||
if (value.length > 0) {
|
||||
// First lets make sure it ends in a new line
|
||||
value += value[value.length] === "\n" ? " \n" : "\n \n";
|
||||
// Second, we sanatize the text of html
|
||||
const sanatized_text = sanatize_text(value);
|
||||
const signed_text = signDocument(sanatized_text, pen_color, edit_usr);
|
||||
// Second, we sanitize the text of html
|
||||
const sanitized_text = sanitizeText(value);
|
||||
const signed_text = signDocument(sanitized_text, pen_color, edit_usr);
|
||||
// Third we replace the [__] with fields as markedjs fucks them up
|
||||
const fielded_text = createFields(
|
||||
signed_text, pen_font, 12, pen_color, field_counter);
|
||||
// Fourth, parse the text using markup
|
||||
const formated_text = run_marked_default(fielded_text.text);
|
||||
const formatted_text = run_marked_default(fielded_text.text);
|
||||
// Fifth, we wrap the created text in the pin color, and font.
|
||||
// crayon is bold (<b> tags), mabye make fountain pin italic?
|
||||
// crayon is bold (<b> tags), maybe make fountain pin italic?
|
||||
const fonted_text = setFontinText(
|
||||
formated_text, pen_font, pen_color, is_crayon);
|
||||
formatted_text, pen_font, pen_color, is_crayon);
|
||||
out.text += fonted_text;
|
||||
out.field_counter = fielded_text.counter;
|
||||
}
|
||||
if (do_fields) {
|
||||
// finaly we check all the form fields to see
|
||||
// finally we check all the form fields to see
|
||||
// if any data was entered by the user and
|
||||
// if it was return the data and modify the text
|
||||
const final_processing = checkAllFields(
|
||||
@@ -440,7 +425,7 @@ class PaperSheetEdit extends Component {
|
||||
+ this.state.textarea_text.length;
|
||||
if (combined_length > MAX_PAPER_LENGTH) {
|
||||
if ((combined_length - MAX_PAPER_LENGTH) >= value.length) {
|
||||
// Basicly we cannot add any more text to the paper
|
||||
// Basically we cannot add any more text to the paper
|
||||
value = '';
|
||||
} else {
|
||||
value = value.substr(0, value.length
|
||||
@@ -473,15 +458,15 @@ class PaperSheetEdit extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
value="",
|
||||
textColor,
|
||||
fontFamily,
|
||||
stamps,
|
||||
backgroundColor,
|
||||
...rest
|
||||
} = this.props;
|
||||
return (
|
||||
<Flex direction="column" fillPositionedParent>
|
||||
<Flex
|
||||
direction="column"
|
||||
fillPositionedParent>
|
||||
<Flex.Item>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
@@ -541,7 +526,7 @@ class PaperSheetEdit extends Component {
|
||||
this.setState({ previewSelected: "confirm" });
|
||||
}
|
||||
}}>
|
||||
{this.state.previewSelected === "confirm" ? "confirm" : "save"}
|
||||
{this.state.previewSelected === "confirm" ? "Confirm" : "Save"}
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
</Flex.Item>
|
||||
@@ -574,19 +559,16 @@ export const PaperSheet = (props, context) => {
|
||||
const {
|
||||
edit_mode,
|
||||
text,
|
||||
paper_color,
|
||||
paper_color = "white",
|
||||
pen_color = "black",
|
||||
pen_font = "Verdana",
|
||||
stamps,
|
||||
stamp_class,
|
||||
stamped,
|
||||
sizeX,
|
||||
sizeY,
|
||||
name,
|
||||
} = data;
|
||||
// You might ask why? Because Window/window content do wierd
|
||||
// css stuff with white for some reason
|
||||
const backgroundColor = paper_color && paper_color !== "white"
|
||||
? paper_color
|
||||
: "#FFFFFF";
|
||||
const stamp_list = !stamps || stamps === null
|
||||
const stamp_list = !stamps
|
||||
? []
|
||||
: stamps;
|
||||
const decide_mode = mode => {
|
||||
@@ -605,7 +587,7 @@ export const PaperSheet = (props, context) => {
|
||||
textColor={pen_color}
|
||||
fontFamily={pen_font}
|
||||
stamps={stamp_list}
|
||||
backgroundColor={backgroundColor} />
|
||||
backgroundColor={paper_color} />
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
@@ -620,14 +602,18 @@ export const PaperSheet = (props, context) => {
|
||||
};
|
||||
return (
|
||||
<Window
|
||||
title={name}
|
||||
theme="paper"
|
||||
width={400}
|
||||
height={500}
|
||||
width={sizeX || 400}
|
||||
height={sizeY || 500}
|
||||
resizable>
|
||||
<Window.Content>
|
||||
<Window.Content
|
||||
backgroundColor={paper_color}
|
||||
scrollable>
|
||||
<Box
|
||||
fillPositionedParent
|
||||
backgroundColor={backgroundColor}>
|
||||
id="page"
|
||||
fitted
|
||||
fillPositionedParent>
|
||||
{decide_mode(edit_mode)}
|
||||
</Box>
|
||||
</Window.Content>
|
||||
|
||||
@@ -45,6 +45,7 @@ export const Photocopier = (props, context) => {
|
||||
const Toner = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
has_toner,
|
||||
max_toner,
|
||||
current_toner,
|
||||
} = data;
|
||||
@@ -57,6 +58,7 @@ const Toner = (props, context) => {
|
||||
title="Toner"
|
||||
buttons={
|
||||
<Button
|
||||
disabled={!has_toner}
|
||||
onClick={() => act('remove_toner')}
|
||||
icon="eject">
|
||||
Eject
|
||||
|
||||
@@ -10,7 +10,7 @@ export const PortableScrubber = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={320}
|
||||
height={350}>
|
||||
height={376}>
|
||||
<Window.Content>
|
||||
<PortableBasicInfo />
|
||||
<Section title="Filters">
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import { resolveAsset } from '../assets';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Button, Flex, NoticeBox, Section, Tabs } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const PortraitPicker = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0);
|
||||
const [listIndex, setListIndex] = useLocalState(context, 'listIndex', 0);
|
||||
const {
|
||||
library,
|
||||
library_secure,
|
||||
library_private,
|
||||
} = data;
|
||||
const TABS = [
|
||||
{
|
||||
name: 'Common Portraits',
|
||||
list: library,
|
||||
},
|
||||
{
|
||||
name: 'Secure Portraits',
|
||||
list: library_secure,
|
||||
},
|
||||
{
|
||||
name: 'Private Portraits',
|
||||
list: library_private,
|
||||
},
|
||||
];
|
||||
const tab2list = TABS[tabIndex].list;
|
||||
const current_portrait = tab2list[listIndex]["title"];
|
||||
return (
|
||||
<Window
|
||||
theme="ntos"
|
||||
title="Portrait Picker"
|
||||
width={400}
|
||||
height={406}>
|
||||
<Window.Content>
|
||||
<Flex height="100%" direction="column">
|
||||
<Flex.Item mb={1}>
|
||||
<Section fitted>
|
||||
<Tabs fluid textAlign="center">
|
||||
{TABS.map((tabObj, i) => (
|
||||
<Tabs.Tab
|
||||
key={i}
|
||||
selected={i === tabIndex}
|
||||
onClick={() => {
|
||||
setListIndex(0);
|
||||
setTabIndex(i);
|
||||
}}>
|
||||
{tabObj.name}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item mb={1} grow={2}>
|
||||
<Section fill>
|
||||
<Flex
|
||||
height="100%"
|
||||
align="center"
|
||||
justify="center"
|
||||
direction="column">
|
||||
<Flex.Item>
|
||||
<img
|
||||
src={resolveAsset(current_portrait)}
|
||||
height="96px"
|
||||
width="96px"
|
||||
style={{
|
||||
'vertical-align': 'middle',
|
||||
'-ms-interpolation-mode': 'nearest-neighbor',
|
||||
}} />
|
||||
</Flex.Item>
|
||||
<Flex.Item className="Section__titleText">
|
||||
{current_portrait}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Flex>
|
||||
<Flex.Item grow={3}>
|
||||
<Section height="100%">
|
||||
<Flex justify="space-between">
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="angle-double-left"
|
||||
disabled={listIndex === 0}
|
||||
onClick={() => setListIndex(0)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={3}>
|
||||
<Button
|
||||
disabled={listIndex === 0}
|
||||
icon="chevron-left"
|
||||
onClick={() => setListIndex(listIndex-1)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={3}>
|
||||
<Button
|
||||
icon="check"
|
||||
content="Select Portrait"
|
||||
onClick={() => act("select", {
|
||||
tab: tabIndex+1,
|
||||
selected: listIndex+1,
|
||||
})}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="chevron-right"
|
||||
disabled={listIndex === tab2list.length-1}
|
||||
onClick={() => setListIndex(listIndex+1)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Button
|
||||
icon="angle-double-right"
|
||||
disabled={listIndex === tab2list.length-1}
|
||||
onClick={() => setListIndex(tab2list.length-1)}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex.Item mt={1}>
|
||||
<NoticeBox info>
|
||||
Only the 23x23 or 24x24 canvas size art can be
|
||||
displayed. Make sure you read the warning below
|
||||
before embracing the wide wonderful world of
|
||||
artistic expression!
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<NoticeBox danger>
|
||||
WARNING: While Central Command loves art as much as you do,
|
||||
choosing erotic art will lead to severe consequences.
|
||||
Additionally, Central Command reserves the right to request
|
||||
you change your display portrait, for any reason.
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -42,7 +42,7 @@ export const RouletteNumberButton = (props, context) => {
|
||||
textAlign="center"
|
||||
mb={0}
|
||||
className="Roulette__board-extrabutton"
|
||||
onClick={() => act('ChangeBetType', { type: number })}
|
||||
onClick={() => act('ChangeBetType', { type: number.toString() })}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -70,7 +70,7 @@ export const RouletteBoard = (props, context) => {
|
||||
color="transparent"
|
||||
height="88px"
|
||||
className="Roulette__board-extrabutton"
|
||||
onClick={() => act('ChangeBetType', { type: 0 })}
|
||||
onClick={() => act('ChangeBetType', { type: "0" })}
|
||||
/>
|
||||
</td>
|
||||
{firstRow.map(number => (
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { resolveAsset } from '../assets';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Icon, Section } from '../components';
|
||||
import { Window } from "../layouts";
|
||||
|
||||
export const Safe = (properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
dial,
|
||||
open,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={625}
|
||||
height={800}
|
||||
theme="ntos">
|
||||
<Window.Content>
|
||||
<Box className="Safe__engraving">
|
||||
<Dialer />
|
||||
<Box>
|
||||
<Box
|
||||
className="Safe__engraving-hinge"
|
||||
top="25%" />
|
||||
<Box
|
||||
className="Safe__engraving-hinge"
|
||||
top="75%" />
|
||||
</Box>
|
||||
<Icon
|
||||
className="Safe__engraving-arrow"
|
||||
name="long-arrow-alt-down"
|
||||
size="5"
|
||||
/><br />
|
||||
{open ? (
|
||||
<Contents />
|
||||
) : (
|
||||
<Box
|
||||
as="img"
|
||||
className="Safe__dial"
|
||||
src={resolveAsset('safe_dial.png')}
|
||||
style={{
|
||||
"transform": "rotate(-" + (3.6 * dial) + "deg)",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{!open && (
|
||||
<Help />
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const Dialer = (properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
dial,
|
||||
open,
|
||||
locked,
|
||||
broken,
|
||||
} = data;
|
||||
const dialButton = (amount, right) => {
|
||||
return (
|
||||
<Button
|
||||
disabled={open || (right && !locked) || broken}
|
||||
icon={"arrow-" + (right ? "right" : "left")}
|
||||
content={(right ? "Right" : "Left") + " " + amount}
|
||||
iconPosition={right ? "right" : "left"}
|
||||
onClick={() => act(!right ? "turnright" : "turnleft", {
|
||||
num: amount,
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Box className="Safe__dialer">
|
||||
<Button
|
||||
disabled={locked && !broken}
|
||||
icon={open ? "lock" : "lock-open"}
|
||||
content={open ? "Close" : "Open"}
|
||||
mb="0.5rem"
|
||||
onClick={() => act('open')}
|
||||
/><br />
|
||||
<Box position="absolute">
|
||||
{[dialButton(50), dialButton(10), dialButton(1)]}
|
||||
</Box>
|
||||
<Box
|
||||
className="Safe__dialer-right"
|
||||
position="absolute" right="5px">
|
||||
{[dialButton(1, true), dialButton(10, true), dialButton(50, true)]}
|
||||
</Box>
|
||||
<Box className="Safe__dialer-number">
|
||||
{dial}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Contents = (properties, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
contents,
|
||||
} = data;
|
||||
return (
|
||||
<Box
|
||||
className="Safe__contents"
|
||||
overflow="auto">
|
||||
{contents.map((item, index) => (
|
||||
<Fragment key={item}>
|
||||
<Button
|
||||
mb="0.5rem"
|
||||
onClick={() => act("retrieve", {
|
||||
index: index + 1,
|
||||
})}>
|
||||
<Box
|
||||
as="img"
|
||||
src={item.sprite + ".png"}
|
||||
verticalAlign="middle"
|
||||
ml="-6px"
|
||||
mr="0.5rem"
|
||||
/>
|
||||
{item.name}
|
||||
</Button>
|
||||
<br />
|
||||
</Fragment>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Help = (properties, context) => {
|
||||
return (
|
||||
<Section
|
||||
className="Safe__help"
|
||||
title="Safe opening instructions (because you all keep forgetting)">
|
||||
<Box>
|
||||
1. Turn the dial left to the first number.<br />
|
||||
2. Turn the dial right to the second number.<br />
|
||||
3. Continue repeating this process for each number,
|
||||
switching between left and right each time.<br />
|
||||
4. Open the safe.
|
||||
</Box>
|
||||
<Box bold>
|
||||
To lock fully, turn the dial to the left after closing the safe.
|
||||
</Box>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,678 @@
|
||||
import { toFixed } from 'common/math';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Button, Flex, LabeledControls, NoticeBox, RoundGauge, Section } from '../components';
|
||||
import { FlexItem } from '../components/Flex';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const TAB2NAME = [
|
||||
{
|
||||
title: 'Debugging',
|
||||
blurb: 'Where useless shit goes to die',
|
||||
gauge: 5,
|
||||
component: () => DebuggingTab,
|
||||
},
|
||||
{
|
||||
title: 'Helpful',
|
||||
blurb: 'Where fuckwits put logging',
|
||||
gauge: 25,
|
||||
component: () => HelpfulTab,
|
||||
},
|
||||
{
|
||||
title: 'Fun',
|
||||
blurb: 'How I ran an """event"""',
|
||||
gauge: 75,
|
||||
component: () => FunTab,
|
||||
},
|
||||
{
|
||||
title: 'Only Fun For You',
|
||||
blurb: 'How I spent my last day adminning',
|
||||
gauge: 95,
|
||||
component: () => FunForYouTab,
|
||||
},
|
||||
];
|
||||
|
||||
const lineHeightNormal = 2.79;
|
||||
const lineHeightDebug = 6;
|
||||
|
||||
const DebuggingTab = (props, context) => {
|
||||
const { act } = useBackend(context);
|
||||
return (
|
||||
<Flex
|
||||
grow={1}
|
||||
mb={-0.25}
|
||||
mx={-0.5}
|
||||
direction="column"
|
||||
height="100%"
|
||||
textAlign="center"
|
||||
align="stretch"
|
||||
justify="center">
|
||||
<Flex.Item my={0.5}>
|
||||
<Button
|
||||
lineHeight={lineHeightDebug}
|
||||
icon="question"
|
||||
fluid
|
||||
content="Change all maintenance doors to engie/brig access only"
|
||||
onClick={() => act("maint_access_engiebrig")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item my={0.5}>
|
||||
<Button
|
||||
lineHeight={lineHeightDebug}
|
||||
icon="question"
|
||||
fluid
|
||||
content="Change all maintenance doors to brig access only"
|
||||
onClick={() => act("maint_access_brig")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item mt={0.5} mb={-0.5}>
|
||||
<Button
|
||||
lineHeight={lineHeightDebug}
|
||||
icon="question"
|
||||
fluid
|
||||
content="Remove cap on security officers"
|
||||
onClick={() => act("infinite_sec")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const HelpfulTab = (props, context) => {
|
||||
const { act } = useBackend(context);
|
||||
return (
|
||||
<Flex direction="column" mb={-0.75} mx={-0.5}>
|
||||
<Flex
|
||||
mb={1}
|
||||
grow={1}
|
||||
direction="row"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="space-between">
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="plus"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Cure all diseases currently in existence"
|
||||
onClick={() => act("clear_virus")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} ml={0.5}>
|
||||
<Button
|
||||
icon="eye"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Show Gamemode"
|
||||
onClick={() => act("showgm")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex
|
||||
mb={1}
|
||||
grow={1}
|
||||
direction="row"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="space-between">
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="bomb"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="List Bombers"
|
||||
onClick={() => act("list_bombers")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} mx={0.5}>
|
||||
<Button
|
||||
icon="signal"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="List Signalers"
|
||||
onClick={() => act("list_signalers")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="robot"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="List laws"
|
||||
onClick={() => act("list_lawchanges")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex
|
||||
mb={1}
|
||||
grow={1}
|
||||
direction="row"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="space-between">
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="address-book"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Show Manifest"
|
||||
onClick={() => act("manifest")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} mx={0.5}>
|
||||
<Button
|
||||
icon="dna"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Show DNA"
|
||||
onClick={() => act("dna")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="fingerprint"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Show Fingerprints"
|
||||
onClick={() => act("fingerprints")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex
|
||||
mb={1}
|
||||
grow={1}
|
||||
direction="row"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="space-between">
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="flag"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Toggle CTF"
|
||||
onClick={() => act("ctfbutton")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} mx={0.5}>
|
||||
<Button
|
||||
icon="sync-alt"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Reset Thunderdome"
|
||||
onClick={() => act("tdomereset")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="moon"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Set Nightshift"
|
||||
onClick={() => act("night_shift_set")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex
|
||||
mb={1}
|
||||
grow={1}
|
||||
direction="row"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="space-between">
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="pencil-alt"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Rename Station"
|
||||
onClick={() => act("set_name")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} ml={0.5}>
|
||||
<Button
|
||||
icon="eraser"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Reset Station"
|
||||
onClick={() => act("reset_name")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex
|
||||
grow={1}
|
||||
direction="row"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="space-between">
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="plane-departure"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Move Ferry"
|
||||
onClick={() => act("moveferry")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} mx={0.5}>
|
||||
<Button
|
||||
icon="plane"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Toggle Arrivals"
|
||||
onClick={() => act("togglearrivals")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="plane-arrival"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Move Labor"
|
||||
onClick={() => act("movelaborshuttle")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const FunTab = (props, context) => {
|
||||
const { act } = useBackend(context);
|
||||
return (
|
||||
<Flex direction="column" mb={-0.75} mx={-0.5} textAlign="center">
|
||||
<Flex
|
||||
mb={1}
|
||||
grow={1}
|
||||
direction="row"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="space-between">
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="grin-beam-sweat"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Break all lights"
|
||||
onClick={() => act("blackout")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} mx={0.5}>
|
||||
<Button
|
||||
icon="magic"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Fix all lights"
|
||||
onClick={() => act("whiteout")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="biohazard"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Trigger Outbreak"
|
||||
onClick={() => act("virus")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex
|
||||
mb={1}
|
||||
grow={1}
|
||||
direction="row"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="space-between">
|
||||
<Flex.Item>
|
||||
<Button
|
||||
icon="bolt"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="All areas powered"
|
||||
onClick={() => act("power")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} mx={0.5}>
|
||||
<Button
|
||||
icon="moon"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="All areas unpowered"
|
||||
onClick={() => act("unpower")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Button
|
||||
icon="plug"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="IC power (SMES charged)"
|
||||
onClick={() => act("quickpower")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex
|
||||
mb={1}
|
||||
grow={1}
|
||||
direction="row"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="space-between">
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="user-ninja"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Anonymous Names"
|
||||
onClick={() => act("anon_name")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} mx={0.5}>
|
||||
<Button
|
||||
icon="users"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Triple AI mode"
|
||||
onClick={() => act("tripleAI")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="bullhorn"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="THERE CAN ONLY BE ONE!"
|
||||
onClick={() => act("onlyone")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex
|
||||
mb={1}
|
||||
grow={1}
|
||||
direction="row"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="space-between">
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="grin-beam-sweat"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Summon Guns"
|
||||
onClick={() => act("guns")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} mx={0.5}>
|
||||
<Button
|
||||
icon="magic"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Summon Magic"
|
||||
onClick={() => act("magic")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="meteor"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Summon Events"
|
||||
onClick={() => act("events")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex
|
||||
mb={1}
|
||||
grow={1}
|
||||
direction="row"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="space-between">
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="hammer"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Egalitarian Station"
|
||||
onClick={() => act("eagles")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} ml={0.5}>
|
||||
<Button
|
||||
icon="dollar-sign"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Anarcho-Capitalist Station"
|
||||
onClick={() => act("ancap")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex
|
||||
grow={1}
|
||||
direction="row"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="space-between">
|
||||
<Flex.Item grow={1}>
|
||||
<Button
|
||||
icon="bullseye"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Custom Portal Storm"
|
||||
onClick={() => act("customportal")} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} ml={0.5}>
|
||||
<Button
|
||||
icon="bomb"
|
||||
lineHeight={lineHeightNormal}
|
||||
fluid
|
||||
content="Change Bomb Cap"
|
||||
onClick={() => act("changebombcap")} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const FunForYouTab = (props, context) => {
|
||||
const { act } = useBackend(context);
|
||||
return (
|
||||
<Flex
|
||||
grow={1}
|
||||
mx={-0.5}
|
||||
mb={-1.75}
|
||||
direction="column"
|
||||
height="100%"
|
||||
align="stretch"
|
||||
justify="center">
|
||||
<Flex.Item>
|
||||
<NoticeBox danger>
|
||||
<Button
|
||||
color="red"
|
||||
icon="paw"
|
||||
fluid
|
||||
content="Turn all humans into monkeys"
|
||||
onClick={() => act("monkey")} />
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<NoticeBox danger>
|
||||
<Button
|
||||
color="red"
|
||||
icon="user-secret"
|
||||
fluid
|
||||
content="Everyone is the traitor"
|
||||
onClick={() => act("traitor_all")} />
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<NoticeBox danger>
|
||||
<Button
|
||||
color="red"
|
||||
icon="brain"
|
||||
fluid
|
||||
content="Make all players brain damaged"
|
||||
onClick={() => act("massbraindamage")} />
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<NoticeBox danger>
|
||||
<Button
|
||||
color="black"
|
||||
icon="fire"
|
||||
fluid
|
||||
content="The floor is lava! (DANGEROUS: extremely lame)"
|
||||
onClick={() => act("floorlava")} />
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<NoticeBox danger>
|
||||
<Button
|
||||
color="black"
|
||||
icon="tired"
|
||||
fluid
|
||||
content="Chinese Cartoons! (DANGEROUS: no going back, also fuck you)"
|
||||
onClick={() => act("anime")} />
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Flex>
|
||||
<Flex.Item width="240px" mr={0.25}>
|
||||
<NoticeBox danger>
|
||||
<Button
|
||||
color="red"
|
||||
icon="cat"
|
||||
fluid
|
||||
content="Mass Purrbation"
|
||||
onClick={() => act("masspurrbation")} />
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} ml={0.25}>
|
||||
<NoticeBox info>
|
||||
<Button
|
||||
color="blue"
|
||||
icon="user"
|
||||
fluid
|
||||
content="Mass Remove Purrbation"
|
||||
onClick={() => act("massremovepurrbation")} />
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Flex justify="space-between">
|
||||
<Flex.Item width="240px" mr={0.25}>
|
||||
<NoticeBox danger>
|
||||
<Button
|
||||
color="red"
|
||||
icon="flushed"
|
||||
fluid
|
||||
content="Fully Immerse Everyone"
|
||||
onClick={() => act("massimmerse")} />
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} ml={0.25}>
|
||||
<NoticeBox info>
|
||||
<Button
|
||||
color="blue"
|
||||
icon="sync-alt"
|
||||
fluid
|
||||
content="Shatter the Immersion"
|
||||
onClick={() => act("unmassimmerse")} />
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const Secrets = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
is_debugger,
|
||||
is_funmin,
|
||||
} = data;
|
||||
const [
|
||||
tabIndex,
|
||||
setTabIndex,
|
||||
] = useLocalState(context, 'tab-index', 2);
|
||||
const TabComponent = TAB2NAME[tabIndex-1].component();
|
||||
return (
|
||||
<Window
|
||||
title="Secrets Panel"
|
||||
width={500}
|
||||
height={485}>
|
||||
<Window.Content>
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex.Item mb={1}>
|
||||
<Section
|
||||
title="Secrets"
|
||||
buttons={(
|
||||
<Fragment>
|
||||
<Button
|
||||
color="blue"
|
||||
icon="address-card"
|
||||
content="Admin Log"
|
||||
onClick={() => act("admin_log")} />
|
||||
<Button
|
||||
color="blue"
|
||||
icon="eye"
|
||||
content="Show Admins"
|
||||
onClick={() => act("show_admins")} />
|
||||
</Fragment>)}>
|
||||
<Flex
|
||||
mx={-0.5}
|
||||
align="stretch"
|
||||
justify="center">
|
||||
<Flex.Item bold>
|
||||
<NoticeBox color="black">
|
||||
"The first rule of adminbuse is:
|
||||
you don't talk about the adminbuse."
|
||||
</NoticeBox>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex
|
||||
textAlign="center"
|
||||
mx={-0.5}
|
||||
align="stretch"
|
||||
justify="center">
|
||||
<Flex.Item ml={-10} mr={1}>
|
||||
<Button
|
||||
selected={tabIndex === 2}
|
||||
icon="check-circle"
|
||||
content="Helpful"
|
||||
onClick={() => setTabIndex(2)} />
|
||||
</Flex.Item>
|
||||
<Flex.Item ml={1}>
|
||||
<Button
|
||||
disabled={is_funmin === 0}
|
||||
selected={tabIndex === 3}
|
||||
icon="smile"
|
||||
content="Fun"
|
||||
onClick={() => setTabIndex(3)} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<Flex
|
||||
mx={-0.5}
|
||||
align="stretch"
|
||||
justify="center">
|
||||
<Flex.Item mt={1}>
|
||||
<Button
|
||||
disabled={is_debugger === 0}
|
||||
selected={tabIndex === 1}
|
||||
icon="glasses"
|
||||
content="Debugging"
|
||||
onClick={() => setTabIndex(1)} />
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<LabeledControls>
|
||||
<LabeledControls.Item
|
||||
minWidth="66px"
|
||||
label="Chances of admin complaint">
|
||||
<RoundGauge
|
||||
size={2}
|
||||
value={TAB2NAME[tabIndex-1].gauge}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
alertAfter={100 * 0.70}
|
||||
ranges={{
|
||||
"good": [-2, 100 * 0.25],
|
||||
"average": [100 * 0.25, 100 * 0.75],
|
||||
"bad": [100 * 0.75, 100],
|
||||
}}
|
||||
format={value => toFixed(value) + '%'} />
|
||||
</LabeledControls.Item>
|
||||
</LabeledControls>
|
||||
</Flex.Item>
|
||||
<Flex.Item mt={1}>
|
||||
<Button
|
||||
disabled={is_funmin === 0}
|
||||
selected={tabIndex === 4}
|
||||
icon="smile-wink"
|
||||
content="Only Fun For You"
|
||||
onClick={() => setTabIndex(4)} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Section
|
||||
fill={false}
|
||||
title={TAB2NAME[tabIndex-1].title
|
||||
+" Or: "+TAB2NAME[tabIndex-1].blurb}>
|
||||
<TabComponent />
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -59,6 +59,16 @@ const getLocationIdByName = (locations, name) => {
|
||||
return locations?.find(location => location.name === name)?.id;
|
||||
};
|
||||
|
||||
const STATUS_COLOR_KEYS = {
|
||||
"In Transit": "good",
|
||||
"Idle": "average",
|
||||
"Igniting": "average",
|
||||
"Recharging": "average",
|
||||
"Missing": "bad",
|
||||
"Unauthorized Access": "bad",
|
||||
"Locked": "bad",
|
||||
};
|
||||
|
||||
const ShuttleConsoleContent = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
@@ -90,13 +100,7 @@ const ShuttleConsoleContent = (props, context) => {
|
||||
</Box>
|
||||
<Box
|
||||
inline
|
||||
color={status==="In Transit"
|
||||
? 'good'
|
||||
: status==="Idle"
|
||||
? 'average'
|
||||
: status==="Igniting"
|
||||
? 'average'
|
||||
: 'bad'}
|
||||
color={STATUS_COLOR_KEYS[status] || "bad"}
|
||||
ml={1}>
|
||||
{status || "Not Available"}
|
||||
</Box>
|
||||
@@ -110,15 +114,20 @@ const ShuttleConsoleContent = (props, context) => {
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Destination">
|
||||
{locations.length===0 && (
|
||||
<Box color="bad">
|
||||
<Box
|
||||
mb={1.7}
|
||||
color="bad">
|
||||
Not Available
|
||||
</Box>
|
||||
) || locations.length===1 &&(
|
||||
<Box color="average">
|
||||
<Box
|
||||
mb={1.7}
|
||||
color="average">
|
||||
{getLocationNameById(locations, destination)}
|
||||
</Box>
|
||||
) || (
|
||||
<Dropdown
|
||||
mb={1.7}
|
||||
over
|
||||
width="240px"
|
||||
options={locations.map(location => location.name)}
|
||||
@@ -134,7 +143,6 @@ const ShuttleConsoleContent = (props, context) => {
|
||||
content="Depart"
|
||||
disabled={!getLocationNameById(locations, destination)
|
||||
|| locked || authorization_required}
|
||||
mt={1.5}
|
||||
icon="arrow-up"
|
||||
textAlign="center"
|
||||
onClick={() => act('move', {
|
||||
|
||||
@@ -109,7 +109,7 @@ export const ShuttleManipulatorTemplates = (props, context) => {
|
||||
selectedTemplateId,
|
||||
setSelectedTemplateId,
|
||||
] = useLocalState(context, 'templateId', Object.keys(templateObject)[0]);
|
||||
const actualTemplates = templateObject[selectedTemplateId]?.templates;
|
||||
const actualTemplates = templateObject[selectedTemplateId]?.templates || [];
|
||||
return (
|
||||
<Section>
|
||||
<Flex>
|
||||
@@ -227,15 +227,21 @@ export const ShuttleManipulatorModification = (props, context) => {
|
||||
<Section
|
||||
level={2}
|
||||
title="Status">
|
||||
<Button
|
||||
content="Load"
|
||||
color="good"
|
||||
onClick={() => act('load', {
|
||||
shuttle_id: selected.shuttle_id,
|
||||
})} />
|
||||
<Button
|
||||
content="Preview"
|
||||
onClick={() => act('preview', {
|
||||
shuttle_id: selected.shuttle_id,
|
||||
})} />
|
||||
<Button
|
||||
content="Load"
|
||||
content="Replace"
|
||||
color="bad"
|
||||
onClick={() => act('load', {
|
||||
onClick={() => act('replace', {
|
||||
shuttle_id: selected.shuttle_id,
|
||||
})} />
|
||||
</Section>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, Box, Section, NoticeBox, TimeDisplay, Flex, Icon, Table, Fragment, LabeledList } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { toFixed } from 'common/math';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Flex, Icon, LabeledList, NoticeBox, Section, Table } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const InsertedSkillchip = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
import { createSearch } from 'common/string';
|
||||
import { Fragment } from 'inferno';
|
||||
import { sortBy } from 'common/collections';
|
||||
import { useBackend, useLocalState } from "../backend";
|
||||
import { Box, Button, Input, NoticeBox, Section, Collapsible, Table } from "../components";
|
||||
import { Window } from "../layouts";
|
||||
|
||||
export const Stack = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const {
|
||||
amount,
|
||||
recipes = [],
|
||||
} = data;
|
||||
|
||||
const [
|
||||
searchText,
|
||||
setSearchText,
|
||||
] = useLocalState(context, 'searchText', '');
|
||||
|
||||
const testSearch = createSearch(searchText, item => {
|
||||
return item;
|
||||
});
|
||||
|
||||
const items = searchText.length > 0
|
||||
&& Object.keys(recipes)
|
||||
.filter(testSearch)
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = recipes[key];
|
||||
return obj;
|
||||
}, {})
|
||||
|| recipes;
|
||||
|
||||
const height = Math.max(94 + Object.keys(recipes).length * 26, 250);
|
||||
|
||||
return (
|
||||
<Window
|
||||
width={400}
|
||||
height={Math.min(height, 500)}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
<Section
|
||||
title={"Amount: " + amount}
|
||||
buttons={(
|
||||
<Fragment>
|
||||
Search
|
||||
<Input
|
||||
autoFocus
|
||||
value={searchText}
|
||||
onInput={(e, value) => setSearchText(value)}
|
||||
mx={1} />
|
||||
</Fragment>
|
||||
)}>
|
||||
{items.length === 0 && (
|
||||
<NoticeBox>
|
||||
No recipes found.
|
||||
</NoticeBox>
|
||||
) || (
|
||||
<RecipeList recipes={items} />
|
||||
)}
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const RecipeList = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const {
|
||||
recipes,
|
||||
} = props;
|
||||
|
||||
const sortedKeys = sortBy(key => key.toLowerCase())(Object.keys(recipes));
|
||||
|
||||
return sortedKeys.map(title => {
|
||||
const recipe = recipes[title];
|
||||
if (recipe.ref === undefined) {
|
||||
return (
|
||||
<Collapsible
|
||||
ml={1}
|
||||
color="label"
|
||||
title={title}>
|
||||
<Box ml={1}>
|
||||
<RecipeList recipes={recipe} />
|
||||
</Box>
|
||||
</Collapsible>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Recipe
|
||||
title={title}
|
||||
recipe={recipe} />
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const buildMultiplier = (recipe, amount) => {
|
||||
if (recipe.req_amount > amount) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.floor(amount / recipe.req_amount);
|
||||
};
|
||||
|
||||
const Multipliers = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const {
|
||||
recipe,
|
||||
maxMultiplier,
|
||||
} = props;
|
||||
|
||||
const maxM = Math.min(maxMultiplier,
|
||||
Math.floor(recipe.max_res_amount / recipe.res_amount));
|
||||
|
||||
const multipliers = [5, 10, 25];
|
||||
|
||||
let finalResult = [];
|
||||
|
||||
for (const multiplier of multipliers) {
|
||||
if (maxM >= multiplier) {
|
||||
finalResult.push((
|
||||
<Button
|
||||
content={multiplier * recipe.res_amount + "x"}
|
||||
onClick={() => act("make", {
|
||||
ref: recipe.ref,
|
||||
multiplier: multiplier,
|
||||
})} />
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (multipliers.indexOf(maxM) === -1) {
|
||||
finalResult.push((
|
||||
<Button
|
||||
content={maxM * recipe.res_amount + "x"}
|
||||
onClick={() => act("make", {
|
||||
ref: recipe.ref,
|
||||
multiplier: maxM,
|
||||
})} />
|
||||
));
|
||||
}
|
||||
|
||||
return finalResult;
|
||||
};
|
||||
|
||||
const Recipe = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const {
|
||||
amount,
|
||||
} = data;
|
||||
|
||||
const {
|
||||
recipe,
|
||||
title,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
res_amount,
|
||||
max_res_amount,
|
||||
req_amount,
|
||||
ref,
|
||||
} = recipe;
|
||||
|
||||
let buttonName = title;
|
||||
buttonName += " (";
|
||||
buttonName += req_amount + " ";
|
||||
buttonName += ("sheet" + (req_amount > 1 ? "s" : ""));
|
||||
buttonName += ")";
|
||||
|
||||
if (res_amount > 1) {
|
||||
buttonName = res_amount + "x " + buttonName;
|
||||
}
|
||||
|
||||
const maxMultiplier = buildMultiplier(recipe, amount);
|
||||
|
||||
return (
|
||||
<Box mb={1}>
|
||||
<Table>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
fluid
|
||||
disabled={!maxMultiplier}
|
||||
icon="wrench"
|
||||
content={buttonName}
|
||||
onClick={() => act("make", {
|
||||
ref: recipe.ref,
|
||||
multiplier: 1,
|
||||
})} />
|
||||
</Table.Cell>
|
||||
{max_res_amount > 1 && maxMultiplier > 1 && (
|
||||
<Table.Cell collapsing>
|
||||
<Multipliers
|
||||
recipe={recipe}
|
||||
maxMultiplier={maxMultiplier} />
|
||||
</Table.Cell>
|
||||
)}
|
||||
</Table.Row>
|
||||
</Table>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, LabeledList, NoticeBox, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const StackingConsole = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
machine,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={320}
|
||||
height={340}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
{!machine ? (
|
||||
<NoticeBox>
|
||||
No connected stacking machine
|
||||
</NoticeBox>
|
||||
) : (
|
||||
<StackingConsoleContent />
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export const StackingConsoleContent = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
stacking_amount,
|
||||
contents = [],
|
||||
} = data;
|
||||
return (
|
||||
<Fragment>
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Stacking Amount">
|
||||
{stacking_amount || "Unknown"}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section title="Stored Materials">
|
||||
{!contents.length ? (
|
||||
<NoticeBox>
|
||||
No stored materials
|
||||
</NoticeBox>
|
||||
) : (
|
||||
<LabeledList>
|
||||
{contents.map(sheet => (
|
||||
<LabeledList.Item
|
||||
key={sheet.type}
|
||||
label={sheet.name}
|
||||
buttons={(
|
||||
<Button
|
||||
icon="eject"
|
||||
content="Release"
|
||||
onClick={() => act('release', {
|
||||
type: sheet.type,
|
||||
})} />
|
||||
)}>
|
||||
{sheet.amount || "Unknown"}
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</LabeledList>
|
||||
)}
|
||||
</Section>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
@@ -1,28 +1,51 @@
|
||||
import { toFixed } from 'common/math';
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, LabeledList, NumberInput, ProgressBar, Section } from '../components';
|
||||
import { Button, LabeledControls, NumberInput, RoundGauge, Section } from '../components';
|
||||
import { formatSiUnit } from '../format';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const formatPressure = value => {
|
||||
if (value < 10000) {
|
||||
return toFixed(value) + ' kPa';
|
||||
}
|
||||
return formatSiUnit(value * 1000, 1, 'Pa');
|
||||
};
|
||||
|
||||
export const Tank = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
defaultReleasePressure,
|
||||
minReleasePressure,
|
||||
maxReleasePressure,
|
||||
leakPressure,
|
||||
fragmentPressure,
|
||||
tankPressure,
|
||||
releasePressure,
|
||||
connected,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={400}
|
||||
resizable
|
||||
width={275}
|
||||
height={120}>
|
||||
<Window.Content>
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Pressure">
|
||||
<ProgressBar
|
||||
value={data.tankPressure / 1013}
|
||||
<LabeledControls>
|
||||
<LabeledControls.Item label="Pressure">
|
||||
<RoundGauge
|
||||
value={tankPressure}
|
||||
minValue={0}
|
||||
maxValue={fragmentPressure * 1.15}
|
||||
alertAfter={leakPressure}
|
||||
ranges={{
|
||||
good: [0.35, Infinity],
|
||||
average: [0.15, 0.35],
|
||||
bad: [-Infinity, 0.15],
|
||||
}}>
|
||||
{data.tankPressure + ' kPa'}
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Pressure Regulator">
|
||||
"good": [0, leakPressure],
|
||||
"average": [leakPressure, fragmentPressure],
|
||||
"bad": [fragmentPressure, fragmentPressure * 1.15],
|
||||
}}
|
||||
format={formatPressure}
|
||||
size={2} />
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item label="Pressure Regulator">
|
||||
<Button
|
||||
icon="fast-backward"
|
||||
disabled={data.ReleasePressure === data.minReleasePressure}
|
||||
@@ -52,8 +75,8 @@ export const Tank = (props, context) => {
|
||||
onClick={() => act('pressure', {
|
||||
pressure: 'reset',
|
||||
})} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</LabeledControls.Item>
|
||||
</LabeledControls>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
|
||||
@@ -14,8 +14,8 @@ export const Teleporter = (props, context) => {
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={470}
|
||||
height={140}>
|
||||
width={360}
|
||||
height={130}>
|
||||
<Window.Content>
|
||||
<Section>
|
||||
{!power_station && (
|
||||
@@ -28,29 +28,22 @@ export const Teleporter = (props, context) => {
|
||||
</Box>
|
||||
)) || (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Current Regime"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="tools"
|
||||
content="Change Regime"
|
||||
onClick={() => act('regimeset')} />
|
||||
)}>
|
||||
{regime_set}
|
||||
<LabeledList.Item label="Regime">
|
||||
<Button
|
||||
content={regime_set}
|
||||
onClick={() => act('regimeset')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Current Target"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="tools"
|
||||
content="Set Target"
|
||||
onClick={() => act('settarget')} />
|
||||
)}>
|
||||
{target}
|
||||
<LabeledList.Item label="Target">
|
||||
<Button
|
||||
icon="edit"
|
||||
content={target}
|
||||
onClick={() => act('settarget')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Calibration"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="tools"
|
||||
content="Calibrate Hub"
|
||||
content="Calibrate"
|
||||
onClick={() => act('calibrate')} />
|
||||
)}>
|
||||
{calibrating && (
|
||||
|
||||
@@ -8,7 +8,7 @@ export const ThermoMachine = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={300}
|
||||
height={230}>
|
||||
height={250}>
|
||||
<Window.Content>
|
||||
<Section title="Status">
|
||||
<LabeledList>
|
||||
@@ -36,6 +36,13 @@ export const ThermoMachine = (props, context) => {
|
||||
onClick={() => act('power')} />
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Setting">
|
||||
<Button
|
||||
icon={data.cooling ? 'cooling' : 'heating'}
|
||||
content={data.cooling ? 'Cooling' : 'Heating'}
|
||||
selected={data.cooling}
|
||||
onClick={() => act('cooling')} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Target Temperature">
|
||||
<NumberInput
|
||||
animated
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { sortBy } from "common/collections";
|
||||
import { useBackend } from "../backend";
|
||||
import { Box, Flex, ProgressBar, Section, Table } from "../components";
|
||||
import { Window } from "../layouts";
|
||||
|
||||
const JOB_REPORT_MENU_FAIL_REASON_TRACKING_DISABLED = 1;
|
||||
const JOB_REPORT_MENU_FAIL_REASON_NO_RECORDS = 2;
|
||||
|
||||
const sortByPlaytime = sortBy(([_, playtime]) => -playtime);
|
||||
|
||||
const PlaytimeSection = props => {
|
||||
const { playtimes } = props;
|
||||
const sortedPlaytimes = sortByPlaytime(Object.entries(playtimes));
|
||||
const mostPlayed = sortedPlaytimes[0][1];
|
||||
|
||||
return (
|
||||
<Table>
|
||||
{sortedPlaytimes.map(([jobName, playtime]) => {
|
||||
const ratio = playtime / mostPlayed;
|
||||
|
||||
return (
|
||||
<Table.Row key={jobName}>
|
||||
<Table.Cell collapsing p={0.5} style={{
|
||||
"vertical-align": "middle",
|
||||
}}>
|
||||
<Box align="right">{jobName}</Box>
|
||||
</Table.Cell>
|
||||
|
||||
<Table.Cell>
|
||||
<ProgressBar
|
||||
maxValue={mostPlayed}
|
||||
value={playtime}>
|
||||
<Flex>
|
||||
<Flex.Item width={`${ratio * 100}%`} />
|
||||
|
||||
<Flex.Item>
|
||||
{(playtime / 60).toLocaleString(undefined, {
|
||||
"minimumFractionDigits": 1,
|
||||
"maximumFractionDigits": 1,
|
||||
})}h
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</ProgressBar>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export const TrackedPlaytime = (props, context) => {
|
||||
const { data } = useBackend(context);
|
||||
const {
|
||||
failReason,
|
||||
jobPlaytimes,
|
||||
specialPlaytimes,
|
||||
|
||||
livingTime,
|
||||
ghostTime,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Window
|
||||
title="Tracked Playtime"
|
||||
width={550}
|
||||
height={650}
|
||||
resizable>
|
||||
<Window.Content scrollable>
|
||||
{failReason && (
|
||||
failReason === JOB_REPORT_MENU_FAIL_REASON_TRACKING_DISABLED
|
||||
&& <Box>This server has disabled tracking.</Box>
|
||||
|| failReason === JOB_REPORT_MENU_FAIL_REASON_NO_RECORDS
|
||||
&& <Box>You have no records.</Box>
|
||||
) || (
|
||||
<Box>
|
||||
<Section title="Total">
|
||||
<PlaytimeSection
|
||||
playtimes={{
|
||||
"Ghost": ghostTime,
|
||||
"Living": livingTime,
|
||||
}}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section title="Jobs">
|
||||
<PlaytimeSection
|
||||
playtimes={jobPlaytimes}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
<Section title="Special">
|
||||
<PlaytimeSection
|
||||
playtimes={specialPlaytimes}
|
||||
/>
|
||||
</Section>
|
||||
</Box>
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -14,7 +14,7 @@ export const TurretControl = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={305}
|
||||
height={172}>
|
||||
height={data.siliconUser ? 168 : 164}>
|
||||
<Window.Content>
|
||||
<InterfaceLockNoticeBox />
|
||||
<Section>
|
||||
|
||||
@@ -14,6 +14,7 @@ const VendingRow = (props, context) => {
|
||||
onstation,
|
||||
department,
|
||||
user,
|
||||
jobDiscount,
|
||||
} = data;
|
||||
const free = (
|
||||
!onstation
|
||||
@@ -22,9 +23,10 @@ const VendingRow = (props, context) => {
|
||||
!product.premium
|
||||
&& department
|
||||
&& user
|
||||
&& department === user.department
|
||||
)
|
||||
);
|
||||
const discount = department === user?.department;
|
||||
const redPrice = Math.round(product.price * jobDiscount);
|
||||
return (
|
||||
<Table.Row>
|
||||
<Table.Cell collapsing>
|
||||
@@ -79,7 +81,8 @@ const VendingRow = (props, context) => {
|
||||
|| product.price > data.user.cash
|
||||
)
|
||||
)}
|
||||
content={free ? 'FREE' : product.price + ' cr'}
|
||||
content={(free && discount)
|
||||
? `${redPrice} cr` : `${product.price} cr`}
|
||||
onClick={() => act('vend', {
|
||||
'ref': product.ref,
|
||||
})} />
|
||||
@@ -102,7 +105,7 @@ export const Vending = (props, context) => {
|
||||
let inventory;
|
||||
let custom = false;
|
||||
if (data.vending_machine_input) {
|
||||
inventory = data.vending_machine_input || [];
|
||||
inventory = data.vending_machine_input;
|
||||
custom = true;
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { decodeHtmlEntities } from 'common/string';
|
||||
import { useBackend, useLocalState } from '../../backend';
|
||||
import { Box, Button, LabeledList, NumberInput, Section } from '../../components';
|
||||
import { useBackend } from '../../backend';
|
||||
import { Button, LabeledList, NumberInput, Section } from '../../components';
|
||||
import { getGasLabel } from '../../constants';
|
||||
|
||||
|
||||
export const Vent = (props, context) => {
|
||||
const { vent } = props;
|
||||
const { act } = useBackend(context);
|
||||
|
||||
@@ -26,7 +26,7 @@ export const InterfaceLockNoticeBox = (props, context) => {
|
||||
// For silicon users
|
||||
if (siliconUser) {
|
||||
return (
|
||||
<NoticeBox color={siliconUser && 'grey'}>
|
||||
<NoticeBox color="grey">
|
||||
<Flex align="center">
|
||||
<Flex.Item>
|
||||
Interface lock status:
|
||||
|
||||
@@ -63,13 +63,11 @@ export const NtosWindow = (props, context) => {
|
||||
src={resolveAsset(PC_ntneticon)} />
|
||||
)}
|
||||
</Box>
|
||||
{!!PC_showbatteryicon && PC_batteryicon && (
|
||||
{!!(PC_showbatteryicon && PC_batteryicon) && (
|
||||
<Box inline mr={1}>
|
||||
{PC_batteryicon && (
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={resolveAsset(PC_batteryicon)} />
|
||||
)}
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={resolveAsset(PC_batteryicon)} />
|
||||
{PC_batterypercent && (
|
||||
PC_batterypercent
|
||||
)}
|
||||
|
||||
@@ -44,6 +44,7 @@ export class Window extends Component {
|
||||
render() {
|
||||
const {
|
||||
resizable,
|
||||
noClose,
|
||||
theme,
|
||||
title,
|
||||
children,
|
||||
@@ -74,7 +75,8 @@ export class Window extends Component {
|
||||
onClose={() => {
|
||||
logger.log('pressed close');
|
||||
dispatch(backendSuspendStart());
|
||||
}} />
|
||||
}}
|
||||
noClose={noClose} />
|
||||
<div
|
||||
className={classes([
|
||||
'Window__rest',
|
||||
@@ -142,6 +144,7 @@ const TitleBar = (props, context) => {
|
||||
className,
|
||||
title,
|
||||
status,
|
||||
noClose,
|
||||
fancy,
|
||||
onDragStart,
|
||||
onClose,
|
||||
@@ -180,7 +183,7 @@ const TitleBar = (props, context) => {
|
||||
<Icon name="bug" />
|
||||
</div>
|
||||
)}
|
||||
{!!fancy && (
|
||||
{!!fancy && !noClose && (
|
||||
<div
|
||||
className="TitleBar__close TitleBar__clickable"
|
||||
// IE8: Synthetic onClick event doesn't work on IE8.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"dompurify": "^2.0.12",
|
||||
"inferno": "^7.4.2",
|
||||
"inferno-vnode-flags": "^7.4.2",
|
||||
"marked": "^1.1.0",
|
||||
"marked": "^1.1.1",
|
||||
"tgui-dev-server": "workspace:*",
|
||||
"tgui-polyfill": "workspace:*"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Uses DOMPurify to purify/sanitise HTML.
|
||||
*/
|
||||
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
// Default values
|
||||
let defTag = [
|
||||
'br', 'code', 'li', 'p', 'pre',
|
||||
'span', 'table', 'td', 'tr', 'i',
|
||||
'th', 'ul', 'ol', 'menu', 'font', 'b',
|
||||
'center', 'table', 'tr', 'th', 'hr',
|
||||
];
|
||||
let defAttr = ['class', 'style'];
|
||||
|
||||
/**
|
||||
* Feed it a string and it should spit out a sanitized version.
|
||||
*
|
||||
* @param {string} input
|
||||
* @param {array} tags
|
||||
* @param {array} forbidAttr
|
||||
*/
|
||||
export const sanitizeText = (input, tags = defTag, forbidAttr = defAttr) => {
|
||||
// This is VERY important to think first if you NEED
|
||||
// the tag you put in here. We are pushing all this
|
||||
// though dangerouslySetInnerHTML and even though
|
||||
// the default DOMPurify kills javascript, it dosn't
|
||||
// kill href links or such
|
||||
return DOMPurify.sanitize(input, {
|
||||
ALLOWED_TAGS: tags,
|
||||
FORBID_ATTR: forbidAttr,
|
||||
});
|
||||
};
|
||||
@@ -76,6 +76,13 @@ $bg-map: colors.$bg-map !default;
|
||||
}
|
||||
}
|
||||
|
||||
.Button--hasContent.Button--iconPosition--right {
|
||||
.fa, .fas, .far {
|
||||
margin-right: 0px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.Button--ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020
|
||||
* @author ThePotato97 (https://github.com/ThePotato97)
|
||||
* @license ISC
|
||||
*/
|
||||
|
||||
.IconStack > .Icon {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
.IconStack {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 1.2em;
|
||||
line-height: 2em;
|
||||
vertical-align: middle;
|
||||
|
||||
&:after {
|
||||
color: transparent;
|
||||
content: '.';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2020 bobbahbrown (https://github.com/bobbahbrown)
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use '../base.scss';
|
||||
@use '../colors.scss';
|
||||
@use '../functions.scss' as *;
|
||||
|
||||
$fg-map: colors.$fg-map !default;
|
||||
$ring-color: #6a96c9 !default;
|
||||
|
||||
.RoundGauge {
|
||||
font-size: 1rem;
|
||||
width: 2.6em;
|
||||
height: 1.3em;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
||||
$pi: 3.1416;
|
||||
|
||||
.RoundGauge__ringTrack {
|
||||
fill: transparent;
|
||||
stroke: rgba(255, 255, 255, 0.1);
|
||||
stroke-width: 10;
|
||||
stroke-dasharray: 50 * $pi;
|
||||
stroke-dashoffset: 50 * $pi;
|
||||
}
|
||||
|
||||
.RoundGauge__ringFill {
|
||||
fill: transparent;
|
||||
stroke: $ring-color;
|
||||
stroke-width: 10;
|
||||
stroke-dasharray: 100 * $pi;
|
||||
transition: stroke 50ms;
|
||||
}
|
||||
|
||||
.RoundGauge__needle, .RoundGauge__ringFill {
|
||||
transition: transform 50ms ease-in-out;
|
||||
}
|
||||
|
||||
.RoundGauge__needleLine, .RoundGauge__needleMiddle {
|
||||
fill: colors.$bad;
|
||||
}
|
||||
|
||||
.RoundGauge__alert {
|
||||
fill-rule: evenodd;
|
||||
clip-rule: evenodd;
|
||||
stroke-linejoin: round;
|
||||
stroke-miterlimit: 2;
|
||||
fill: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.RoundGauge__alert.max {
|
||||
fill: colors.$bad;
|
||||
}
|
||||
|
||||
@each $color-name, $color-value in $fg-map {
|
||||
.RoundGauge--color--#{$color-name}.RoundGauge__ringFill {
|
||||
stroke: $color-value;
|
||||
}
|
||||
}
|
||||
|
||||
@each $color-name, $color-value in $fg-map {
|
||||
.RoundGauge__alert--#{$color-name} {
|
||||
fill: $color-value;
|
||||
transition: opacity 0.6s cubic-bezier(0.25, 1, 0.5, 1);
|
||||
animation: RoundGauge__alertAnim 1s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes RoundGauge__alertAnim {
|
||||
0% {
|
||||
opacity: 0.1;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.1;
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,12 @@ $shadow-offset: 0 0 !default;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.Section--scrollable .Section__content {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.Section--fill .Section__content {
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -76,7 +82,7 @@ $shadow-offset: 0 0 !default;
|
||||
|
||||
.Section--scrollable {
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.Section--level--1 .Section__titleText {
|
||||
|
||||
@@ -21,7 +21,7 @@ $border-radius: base.$border-radius !default;
|
||||
&::after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
white-space: pre;
|
||||
z-index: 2;
|
||||
padding: 0.5em 0.75em;
|
||||
transform: translateX(-50%);
|
||||
@@ -61,6 +61,26 @@ $border-radius: base.$border-radius !default;
|
||||
transform: translateX(-50%) translateY(-0.5em);
|
||||
}
|
||||
|
||||
.Tooltip--top-left::after {
|
||||
bottom: 100%;
|
||||
right: 50%;
|
||||
transform: translateX(12px) translateY(8px);
|
||||
}
|
||||
|
||||
.Tooltip--top-left:hover::after {
|
||||
transform: translateX(12px) translateY(-8px);
|
||||
}
|
||||
|
||||
.Tooltip--top-right::after {
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
transform: translateX(100%) translateY(-50%);
|
||||
}
|
||||
|
||||
.Tooltip--top-right:hover::after {
|
||||
transform: translateX(100%) translateY(-100%);
|
||||
}
|
||||
|
||||
.Tooltip--bottom::after {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Copyright (c) 2020 bobbahbrown (https://github.com/bobbahbrown)
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use '../colors.scss';
|
||||
|
||||
.AlertModal__Message {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.AlertModal__Buttons {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.AlertModal__Loader {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.AlertModal__LoaderProgress {
|
||||
position: absolute;
|
||||
transition: background-color 500ms, width 500ms;
|
||||
background-color: colors.bg(colors.$primary);
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020
|
||||
* @author ThePotato97 (https://github.com/ThePotato97)
|
||||
* @license ISC
|
||||
*/
|
||||
|
||||
.Paper {
|
||||
&__Stamp {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
&__Page {
|
||||
word-break: break-word;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
.Safe__engraving {
|
||||
position: absolute;
|
||||
width: 95%;
|
||||
height: 96%;
|
||||
left: 2.5%;
|
||||
top: 2%;
|
||||
border: 5px outset #3e4f6a;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.Safe__engraving-arrow {
|
||||
color: #35435a;
|
||||
}
|
||||
|
||||
.Safe__engraving-hinge {
|
||||
content: " ";
|
||||
background-color: #191f2a;
|
||||
width: 25px;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
right: -15px;
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.Safe__dialer {
|
||||
margin-bottom: 1.25rem;
|
||||
.Button {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.Safe__dialer-right {
|
||||
.Button {
|
||||
i {
|
||||
z-index: -100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Safe__dialer-number {
|
||||
color: #bbbbbb;
|
||||
display: inline;
|
||||
background-color: #191f2a;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.Safe__contents {
|
||||
border: 10px solid #191f2a;
|
||||
background-color: #0f131a;
|
||||
height: calc(85% + 7.5px);
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.Safe__help {
|
||||
position: absolute;
|
||||
top: 73%;
|
||||
left: 10px;
|
||||
width: 50%;
|
||||
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif;
|
||||
font-style: italic;
|
||||
color: black;
|
||||
box-shadow: 5px 5px #111111;
|
||||
background-image: linear-gradient(to bottom,
|
||||
#b2ae74 0%,
|
||||
#8e8b5d 100%);
|
||||
transform: rotate(-1deg);
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 40px;
|
||||
background-image: linear-gradient(to bottom,
|
||||
transparent 0%,
|
||||
#ffffff 100%);
|
||||
box-shadow: 1px 1px #111111;
|
||||
opacity: 0.2;
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
left: calc(50% - 12px);
|
||||
transform: rotate(-5deg);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
@include meta.load-css('./components/Divider.scss');
|
||||
@include meta.load-css('./components/Dropdown.scss');
|
||||
@include meta.load-css('./components/Flex.scss');
|
||||
@include meta.load-css('./components/Icon.scss');
|
||||
@include meta.load-css('./components/Input.scss');
|
||||
@include meta.load-css('./components/Knob.scss');
|
||||
@include meta.load-css('./components/LabeledList.scss');
|
||||
@@ -31,6 +32,7 @@
|
||||
@include meta.load-css('./components/NoticeBox.scss');
|
||||
@include meta.load-css('./components/NumberInput.scss');
|
||||
@include meta.load-css('./components/ProgressBar.scss');
|
||||
@include meta.load-css('./components/RoundGauge.scss');
|
||||
@include meta.load-css('./components/Section.scss');
|
||||
@include meta.load-css('./components/Slider.scss');
|
||||
@include meta.load-css('./components/Table.scss');
|
||||
@@ -39,9 +41,12 @@
|
||||
@include meta.load-css('./components/Tooltip.scss');
|
||||
|
||||
// Interfaces
|
||||
@include meta.load-css('./interfaces/AlertModal.scss');
|
||||
@include meta.load-css('./interfaces/CameraConsole.scss');
|
||||
@include meta.load-css('./interfaces/NuclearBomb.scss');
|
||||
@include meta.load-css('./interfaces/Paper.scss');
|
||||
@include meta.load-css('./interfaces/Roulette.scss');
|
||||
@include meta.load-css('./interfaces/Safe.scss');
|
||||
|
||||
// Layouts
|
||||
@include meta.load-css('./layouts/Layout.scss');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user