mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-30 16:47:13 +01:00
tgui: Knobs, Sliders and Performance Improvements (#50483)
* Optimize Box * Some tweaks to vending machines * Layout debugger, initial work on sliders * Slider, Knob, docs, refactoring, etc * Rebuild tgui * Knobby Canister * Remove imports
This commit is contained in:
@@ -5,8 +5,8 @@
|
||||
desc = "A canister for the storage of gas."
|
||||
icon_state = "yellow"
|
||||
density = TRUE
|
||||
ui_x = 420
|
||||
ui_y = 405
|
||||
ui_x = 346
|
||||
ui_y = 268
|
||||
|
||||
var/valve_open = FALSE
|
||||
var/obj/machinery/atmospherics/components/binary/passive_gate/pump
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
use_power = NO_POWER_USE
|
||||
circuit = /obj/item/circuitboard/machine/smes
|
||||
ui_x = 340
|
||||
ui_y = 440
|
||||
ui_y = 350
|
||||
|
||||
var/capacity = 5e6 // maximum charge
|
||||
var/charge = 0 // actual charge
|
||||
@@ -327,23 +327,21 @@
|
||||
|
||||
/obj/machinery/power/smes/ui_data()
|
||||
var/list/data = list(
|
||||
"capacityPercent" = round(100*charge/capacity, 0.1),
|
||||
"capacity" = capacity,
|
||||
"capacityPercent" = round(100*charge/capacity, 0.1),
|
||||
"charge" = charge,
|
||||
|
||||
"inputAttempt" = input_attempt,
|
||||
"inputting" = inputting,
|
||||
"inputLevel" = input_level,
|
||||
"inputLevel_text" = DisplayPower(input_level),
|
||||
"inputLevelMax" = input_level_max,
|
||||
"inputAvailable" = DisplayPower(input_available),
|
||||
|
||||
"inputAvailable" = input_available,
|
||||
"outputAttempt" = output_attempt,
|
||||
"outputting" = outputting,
|
||||
"outputLevel" = output_level,
|
||||
"outputLevel_text" = DisplayPower(output_level),
|
||||
"outputLevelMax" = output_level_max,
|
||||
"outputUsed" = DisplayPower(output_used)
|
||||
"outputUsed" = output_used,
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
+2
-2
@@ -625,7 +625,7 @@ rules:
|
||||
## Prevent invalid characters from appearing in markup
|
||||
react/no-unescaped-entities: error
|
||||
## Prevent usage of unknown DOM property (fixable)
|
||||
react/no-unknown-property: error
|
||||
# react/no-unknown-property: error
|
||||
## Prevent usage of unsafe lifecycle methods
|
||||
react/no-unsafe: error
|
||||
## Prevent definitions of unused prop types
|
||||
@@ -705,7 +705,7 @@ rules:
|
||||
## Validate JSX has key prop when in array or iterator
|
||||
react/jsx-key: error
|
||||
## Validate JSX maximum depth
|
||||
react/jsx-max-depth: [error, { max: 6 }] ## Generous
|
||||
react/jsx-max-depth: [error, { max: 10 }] ## Generous
|
||||
## Limit maximum of props on a single line in JSX (fixable)
|
||||
# react/jsx-max-props-per-line: error
|
||||
## Prevent usage of .bind() and arrow functions in JSX props
|
||||
|
||||
+123
-18
@@ -15,7 +15,7 @@ People come to tgui from different backgrounds and with different
|
||||
learning styles. Whether you prefer a more theoretical or a practical
|
||||
approach, we hope you’ll find this section helpful.
|
||||
|
||||
### Practical tutorial
|
||||
### Practical Tutorial
|
||||
|
||||
If you are completely new to frontend and prefer to **learn by doing**,
|
||||
start with our [practical tutorial](docs/tutorial-and-examples.md).
|
||||
@@ -123,7 +123,26 @@ Note that in Windows, you have to go through Advanced System Settings,
|
||||
System Properties and then open Environment Variables window to do the
|
||||
same thing. You may need to reboot after this.
|
||||
|
||||
## Project structure
|
||||
## Developer Tools
|
||||
|
||||
When developing with `tgui-dev-server`, you will have access to certain
|
||||
development only features.
|
||||
|
||||
**Debug Logs.**
|
||||
When running server via `bin/tgui --dev --debug`, server will print debug
|
||||
logs and time spent on rendering. Use this information to optimize your
|
||||
code, and try to keep re-renders below 16ms.
|
||||
|
||||
**Kitchen Sink.**
|
||||
Press `Ctrl+Alt+=` to open the KitchenSink interface. This interface is a
|
||||
playground to test various tgui components.
|
||||
|
||||
**Layout Debugger.**
|
||||
Press `Ctrl+Alt+-` to toggle the *layout debugger*. It will show outlines of
|
||||
all tgui elements, which makes it easy to understand how everything comes
|
||||
together, and can reveal certain layout bugs which are not normally visible.
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `/packages` - Each folder here represents a self-contained Node module.
|
||||
- `/packages/common` - Helper functions
|
||||
@@ -138,17 +157,17 @@ interfaces, otherwise they simply won't load.
|
||||
window elements, like the titlebar, buttons, resize handlers. Calls
|
||||
`routes.js` to decide which component to render.
|
||||
- `/packages/tgui/styles/main.scss` - CSS entry point.
|
||||
- `/packages/tgui/styles/atomic.scss` - Atomic CSS classes.
|
||||
- `/packages/tgui/styles/atomic` - Atomic CSS classes.
|
||||
These are very simple, tiny, reusable CSS classes which you can use and
|
||||
combine to change appearance of your elements. Keep them small.
|
||||
- `/packages/tgui/styles/components.scss` - CSS classes which are used
|
||||
- `/packages/tgui/styles/components` - CSS classes which are used
|
||||
in UI components, and most of the stylesheets referenced here are located
|
||||
in `/packages/tgui/components`. These stylesheets closely follow the
|
||||
[BEM](https://en.bem.info/methodology/) methodology.
|
||||
- `/packages/tgui/styles/functions.scss` - Useful SASS functions.
|
||||
Stuff like `lighten`, `darken`, `luminance` are defined here.
|
||||
|
||||
## Component reference
|
||||
## Component Reference
|
||||
|
||||
> Notice: This documentation might be out of date, so always check the source
|
||||
> code to see the most up-to-date information.
|
||||
@@ -162,17 +181,14 @@ it is used a lot in this framework.
|
||||
|
||||
There are a few important semantics you need to know about:
|
||||
|
||||
- `content` prop is a synonym to a `children` prop.
|
||||
- Some elements support a `content` prop, which is a synonym to a
|
||||
`children` prop.
|
||||
- `content` is better used when your element is a self-closing tag
|
||||
(like `<Button content="Hello" />`), and when content is small and simple
|
||||
enough to fit in a prop. Keep in mind, that this prop is **not** native
|
||||
to React, and is a feature of this component system.
|
||||
- `children` is better used when your element is a full tag (like
|
||||
`<Button>Hello</Button>`), and when content is long and complex. This is
|
||||
a native React prop (unlike `content`), and contains all elements you
|
||||
defined between the opening and the closing tag of an element.
|
||||
- You should never use both on a same element.
|
||||
to React, and is only available on these components: `Button`, `Tooltip`.
|
||||
- You should never use `children` explicitly as a prop on an element.
|
||||
Instead open a full tag, and place children or text inside the tag.
|
||||
- Inferno supports both camelcase (`onClick`) and lowercase (`onclick`)
|
||||
event names.
|
||||
- Camel case names are what's called "synthetic" events, and are the
|
||||
@@ -183,8 +199,8 @@ event names.
|
||||
- Lower case names are native browser events and should be used sparingly,
|
||||
for example when you need an explicit IE8 support. **DO NOT** use
|
||||
lowercase event handlers unless you really know what you are doing.
|
||||
- [Button](#button) component straight up does not support lowercase event
|
||||
handlers. Use the camel case `onClick` instead.
|
||||
- [Button](#button) component does not support lowercase `onclick` event.
|
||||
Use the camel case `onClick` instead.
|
||||
|
||||
### `AnimatedNumber`
|
||||
|
||||
@@ -430,6 +446,8 @@ Props:
|
||||
|
||||
Dims surrounding area to emphasize content placed inside.
|
||||
|
||||
Content is automatically centered inside the dimmer.
|
||||
|
||||
Props:
|
||||
|
||||
- See inherited props: [Box](#box)
|
||||
@@ -498,6 +516,8 @@ Props:
|
||||
(1 unit - 0.5em). Does not directly relate to a flex css property
|
||||
(adds a modifier class under the hood), and only integer numbers are
|
||||
supported.
|
||||
- `inline: boolean` - Makes flexbox container inline, with similar behavior
|
||||
to an `inline` property on a `Box`.
|
||||
- `direction: string` - This establishes the main-axis, thus defining the
|
||||
direction flex items are placed in the flex container.
|
||||
- `row` (default) - left to right.
|
||||
@@ -549,14 +569,22 @@ item should take up. This number is unit-less and is relative to other
|
||||
siblings.
|
||||
- `shrink: number` - This defines the ability for a flex item to shrink
|
||||
if necessary. Inverse of `grow`.
|
||||
- `basis: string` - This defines the default size of an element before the
|
||||
remaining space is distributed. It can be a length (e.g. `20%`, `5rem`, etc.),
|
||||
- `basis: string` - This defines the default size of an element before any
|
||||
flex-related calculations are done. Has to be a length (e.g. `20%`, `5rem`),
|
||||
an `auto` or `content` keyword.
|
||||
- **Important:** IE11 flex is buggy, and auto width/height calculations
|
||||
can sometimes end up in a circular dependency. This usually happens, when
|
||||
working with tables inside flex (they have wacky internal widths and such).
|
||||
Setting basis to `0` breaks the loop and fixes all of the problems.
|
||||
- `align: string` - This allows the default alignment (or the one specified by
|
||||
align-items) to be overridden for individual flex items. See: [Flex](#flex).
|
||||
|
||||
### `Grid`
|
||||
|
||||
> **Deprecated:** This component is no longer recommended due to the variety
|
||||
> of bugs that come with table-based layouts.
|
||||
> We recommend using [Flex](#flex) instead.
|
||||
|
||||
Helps you to divide horizontal space into two or more equal sections.
|
||||
It is essentially a single-row `Table`, but with some extra features.
|
||||
|
||||
@@ -565,10 +593,14 @@ Example:
|
||||
```jsx
|
||||
<Grid>
|
||||
<Grid.Column>
|
||||
<Section title="Section 1" content="Hello world!" />
|
||||
<Section title="Section 1">
|
||||
Hello world!
|
||||
</Section>
|
||||
</Grid.Column>
|
||||
<Grid.Column size={2}>
|
||||
<Section title="Section 2" content="Hello world!" />
|
||||
<Section title="Section 2">
|
||||
Hello world!
|
||||
</Section>
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
```
|
||||
@@ -627,6 +659,44 @@ when this happens. Useful for things like chat inputs.
|
||||
the text by either unfocusing the input box, or by pressing the Enter key.
|
||||
- `onInput: (e, value) => void` - An event, which fires on every keypress.
|
||||
|
||||
### `Knob`
|
||||
|
||||
A radial control, which allows dialing in precise values by dragging it
|
||||
up and down.
|
||||
|
||||
Single click opens an input box to manually type in a number.
|
||||
|
||||
Props:
|
||||
|
||||
- See inherited props: [Box](#box)
|
||||
- `animated: boolean` - Animates the value if it was changed externally.
|
||||
- `bipolar: boolean` - Knob can be bipolar or unipolar.
|
||||
- `size: number` - Relative size of the knob. `1` is normal size, `2` is two
|
||||
times bigger. Fractional numbers are supported.
|
||||
- `color: string` - Color of the outer ring around the knob.
|
||||
- `value: number` - Value itself, controls the position of the cursor.
|
||||
- `unit: string` - Unit to display to the right of value.
|
||||
- `minValue: number` - Lowest possible value.
|
||||
- `maxValue: number` - Highest possible value.
|
||||
- `fillValue: number` - If set, this value will be used to set the fill
|
||||
percentage of the outer ring independently of the main value.
|
||||
- `ranges: { color: [from, to] }` - Applies a `color` to the outer ring around
|
||||
the knob based on whether the value lands in the range between `from` and `to`.
|
||||
See an example of this prop in [ProgressBar](#progressbar).
|
||||
- `step: number` (default: 1) - Adjust value by this amount when
|
||||
dragging the input.
|
||||
- `stepPixelSize: number` (default: 1) - Screen distance mouse needs
|
||||
to travel to adjust value by one `step`.
|
||||
- `format: value => value` - Format value using this function before
|
||||
displaying it.
|
||||
- `suppressFlicker: number` - A number in milliseconds, for which the input
|
||||
will hold off from updating while events propagate through the backend.
|
||||
Default is about 250ms, increase it if you still see flickering.
|
||||
- `onChange: (e, value) => void` - An event, which fires when you release
|
||||
the input, or successfully enter a number.
|
||||
- `onDrag: (e, value) => void` - An event, which fires about every 500ms
|
||||
when you drag the input up and down, on release and on manual editing.
|
||||
|
||||
### `LabeledList`
|
||||
|
||||
LabeledList is a continuous, vertical list of text and other content, where
|
||||
@@ -795,6 +865,41 @@ means deeper level of nesting. Must be an integer number.
|
||||
- `buttons: any` - Buttons to render aside the section title.
|
||||
- `content/children: any` - Content of this section.
|
||||
|
||||
### `Slider`
|
||||
|
||||
A horizontal, [ProgressBar](#progressbar)-like control, which allows dialing
|
||||
in precise values by dragging it left and right.
|
||||
|
||||
Single click opens an input box to manually type in a number.
|
||||
|
||||
Props:
|
||||
|
||||
- See inherited props: [Box](#box)
|
||||
- `animated: boolean` - Animates the value if it was changed externally.
|
||||
- `color: string` - Color of the slider.
|
||||
- `value: number` - Value itself, controls the position of the cursor.
|
||||
- `unit: string` - Unit to display to the right of value.
|
||||
- `minValue: number` - Lowest possible value.
|
||||
- `maxValue: number` - Highest possible value.
|
||||
- `fillValue: number` - If set, this value will be used to set the fill
|
||||
percentage of the progress bar filler independently of the main value.
|
||||
- `ranges: { color: [from, to] }` - Applies a `color` to the slider
|
||||
based on whether the value lands in the range between `from` and `to`.
|
||||
See an example of this prop in [ProgressBar](#progressbar).
|
||||
- `step: number` (default: 1) - Adjust value by this amount when
|
||||
dragging the input.
|
||||
- `stepPixelSize: number` (default: 1) - Screen distance mouse needs
|
||||
to travel to adjust value by one `step`.
|
||||
- `format: value => value` - Format value using this function before
|
||||
displaying it.
|
||||
- `suppressFlicker: number` - A number in milliseconds, for which the input
|
||||
will hold off from updating while events propagate through the backend.
|
||||
Default is about 250ms, increase it if you still see flickering.
|
||||
- `onChange: (e, value) => void` - An event, which fires when you release
|
||||
the input, or successfully enter a number.
|
||||
- `onDrag: (e, value) => void` - An event, which fires about every 500ms
|
||||
when you drag the input up and down, on release and on manual editing.
|
||||
|
||||
### `Table`
|
||||
|
||||
A straight forward mapping to a standard html table, which is slightly
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
/**
|
||||
* Limits a number to the range between 'min' and 'max'.
|
||||
*/
|
||||
export const clamp = (value, min = 0, max = 1) => {
|
||||
export const clamp = (value, min, max) => {
|
||||
return Math.max(min, Math.min(value, max));
|
||||
};
|
||||
|
||||
/**
|
||||
* Limits a number between 0 and 1.
|
||||
*/
|
||||
export const clamp01 = value => clamp(value, 0, 1);
|
||||
|
||||
/**
|
||||
* Scales a number to fit into the range between min and max.
|
||||
*/
|
||||
export const scale = (value, min, max) => {
|
||||
return (value - min) / (max - min);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a rounded number.
|
||||
* TODO: Replace this native rounding function with a more robust one.
|
||||
@@ -15,5 +27,31 @@ export const round = value => Math.round(value);
|
||||
* Returns a string representing a number in fixed point notation.
|
||||
*/
|
||||
export const toFixed = (value, fractionDigits = 0) => {
|
||||
return Number(value).toFixed(fractionDigits);
|
||||
return Number(value).toFixed(Math.max(fractionDigits, 0));
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether a value is within the provided range.
|
||||
*
|
||||
* Range is an array of two numbers, for example: [0, 15].
|
||||
*/
|
||||
export const inRange = (value, range) => {
|
||||
return range
|
||||
&& value >= range[0]
|
||||
&& value <= range[1];
|
||||
};
|
||||
|
||||
/**
|
||||
* Walks over the object with ranges, comparing value against every range,
|
||||
* and returns the key of the first matching range.
|
||||
*
|
||||
* Range is an array of two numbers, for example: [0, 15].
|
||||
*/
|
||||
export const keyOfMatchingRange = (value, ranges) => {
|
||||
for (let rangeName of Object.keys(ranges)) {
|
||||
const range = ranges[rangeName];
|
||||
if (inRange(value, range)) {
|
||||
return rangeName;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,43 +123,53 @@ export const computeBoxProps = props => {
|
||||
}
|
||||
}
|
||||
// Concatenate styles
|
||||
Object.assign(computedStyles, props.style);
|
||||
let style = '';
|
||||
for (let attrName of Object.keys(computedStyles)) {
|
||||
const attrValue = computedStyles[attrName];
|
||||
style += attrName + ':' + attrValue + ';';
|
||||
}
|
||||
if (props.style) {
|
||||
for (let attrName of Object.keys(props.style)) {
|
||||
const attrValue = props.style[attrName];
|
||||
style += attrName + ':' + attrValue + ';';
|
||||
}
|
||||
}
|
||||
if (style.length > 0) {
|
||||
computedProps.style = style;
|
||||
}
|
||||
return computedProps;
|
||||
};
|
||||
|
||||
export const computeBoxClassName = props => {
|
||||
const color = props.textColor || props.color;
|
||||
const backgroundColor = props.backgroundColor;
|
||||
return classes([
|
||||
isColorClass(color) && 'color-' + color,
|
||||
isColorClass(backgroundColor) && 'color-bg-' + backgroundColor,
|
||||
]);
|
||||
};
|
||||
|
||||
export const Box = props => {
|
||||
const {
|
||||
as = 'div',
|
||||
className,
|
||||
content,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
const color = props.textColor || props.color;
|
||||
const backgroundColor = props.backgroundColor;
|
||||
// Render props
|
||||
if (typeof children === 'function') {
|
||||
return children(computeBoxProps(props));
|
||||
}
|
||||
const computedClassName = typeof className === 'string'
|
||||
? className + ' ' + computeBoxClassName(rest)
|
||||
: computeBoxClassName(rest);
|
||||
const computedProps = computeBoxProps(rest);
|
||||
// Render a wrapper element
|
||||
return createVNode(
|
||||
VNodeFlags.HtmlElement,
|
||||
as,
|
||||
classes([
|
||||
className,
|
||||
isColorClass(color) && 'color-' + color,
|
||||
isColorClass(backgroundColor) && 'color-bg-' + backgroundColor,
|
||||
]),
|
||||
content || children,
|
||||
computedClassName,
|
||||
children,
|
||||
ChildFlags.UnknownChildren,
|
||||
computedProps);
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@ export const Button = props => {
|
||||
// IE8: Use a lowercase "onclick" because synthetic events are fucked.
|
||||
// IE8: Use an "unselectable" prop because "user-select" doesn't work.
|
||||
return (
|
||||
<Box as="span"
|
||||
<Box
|
||||
className={classes([
|
||||
'Button',
|
||||
fluid && 'Button--fluid',
|
||||
|
||||
@@ -11,8 +11,9 @@ export const ColorBox = props => {
|
||||
])}
|
||||
color={content ? null : 'transparent'}
|
||||
backgroundColor={color}
|
||||
content={content || '.'}
|
||||
{...rest} />
|
||||
{...rest}>
|
||||
{content || '.'}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
import { clamp } from 'common/math';
|
||||
import { pureComponentHooks } from 'common/react';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { AnimatedNumber } from './AnimatedNumber';
|
||||
|
||||
/**
|
||||
* Reduces screen offset to a single number based on the matrix provided.
|
||||
*/
|
||||
const getScalarScreenOffset = (e, matrix) => {
|
||||
return e.screenX * matrix[0] + e.screenY * matrix[1];
|
||||
};
|
||||
|
||||
export class DraggableControl extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.inputRef = createRef();
|
||||
this.state = {
|
||||
value: props.value,
|
||||
dragging: false,
|
||||
editing: false,
|
||||
internalValue: null,
|
||||
origin: null,
|
||||
suppressingFlicker: false,
|
||||
};
|
||||
|
||||
// Suppresses flickering while the value propagates through the backend
|
||||
this.flickerTimer = null;
|
||||
this.suppressFlicker = () => {
|
||||
const { suppressFlicker } = this.props;
|
||||
if (suppressFlicker > 0) {
|
||||
this.setState({
|
||||
suppressingFlicker: true,
|
||||
});
|
||||
clearTimeout(this.flickerTimer);
|
||||
this.flickerTimer = setTimeout(() => this.setState({
|
||||
suppressingFlicker: false,
|
||||
}), suppressFlicker);
|
||||
}
|
||||
};
|
||||
|
||||
this.handleDragStart = e => {
|
||||
const {
|
||||
value,
|
||||
dragMatrix,
|
||||
} = this.props;
|
||||
const { editing } = this.state;
|
||||
if (editing) {
|
||||
return;
|
||||
}
|
||||
document.body.style['pointer-events'] = 'none';
|
||||
this.ref = e.target;
|
||||
this.setState({
|
||||
dragging: false,
|
||||
origin: getScalarScreenOffset(e, dragMatrix),
|
||||
value,
|
||||
internalValue: value,
|
||||
});
|
||||
this.timer = setTimeout(() => {
|
||||
this.setState({
|
||||
dragging: true,
|
||||
});
|
||||
}, 250);
|
||||
this.dragInterval = setInterval(() => {
|
||||
const { dragging, value } = this.state;
|
||||
const { onDrag } = this.props;
|
||||
if (dragging && onDrag) {
|
||||
onDrag(e, value);
|
||||
}
|
||||
}, 500);
|
||||
document.addEventListener('mousemove', this.handleDragMove);
|
||||
document.addEventListener('mouseup', this.handleDragEnd);
|
||||
};
|
||||
|
||||
this.handleDragMove = e => {
|
||||
const {
|
||||
minValue,
|
||||
maxValue,
|
||||
step,
|
||||
stepPixelSize,
|
||||
dragMatrix,
|
||||
} = this.props;
|
||||
this.setState(prevState => {
|
||||
const state = { ...prevState };
|
||||
const offset = getScalarScreenOffset(e, dragMatrix) - state.origin;
|
||||
if (prevState.dragging) {
|
||||
const stepOffset = Number.isFinite(minValue)
|
||||
? minValue % step
|
||||
: 0;
|
||||
// Translate mouse movement to value
|
||||
// Give it some headroom (by increasing clamp range by 1 step)
|
||||
state.internalValue = clamp(
|
||||
state.internalValue
|
||||
+ offset * step / stepPixelSize,
|
||||
minValue - step,
|
||||
maxValue + step);
|
||||
// Clamp the final value
|
||||
state.value = clamp(
|
||||
state.internalValue
|
||||
- state.internalValue % step
|
||||
+ stepOffset,
|
||||
minValue,
|
||||
maxValue);
|
||||
state.origin = getScalarScreenOffset(e, dragMatrix);
|
||||
}
|
||||
else if (Math.abs(offset) > 4) {
|
||||
state.dragging = true;
|
||||
}
|
||||
return state;
|
||||
});
|
||||
};
|
||||
|
||||
this.handleDragEnd = e => {
|
||||
const {
|
||||
onChange,
|
||||
onDrag,
|
||||
} = this.props;
|
||||
const {
|
||||
dragging,
|
||||
value,
|
||||
internalValue,
|
||||
} = this.state;
|
||||
document.body.style['pointer-events'] = 'auto';
|
||||
clearTimeout(this.timer);
|
||||
clearInterval(this.dragInterval);
|
||||
this.setState({
|
||||
dragging: false,
|
||||
editing: !dragging,
|
||||
origin: null,
|
||||
});
|
||||
document.removeEventListener('mousemove', this.handleDragMove);
|
||||
document.removeEventListener('mouseup', this.handleDragEnd);
|
||||
if (dragging) {
|
||||
this.suppressFlicker();
|
||||
if (onChange) {
|
||||
onChange(e, value);
|
||||
}
|
||||
if (onDrag) {
|
||||
onDrag(e, value);
|
||||
}
|
||||
}
|
||||
else if (this.inputRef) {
|
||||
const input = this.inputRef.current;
|
||||
input.value = internalValue;
|
||||
// IE8: Dies when trying to focus a hidden element
|
||||
// (Error: Object does not support this action)
|
||||
try {
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
dragging,
|
||||
editing,
|
||||
value: intermediateValue,
|
||||
suppressingFlicker,
|
||||
} = this.state;
|
||||
const {
|
||||
animated,
|
||||
value,
|
||||
unit,
|
||||
minValue,
|
||||
maxValue,
|
||||
format,
|
||||
onChange,
|
||||
onDrag,
|
||||
children,
|
||||
// Input props
|
||||
height,
|
||||
lineHeight,
|
||||
fontSize,
|
||||
} = this.props;
|
||||
let displayValue = value;
|
||||
if (dragging || suppressingFlicker) {
|
||||
displayValue = intermediateValue;
|
||||
}
|
||||
// Setup a display element
|
||||
// Shows a formatted number based on what we are currently doing
|
||||
// with the draggable surface.
|
||||
const renderDisplayElement = value => (
|
||||
value + (unit ? ' ' + unit : '')
|
||||
);
|
||||
const displayElement = (
|
||||
animated && !dragging && !suppressingFlicker && (
|
||||
<AnimatedNumber
|
||||
value={displayValue}
|
||||
format={format}>
|
||||
{renderDisplayElement}
|
||||
</AnimatedNumber>
|
||||
) || (
|
||||
renderDisplayElement(format
|
||||
? format(displayValue)
|
||||
: displayValue)
|
||||
)
|
||||
);
|
||||
// Setup an input element
|
||||
// Handles direct input via the keyboard
|
||||
const inputElement = (
|
||||
<input
|
||||
ref={this.inputRef}
|
||||
className="NumberInput__input"
|
||||
style={{
|
||||
display: !editing ? 'none' : undefined,
|
||||
height: height,
|
||||
'line-height': lineHeight,
|
||||
'font-size': fontSize,
|
||||
}}
|
||||
onBlur={e => {
|
||||
if (!editing) {
|
||||
return;
|
||||
}
|
||||
const value = clamp(e.target.value, minValue, maxValue);
|
||||
this.setState({
|
||||
editing: false,
|
||||
value,
|
||||
});
|
||||
this.suppressFlicker();
|
||||
if (onChange) {
|
||||
onChange(e, value);
|
||||
}
|
||||
if (onDrag) {
|
||||
onDrag(e, value);
|
||||
}
|
||||
}}
|
||||
onKeyDown={e => {
|
||||
if (e.keyCode === 13) {
|
||||
const value = clamp(e.target.value, minValue, maxValue);
|
||||
this.setState({
|
||||
editing: false,
|
||||
value,
|
||||
});
|
||||
this.suppressFlicker();
|
||||
if (onChange) {
|
||||
onChange(e, value);
|
||||
}
|
||||
if (onDrag) {
|
||||
onDrag(e, value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (e.keyCode === 27) {
|
||||
this.setState({
|
||||
editing: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}} />
|
||||
);
|
||||
// Return a part of the state for higher-level components to use.
|
||||
return children({
|
||||
dragging,
|
||||
editing,
|
||||
value,
|
||||
displayValue,
|
||||
displayElement,
|
||||
inputElement,
|
||||
handleDragStart: this.handleDragStart,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
DraggableControl.defaultHooks = pureComponentHooks;
|
||||
DraggableControl.defaultProps = {
|
||||
minValue: -Infinity,
|
||||
maxValue: +Infinity,
|
||||
step: 1,
|
||||
stepPixelSize: 1,
|
||||
suppressFlicker: 50,
|
||||
dragMatrix: [1, 0],
|
||||
};
|
||||
@@ -8,12 +8,14 @@ export const computeFlexProps = props => {
|
||||
wrap,
|
||||
align,
|
||||
justify,
|
||||
inline,
|
||||
spacing = 0,
|
||||
...rest
|
||||
} = props;
|
||||
return {
|
||||
className: classes([
|
||||
'Flex',
|
||||
inline && 'Flex--inline',
|
||||
spacing > 0 && 'Flex--spacing--' + spacing,
|
||||
className,
|
||||
]),
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import { keyOfMatchingRange, scale } from 'common/math';
|
||||
import { classes } from 'common/react';
|
||||
import { computeBoxClassName, computeBoxProps } from './Box';
|
||||
import { DraggableControl } from './DraggableControl';
|
||||
|
||||
export const Knob = props => {
|
||||
const {
|
||||
// Draggable props (passthrough)
|
||||
animated,
|
||||
format,
|
||||
maxValue,
|
||||
minValue,
|
||||
onChange,
|
||||
onDrag,
|
||||
step,
|
||||
stepPixelSize,
|
||||
suppressFlicker,
|
||||
unit,
|
||||
value,
|
||||
// Own props
|
||||
className,
|
||||
style,
|
||||
fillValue,
|
||||
color,
|
||||
ranges = {},
|
||||
size,
|
||||
bipolar,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<DraggableControl
|
||||
dragMatrix={[0, -1]}
|
||||
{...{
|
||||
animated,
|
||||
format,
|
||||
maxValue,
|
||||
minValue,
|
||||
onChange,
|
||||
onDrag,
|
||||
step,
|
||||
stepPixelSize,
|
||||
suppressFlicker,
|
||||
unit,
|
||||
value,
|
||||
}}>
|
||||
{control => {
|
||||
const {
|
||||
dragging,
|
||||
editing,
|
||||
value,
|
||||
displayValue,
|
||||
displayElement,
|
||||
inputElement,
|
||||
handleDragStart,
|
||||
} = control;
|
||||
const scaledFillValue = scale(
|
||||
fillValue ?? displayValue,
|
||||
minValue,
|
||||
maxValue);
|
||||
const scaledDisplayValue = scale(
|
||||
displayValue,
|
||||
minValue,
|
||||
maxValue);
|
||||
const effectiveColor = color
|
||||
|| keyOfMatchingRange(fillValue ?? value, ranges)
|
||||
|| 'default';
|
||||
const rotation = (scaledDisplayValue - 0.5) * 270;
|
||||
return (
|
||||
<div
|
||||
className={classes([
|
||||
'Knob',
|
||||
'Knob--color--' + effectiveColor,
|
||||
bipolar && 'Knob--bipolar',
|
||||
className,
|
||||
computeBoxClassName(rest),
|
||||
])}
|
||||
style={{
|
||||
'font-size': size + 'rem',
|
||||
...style,
|
||||
}}
|
||||
{...computeBoxProps(rest)}
|
||||
onMouseDown={handleDragStart}>
|
||||
<div className="Knob__circle">
|
||||
<div
|
||||
className="Knob__cursorBox"
|
||||
style={{
|
||||
transform: `rotate(${rotation}deg)`,
|
||||
}}>
|
||||
<div className="Knob__cursor" />
|
||||
</div>
|
||||
</div>
|
||||
{dragging && (
|
||||
<div className="Knob__popupValue">
|
||||
{displayElement}
|
||||
</div>
|
||||
)}
|
||||
<svg
|
||||
className="Knob__ring Knob__ringTrackPivot"
|
||||
viewBox="0 0 100 100">
|
||||
<circle
|
||||
className="Knob__ringTrack"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="50" />
|
||||
</svg>
|
||||
<svg
|
||||
className="Knob__ring Knob__ringFillPivot"
|
||||
viewBox="0 0 100 100">
|
||||
<circle
|
||||
className="Knob__ringFill"
|
||||
style={{
|
||||
'stroke-dashoffset': (
|
||||
((bipolar ? 2.75 : 2.00) - scaledFillValue * 1.5)
|
||||
* Math.PI * 50
|
||||
),
|
||||
}}
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="50" />
|
||||
</svg>
|
||||
{inputElement}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</DraggableControl>
|
||||
);
|
||||
};
|
||||
@@ -77,13 +77,15 @@ export class NumberInput extends Component {
|
||||
// Give it some headroom (by increasing clamp range by 1 step)
|
||||
state.internalValue = clamp(
|
||||
state.internalValue + offset * step / stepPixelSize,
|
||||
minValue - step, maxValue + step);
|
||||
minValue - step,
|
||||
maxValue + step);
|
||||
// Clamp the final value
|
||||
state.value = clamp(
|
||||
state.internalValue
|
||||
- state.internalValue % step
|
||||
+ stepOffset,
|
||||
minValue, maxValue);
|
||||
minValue,
|
||||
maxValue);
|
||||
state.origin = e.screenY;
|
||||
}
|
||||
else if (Math.abs(offset) > 4) {
|
||||
|
||||
@@ -1,47 +1,41 @@
|
||||
import { clamp, keyOfMatchingRange, toFixed } from 'common/math';
|
||||
import { classes, pureComponentHooks } from 'common/react';
|
||||
import { clamp, toFixed } from 'common/math';
|
||||
import { computeBoxClassName, computeBoxProps } from './Box';
|
||||
|
||||
export const ProgressBar = props => {
|
||||
const {
|
||||
className,
|
||||
value,
|
||||
minValue = 0,
|
||||
maxValue = 1,
|
||||
color,
|
||||
ranges = {},
|
||||
content,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
const scaledValue = (value - minValue) / (maxValue - minValue);
|
||||
const hasContent = content !== undefined || children !== undefined;
|
||||
let { color } = props;
|
||||
// Cycle through ranges in key order to determine progressbar color.
|
||||
if (!color) {
|
||||
for (let rangeName of Object.keys(ranges)) {
|
||||
const range = ranges[rangeName];
|
||||
if (range && value >= range[0] && value <= range[1]) {
|
||||
color = rangeName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Default color
|
||||
if (!color) {
|
||||
color = 'default';
|
||||
}
|
||||
const hasContent = children !== undefined;
|
||||
const effectiveColor = color
|
||||
|| keyOfMatchingRange(value, ranges)
|
||||
|| 'default';
|
||||
return (
|
||||
<div
|
||||
className={classes([
|
||||
'ProgressBar',
|
||||
'ProgressBar--color--' + color,
|
||||
])}>
|
||||
'ProgressBar--color--' + effectiveColor,
|
||||
className,
|
||||
computeBoxClassName(rest),
|
||||
])}
|
||||
{...computeBoxProps(rest)}>
|
||||
<div
|
||||
className="ProgressBar__fill"
|
||||
style={{
|
||||
'width': (clamp(scaledValue, 0, 1) * 100) + '%',
|
||||
width: (clamp(scaledValue, 0, 1) * 100) + '%',
|
||||
}} />
|
||||
<div className="ProgressBar__content">
|
||||
{hasContent && content}
|
||||
{hasContent && children}
|
||||
{!hasContent && toFixed(scaledValue * 100) + '%'}
|
||||
{hasContent
|
||||
? children
|
||||
: toFixed(scaledValue * 100) + '%'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import { clamp01, keyOfMatchingRange, scale } from 'common/math';
|
||||
import { classes } from 'common/react';
|
||||
import { computeBoxClassName, computeBoxProps } from './Box';
|
||||
import { DraggableControl } from './DraggableControl';
|
||||
|
||||
export const Slider = props => {
|
||||
const {
|
||||
// Draggable props (passthrough)
|
||||
animated,
|
||||
format,
|
||||
maxValue,
|
||||
minValue,
|
||||
onChange,
|
||||
onDrag,
|
||||
step,
|
||||
stepPixelSize,
|
||||
suppressFlicker,
|
||||
unit,
|
||||
value,
|
||||
// Own props
|
||||
className,
|
||||
fillValue,
|
||||
color,
|
||||
ranges = {},
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
const hasContent = children !== undefined;
|
||||
return (
|
||||
<DraggableControl
|
||||
dragMatrix={[1, 0]}
|
||||
{...{
|
||||
animated,
|
||||
format,
|
||||
maxValue,
|
||||
minValue,
|
||||
onChange,
|
||||
onDrag,
|
||||
step,
|
||||
stepPixelSize,
|
||||
suppressFlicker,
|
||||
unit,
|
||||
value,
|
||||
}}>
|
||||
{control => {
|
||||
const {
|
||||
dragging,
|
||||
editing,
|
||||
value,
|
||||
displayValue,
|
||||
displayElement,
|
||||
inputElement,
|
||||
handleDragStart,
|
||||
} = control;
|
||||
const hasFillValue = fillValue !== undefined
|
||||
&& fillValue !== null;
|
||||
const scaledValue = scale(
|
||||
value,
|
||||
minValue,
|
||||
maxValue);
|
||||
const scaledFillValue = scale(
|
||||
fillValue ?? displayValue,
|
||||
minValue,
|
||||
maxValue);
|
||||
const scaledDisplayValue = scale(
|
||||
displayValue,
|
||||
minValue,
|
||||
maxValue);
|
||||
const effectiveColor = color
|
||||
|| keyOfMatchingRange(fillValue ?? value, ranges)
|
||||
|| 'default';
|
||||
return (
|
||||
<div
|
||||
className={classes([
|
||||
'Slider',
|
||||
'ProgressBar',
|
||||
'ProgressBar--color--' + effectiveColor,
|
||||
className,
|
||||
computeBoxClassName(rest),
|
||||
])}
|
||||
{...computeBoxProps(rest)}
|
||||
onMouseDown={handleDragStart}>
|
||||
<div
|
||||
className={classes([
|
||||
'ProgressBar__fill',
|
||||
hasFillValue && 'ProgressBar__fill--animated',
|
||||
])}
|
||||
style={{
|
||||
width: clamp01(scaledFillValue) * 100 + '%',
|
||||
opacity: 0.4,
|
||||
}} />
|
||||
<div
|
||||
className="ProgressBar__fill"
|
||||
style={{
|
||||
width: clamp01(Math.min(scaledFillValue, scaledDisplayValue))
|
||||
* 100 + '%',
|
||||
}} />
|
||||
<div
|
||||
className="Slider__cursorOffset"
|
||||
style={{
|
||||
width: clamp01(scaledDisplayValue) * 100 + '%',
|
||||
}}>
|
||||
<div className="Slider__cursor" />
|
||||
<div className="Slider__pointer" />
|
||||
{dragging && (
|
||||
<div className="Slider__popupValue">
|
||||
{displayElement}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="ProgressBar__content">
|
||||
{hasContent
|
||||
? children
|
||||
: displayElement}
|
||||
</div>
|
||||
{inputElement}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</DraggableControl>
|
||||
);
|
||||
};
|
||||
@@ -1,55 +1,66 @@
|
||||
import { classes, pureComponentHooks } from 'common/react';
|
||||
import { Box } from './Box';
|
||||
import { Box, computeBoxClassName, computeBoxProps } from './Box';
|
||||
|
||||
export const Table = props => {
|
||||
const { collapsing, className, content, children, ...rest } = props;
|
||||
const {
|
||||
className,
|
||||
collapsing,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<Box
|
||||
as="table"
|
||||
<table
|
||||
className={classes([
|
||||
'Table',
|
||||
collapsing && 'Table--collapsing',
|
||||
className,
|
||||
computeBoxClassName(rest),
|
||||
])}
|
||||
{...rest}>
|
||||
{...computeBoxProps(rest)}>
|
||||
<tbody>
|
||||
{content}
|
||||
{children}
|
||||
</tbody>
|
||||
</Box>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
Table.defaultHooks = pureComponentHooks;
|
||||
|
||||
export const TableRow = props => {
|
||||
const { className, header, ...rest } = props;
|
||||
const {
|
||||
className,
|
||||
header,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<Box
|
||||
as="tr"
|
||||
<tr
|
||||
className={classes([
|
||||
'Table__row',
|
||||
header && 'Table__row--header',
|
||||
className,
|
||||
computeBoxClassName(props),
|
||||
])}
|
||||
{...rest} />
|
||||
{...computeBoxProps(rest)} />
|
||||
);
|
||||
};
|
||||
|
||||
TableRow.defaultHooks = pureComponentHooks;
|
||||
|
||||
export const TableCell = props => {
|
||||
const { className, collapsing, header, ...rest } = props;
|
||||
const {
|
||||
className,
|
||||
collapsing,
|
||||
header,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<Box
|
||||
as="td"
|
||||
<td
|
||||
className={classes([
|
||||
'Table__cell',
|
||||
collapsing && 'Table__cell--collapsing',
|
||||
header && 'Table__cell--header',
|
||||
className,
|
||||
computeBoxClassName(props),
|
||||
])}
|
||||
{...rest} />
|
||||
{...computeBoxProps(rest)} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -13,11 +13,13 @@ export { Flex } from './Flex';
|
||||
export { Grid } from './Grid';
|
||||
export { Icon } from './Icon';
|
||||
export { Input } from './Input';
|
||||
export { Knob } from './Knob';
|
||||
export { LabeledList } from './LabeledList';
|
||||
export { NoticeBox } from './NoticeBox';
|
||||
export { NumberInput } from './NumberInput';
|
||||
export { ProgressBar } from './ProgressBar';
|
||||
export { Section } from './Section';
|
||||
export { Slider } from './Slider';
|
||||
export { Table } from './Table';
|
||||
export { Tabs } from './Tabs';
|
||||
export { TitleBar } from './TitleBar';
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { clamp, toFixed } from 'common/math';
|
||||
|
||||
const SI_SYMBOLS = [
|
||||
'f', // femto
|
||||
'p', // pico
|
||||
'n', // nano
|
||||
'μ', // micro
|
||||
'm', // milli
|
||||
// NOTE: This is a space for a reason. When we right align si numbers,
|
||||
// in monospace mode, we want to units and numbers stay in their respective
|
||||
// columns. If rendering in HTML mode, this space will collapse into
|
||||
// a single space anyway.
|
||||
' ',
|
||||
'k', // kilo
|
||||
'M', // mega
|
||||
'G', // giga
|
||||
'T', // tera
|
||||
'P', // peta
|
||||
'E', // exa
|
||||
'Z', // zetta
|
||||
'Y', // yotta
|
||||
];
|
||||
|
||||
const SI_BASE_INDEX = SI_SYMBOLS.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.
|
||||
*/
|
||||
const formatSiUnit = (value, minBase1000 = -SI_BASE_INDEX, unit = '') => {
|
||||
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_INDEX + base1000,
|
||||
0,
|
||||
SI_SYMBOLS.length);
|
||||
const symbol = SI_SYMBOLS[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();
|
||||
};
|
||||
|
||||
export const formatPower = (value, minBase1000 = 0) => {
|
||||
return formatSiUnit(value, minBase1000, 'W');
|
||||
};
|
||||
@@ -251,6 +251,13 @@ export const hotKeyReducer = (state, action) => {
|
||||
showKitchenSink: !state.showKitchenSink,
|
||||
};
|
||||
}
|
||||
// Toggle layout debugger
|
||||
if (ctrlKey && altKey && keyCode === KEY_MINUS) {
|
||||
return {
|
||||
...state,
|
||||
debugLayout: !state.debugLayout,
|
||||
};
|
||||
}
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
|
||||
@@ -16,9 +16,9 @@ export const Achievement = props => {
|
||||
<td style={{ 'vertical-align': 'top' }}>
|
||||
<h1>{name}</h1>
|
||||
{desc}
|
||||
<Box
|
||||
color={value ? 'good' : 'bad'}
|
||||
content={value ? 'Unlocked' : 'Locked'} />
|
||||
<Box color={value ? 'good' : 'bad'}>
|
||||
{value ? 'Unlocked' : 'Locked'}
|
||||
</Box>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
@@ -39,9 +39,9 @@ export const Score = props => {
|
||||
<td style={{ 'vertical-align': 'top' }}>
|
||||
<h1>{name}</h1>
|
||||
{desc}
|
||||
<Box
|
||||
color={value > 0 ? 'good' : 'bad'}
|
||||
content={value > 0 ? `Earned ${value} times` : 'Locked'} />
|
||||
<Box color={value > 0 ? 'good' : 'bad'}>
|
||||
{value > 0 ? `Earned ${value} times` : 'Locked'}
|
||||
</Box>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
@@ -105,7 +105,7 @@ export const BlackmarketUplink = props => {
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing textAlign="right">
|
||||
<Button
|
||||
content={'Buy'}
|
||||
content="Buy"
|
||||
disabled={!item.amount || item.cost > data.money}
|
||||
onClick={() => act('select', {
|
||||
item: item.id,
|
||||
|
||||
@@ -43,11 +43,14 @@ export const BorgPanel = props => {
|
||||
<LabeledList.Item label="Charge">
|
||||
{!cell.missing ? (
|
||||
<ProgressBar
|
||||
value={cellPercent}
|
||||
content={cell.charge + ' / ' + cell.maxcharge} />
|
||||
value={cellPercent}>
|
||||
{cell.charge + ' / ' + cell.maxcharge}
|
||||
</ProgressBar>
|
||||
) : (
|
||||
<span className="color-bad">No cell installed</span>
|
||||
) }
|
||||
<span className="color-bad">
|
||||
No cell installed
|
||||
</span>
|
||||
)}
|
||||
<br />
|
||||
<Button
|
||||
icon="pencil-alt"
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, LabeledList, NoticeBox, ProgressBar, Section } from '../components';
|
||||
import { AnimatedNumber, Box, Button, Flex, Knob, LabeledList, NoticeBox, Section } from '../components';
|
||||
|
||||
export const Canister = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
const {
|
||||
portConnected,
|
||||
tankPressure,
|
||||
releasePressure,
|
||||
defaultReleasePressure,
|
||||
minReleasePressure,
|
||||
maxReleasePressure,
|
||||
valveOpen,
|
||||
isPrototype,
|
||||
hasHoldingTank,
|
||||
holdingTank,
|
||||
restricted,
|
||||
} = data;
|
||||
return (
|
||||
<Fragment>
|
||||
<NoticeBox>
|
||||
The regulator {data.hasHoldingTank ? 'is' : 'is not'} connected
|
||||
The regulator {hasHoldingTank ? 'is' : 'is not'} connected
|
||||
to a tank.
|
||||
</NoticeBox>
|
||||
<Section
|
||||
@@ -18,99 +31,91 @@ export const Canister = props => {
|
||||
content="Relabel"
|
||||
onClick={() => act('relabel')} />
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Pressure">
|
||||
<AnimatedNumber value={data.tankPressure} /> kPa
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Port"
|
||||
color={data.portConnected ? 'good' : 'average'}
|
||||
content={data.portConnected ? 'Connected' : 'Not Connected'} />
|
||||
{!!data.isPrototype && (
|
||||
<LabeledList.Item label="Access">
|
||||
<Button
|
||||
icon={data.restricted ? 'lock' : 'unlock'}
|
||||
color="caution"
|
||||
content={data.restricted
|
||||
? 'Restricted to Engineering'
|
||||
: 'Public'}
|
||||
onClick={() => act('restricted')} />
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
|
||||
<Section title="Valve">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Release Pressure">
|
||||
<ProgressBar
|
||||
value={data.releasePressure
|
||||
/ (data.maxReleasePressure - data.minReleasePressure)}>
|
||||
<AnimatedNumber value={data.releasePressure} /> kPa
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Pressure Regulator">
|
||||
<Button
|
||||
icon="undo"
|
||||
disabled={data.releasePressure === data.defaultReleasePressure}
|
||||
content="Reset"
|
||||
onClick={() => act('pressure', {
|
||||
pressure: 'reset',
|
||||
<Flex mx={-1}>
|
||||
<Flex.Item
|
||||
mx={1}
|
||||
align="center"
|
||||
textAlign="center">
|
||||
<Knob
|
||||
size={2}
|
||||
color={!!valveOpen && 'yellow'}
|
||||
value={releasePressure}
|
||||
unit="kPa"
|
||||
minValue={minReleasePressure}
|
||||
maxValue={maxReleasePressure}
|
||||
step={5}
|
||||
stepPixelSize={2}
|
||||
onDrag={(e, value) => act('pressure', {
|
||||
pressure: value,
|
||||
})} />
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
mx={1}
|
||||
my={-0.5}
|
||||
align="center"
|
||||
textAlign="center">
|
||||
<Box my={0.5} color="label">
|
||||
Valve
|
||||
</Box>
|
||||
<Box my={0.5} width="60px">
|
||||
<AnimatedNumber value={releasePressure} /> kPa
|
||||
</Box>
|
||||
<Button
|
||||
icon="minus"
|
||||
disabled={data.releasePressure <= data.minReleasePressure}
|
||||
content="Min"
|
||||
onClick={() => act('pressure', {
|
||||
pressure: 'min',
|
||||
})} />
|
||||
<Button
|
||||
icon="pencil-alt"
|
||||
content="Set"
|
||||
onClick={() => act('pressure', {
|
||||
pressure: 'input',
|
||||
})} />
|
||||
<Button
|
||||
icon="plus"
|
||||
disabled={data.releasePressure >= data.maxReleasePressure}
|
||||
content="Max"
|
||||
onClick={() => act('pressure', {
|
||||
pressure: 'max',
|
||||
})} />
|
||||
</LabeledList.Item>
|
||||
|
||||
<LabeledList.Item label="Valve">
|
||||
<Button
|
||||
icon={data.valveOpen ? 'unlock' : 'lock'}
|
||||
color={data.valveOpen
|
||||
? (data.hasHoldingTank ? 'caution' : 'danger')
|
||||
my={0.5}
|
||||
color={valveOpen
|
||||
? (hasHoldingTank ? 'caution' : 'danger')
|
||||
: null}
|
||||
content={data.valveOpen ? 'Open' : 'Closed'}
|
||||
content={valveOpen ? 'Open' : 'Closed'}
|
||||
onClick={() => act('valve')} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
mx={1}
|
||||
grow={1}
|
||||
basis={0}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Pressure">
|
||||
<AnimatedNumber value={tankPressure} /> kPa
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Port"
|
||||
color={portConnected ? 'good' : 'average'}>
|
||||
{portConnected ? 'Connected' : 'Not Connected'}
|
||||
</LabeledList.Item>
|
||||
{!!isPrototype && (
|
||||
<LabeledList.Item label="Access">
|
||||
<Button
|
||||
icon={restricted ? 'lock' : 'unlock'}
|
||||
color="caution"
|
||||
content={restricted
|
||||
? 'Engineering'
|
||||
: 'Public'}
|
||||
onClick={() => act('restricted')} />
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
</LabeledList>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Holding Tank"
|
||||
buttons={!!data.hasHoldingTank && (
|
||||
buttons={!!hasHoldingTank && (
|
||||
<Button
|
||||
icon="eject"
|
||||
color={data.valveOpen && 'danger'}
|
||||
color={valveOpen && 'danger'}
|
||||
content="Eject"
|
||||
onClick={() => act('eject')} />
|
||||
)}>
|
||||
{!!data.hasHoldingTank && (
|
||||
{!!hasHoldingTank && (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Label">
|
||||
{data.holdingTank.name}
|
||||
{holdingTank.name}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Pressure">
|
||||
<AnimatedNumber value={data.holdingTank.tankPressure} /> kPa
|
||||
<AnimatedNumber value={holdingTank.tankPressure} /> kPa
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
)}
|
||||
{!data.hasHoldingTank && (
|
||||
{!hasHoldingTank && (
|
||||
<Box color="average">
|
||||
No Holding Tank
|
||||
</Box>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button } from '../components';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { pureComponentHooks } from 'common/react';
|
||||
|
||||
|
||||
class PaintCanvas extends Component {
|
||||
constructor(props) {
|
||||
@@ -45,8 +42,7 @@ class PaintCanvas extends Component {
|
||||
|
||||
clickwrapper(event) {
|
||||
const x_size = this.props.value.length;
|
||||
if (!x_size)
|
||||
{
|
||||
if (!x_size) {
|
||||
return;
|
||||
}
|
||||
const y_size = this.props.value[0].length;
|
||||
@@ -78,6 +74,7 @@ class PaintCanvas extends Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const Canvas = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
return (
|
||||
@@ -86,11 +83,13 @@ export const Canvas = props => {
|
||||
value={data.grid}
|
||||
onCanvasClick={(x, y) => act("paint", { x, y })} />
|
||||
<Box>
|
||||
{!data.finalized
|
||||
&& <Button.Confirm
|
||||
onClick={() => act("finalize")}
|
||||
content="Finalize" />}
|
||||
{!data.finalized && (
|
||||
<Button.Confirm
|
||||
onClick={() => act("finalize")}
|
||||
content="Finalize" />
|
||||
)}
|
||||
{data.name}
|
||||
</Box>
|
||||
</Box>);
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -60,7 +60,9 @@ export const Cargo = props => {
|
||||
disabled={!(data.away && data.docked)}
|
||||
onClick={() => act(ref, 'loan')} />
|
||||
) : (
|
||||
<Box color="bad">Loaned to Centcom</Box>
|
||||
<Box color="bad">
|
||||
Loaned to Centcom
|
||||
</Box>
|
||||
)}
|
||||
</LabeledList.Item>
|
||||
) : ''}
|
||||
@@ -158,7 +160,8 @@ const Catalog = props => {
|
||||
)}
|
||||
</td>
|
||||
<td className="LabeledList__cell LabeledList__buttons">
|
||||
<Button fluid
|
||||
<Button
|
||||
fluid
|
||||
content={(data.self_paid
|
||||
? Math.round(pack.cost * 1.1)
|
||||
: pack.cost) + ' credits'}
|
||||
|
||||
@@ -37,8 +37,9 @@ export const ChemDispenser = props => {
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Energy">
|
||||
<ProgressBar
|
||||
value={data.energy / data.maxEnergy}
|
||||
content={toFixed(data.energy) + ' units'} />
|
||||
value={data.energy / data.maxEnergy}>
|
||||
{toFixed(data.energy) + ' units'}
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
@@ -79,7 +80,8 @@ export const ChemDispenser = props => {
|
||||
)}>
|
||||
<Box mr={-1}>
|
||||
{recipes.map(recipe => (
|
||||
<Button key={recipe.name}
|
||||
<Button
|
||||
key={recipe.name}
|
||||
icon="tint"
|
||||
width="129.5px"
|
||||
lineHeight="21px"
|
||||
@@ -99,7 +101,8 @@ export const ChemDispenser = props => {
|
||||
title="Dispense"
|
||||
buttons={(
|
||||
beakerTransferAmounts.map(amount => (
|
||||
<Button key={amount}
|
||||
<Button
|
||||
key={amount}
|
||||
icon="plus"
|
||||
selected={amount === data.amount}
|
||||
content={amount}
|
||||
@@ -110,7 +113,8 @@ export const ChemDispenser = props => {
|
||||
)}>
|
||||
<Box mr={-1}>
|
||||
{data.chemicals.map(chemical => (
|
||||
<Button key={chemical.id}
|
||||
<Button
|
||||
key={chemical.id}
|
||||
icon="tint"
|
||||
width="129.5px"
|
||||
lineHeight="21px"
|
||||
@@ -125,7 +129,8 @@ export const ChemDispenser = props => {
|
||||
title="Beaker"
|
||||
buttons={(
|
||||
beakerTransferAmounts.map(amount => (
|
||||
<Button key={amount}
|
||||
<Button
|
||||
key={amount}
|
||||
icon="minus"
|
||||
disabled={recording}
|
||||
content={amount}
|
||||
|
||||
@@ -191,12 +191,13 @@ const PackagingControlsItem = props => {
|
||||
minValue={1}
|
||||
maxValue={10}
|
||||
onChange={onChangeAmount} />
|
||||
<Button ml={1}
|
||||
<Button
|
||||
ml={1}
|
||||
content="Create"
|
||||
onClick={onCreate} />
|
||||
<Box inline ml={1}
|
||||
color="label"
|
||||
content={sideNote} />
|
||||
<Box inline ml={1} color="label">
|
||||
{sideNote}
|
||||
</Box>
|
||||
</LabeledList.Item>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,15 +27,16 @@ export const Cryo = props => {
|
||||
<Fragment>
|
||||
<Section title="Occupant">
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Occupant"
|
||||
content={data.occupant.name ? data.occupant.name : "No Occupant"} />
|
||||
<LabeledList.Item label="Occupant">
|
||||
{data.occupant.name || 'No Occupant'}
|
||||
</LabeledList.Item>
|
||||
{!!data.hasOccupant && (
|
||||
<Fragment>
|
||||
<LabeledList.Item
|
||||
label="State"
|
||||
content={data.occupant.stat}
|
||||
color={data.occupant.statstate} />
|
||||
color={data.occupant.statstate}>
|
||||
{data.occupant.stat}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Temperature"
|
||||
color={data.occupant.temperaturestatus}>
|
||||
@@ -44,7 +45,7 @@ export const Cryo = props => {
|
||||
<LabeledList.Item label="Health">
|
||||
<ProgressBar
|
||||
value={data.occupant.health / data.occupant.maxHealth}
|
||||
color={(data.occupant.health > 0) ? "good" : "average"}>
|
||||
color={data.occupant.health > 0 ? 'good' : 'average'}>
|
||||
<AnimatedNumber value={data.occupant.health} />
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
@@ -64,17 +65,15 @@ export const Cryo = props => {
|
||||
</Section>
|
||||
<Section title="Cell">
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
label="Power"
|
||||
content={(
|
||||
<Button
|
||||
icon={data.isOperating ? "power-off" : "times"}
|
||||
disabled={data.isOpen}
|
||||
onClick={() => act('power')}
|
||||
color={data.isOperating && ("green")}>
|
||||
{data.isOperating ? "On" : "Off"}
|
||||
</Button>
|
||||
)} />
|
||||
<LabeledList.Item label="Power">
|
||||
<Button
|
||||
icon={data.isOperating ? "power-off" : "times"}
|
||||
disabled={data.isOpen}
|
||||
onClick={() => act('power')}
|
||||
color={data.isOperating && 'green'}>
|
||||
{data.isOperating ? "On" : "Off"}
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Temperature">
|
||||
<AnimatedNumber value={data.cellTemperature} /> K
|
||||
</LabeledList.Item>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, LabeledList, ProgressBar, Section, Tabs } from '../components';
|
||||
import { Button, Section } from '../components';
|
||||
|
||||
export const DecalPainter = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
@@ -10,17 +10,15 @@ export const DecalPainter = props => {
|
||||
return (
|
||||
<Fragment>
|
||||
<Section title="Decal Type">
|
||||
{decal_list.map(decal => {
|
||||
return (
|
||||
<Button
|
||||
key={decal.decal}
|
||||
content={decal.name}
|
||||
selected={decal.decal === data.decal_style}
|
||||
onClick={() => act('select decal', {
|
||||
decals: decal.decal,
|
||||
})} />
|
||||
);
|
||||
})}
|
||||
{decal_list.map(decal => (
|
||||
<Button
|
||||
key={decal.decal}
|
||||
content={decal.name}
|
||||
selected={decal.decal === data.decal_style}
|
||||
onClick={() => act('select decal', {
|
||||
decals: decal.decal,
|
||||
})} />
|
||||
))}
|
||||
</Section>
|
||||
<Section title="Decal Color">
|
||||
{color_list.map(color => {
|
||||
@@ -28,35 +26,35 @@ export const DecalPainter = props => {
|
||||
<Button
|
||||
key={color.colors}
|
||||
content={color.colors === "red"
|
||||
? "Red"
|
||||
: color.colors === "white"
|
||||
? "White"
|
||||
? "Red"
|
||||
: color.colors === "white"
|
||||
? "White"
|
||||
: "Yellow"}
|
||||
selected={color.colors === data.decal_color}
|
||||
onClick={() => act('select color', {
|
||||
colors: color.colors,
|
||||
colors: color.colors,
|
||||
})} />
|
||||
);
|
||||
})}
|
||||
})}
|
||||
</Section>
|
||||
<Section title="Decal Direction">
|
||||
{dir_list.map(dir => {
|
||||
{dir_list.map(dir => {
|
||||
return (
|
||||
<Button
|
||||
key={dir.dirs}
|
||||
content={dir.dirs === 1
|
||||
? "North"
|
||||
: dir.dirs === 2
|
||||
? "South"
|
||||
: dir.dirs === 4
|
||||
? "East"
|
||||
content={dir.dirs === 1
|
||||
? "North"
|
||||
: dir.dirs === 2
|
||||
? "South"
|
||||
: dir.dirs === 4
|
||||
? "East"
|
||||
: "West"}
|
||||
selected={dir.dirs === data.decal_direction}
|
||||
onClick={() => act('selected direction', {
|
||||
dirs: dir.dirs,
|
||||
dirs: dir.dirs,
|
||||
})} />
|
||||
);
|
||||
})}
|
||||
})}
|
||||
</Section>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -22,18 +22,21 @@ export const DnaVault = props => {
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Human DNA">
|
||||
<ProgressBar
|
||||
value={dna / dna_max}
|
||||
content={dna + ' / ' + dna_max + ' Samples'} />
|
||||
value={dna / dna_max}>
|
||||
{dna + ' / ' + dna_max + ' Samples'}
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Plant DNA">
|
||||
<ProgressBar
|
||||
value={plants / plants_max}
|
||||
content={plants + ' / ' + plants_max + ' Samples'} />
|
||||
value={plants / plants_max}>
|
||||
{plants + ' / ' + plants_max + ' Samples'}
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Animal DNA">
|
||||
<ProgressBar
|
||||
value={animals / animals}
|
||||
content={animals + ' / ' + animals_max + ' Samples'} />
|
||||
value={animals / animals}>
|
||||
{animals + ' / ' + animals_max + ' Samples'}
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
|
||||
@@ -22,8 +22,7 @@ export const Gateway = props => {
|
||||
);
|
||||
}
|
||||
|
||||
if (current_target)
|
||||
{
|
||||
if (current_target) {
|
||||
return (
|
||||
<Section title={current_target.name} textAlign="center">
|
||||
<Icon name="rainbow" size={4} color="green" />
|
||||
@@ -37,12 +36,15 @@ export const Gateway = props => {
|
||||
}
|
||||
|
||||
if (!destinations.length) {
|
||||
return (<Section>No gateway nodes detected.</Section>);
|
||||
return (
|
||||
<Section>
|
||||
No gateway nodes detected.
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
const GatewayDest = dest => {
|
||||
if (dest.availible)
|
||||
{
|
||||
const renderGatewayDest = dest => {
|
||||
if (dest.availible) {
|
||||
return (
|
||||
<Section
|
||||
key={dest.ref}
|
||||
@@ -50,29 +52,40 @@ export const Gateway = props => {
|
||||
textAlign="center">
|
||||
<Button
|
||||
fluid
|
||||
onClick={() => act("activate", { "destination": dest.ref })}>
|
||||
onClick={() => act('activate', {
|
||||
destination: dest.ref,
|
||||
})}>
|
||||
Activate
|
||||
</Button>
|
||||
</Section>);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (
|
||||
<Section
|
||||
textAlign="center"
|
||||
key={dest.ref}
|
||||
title={dest.name}>
|
||||
<Box m={1} textColor="bad">{dest.reason}</Box>
|
||||
{!!dest.timeout && (<ProgressBar
|
||||
value={dest.timeout}
|
||||
content="Calibrating..." />)}
|
||||
</Section>);
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Section
|
||||
textAlign="center"
|
||||
key={dest.ref}
|
||||
title={dest.name}>
|
||||
<Box m={1} textColor="bad">
|
||||
{dest.reason}
|
||||
</Box>
|
||||
{!!dest.timeout && (
|
||||
<ProgressBar
|
||||
value={dest.timeout}>
|
||||
Calibrating...
|
||||
</ProgressBar>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{!gateway_status && (<NoticeBox>Gateway Unpowered</NoticeBox>)}
|
||||
{destinations.map(GatewayDest)}
|
||||
</Fragment>);
|
||||
{!gateway_status && (
|
||||
<NoticeBox>
|
||||
Gateway Unpowered
|
||||
</NoticeBox>
|
||||
)}
|
||||
{destinations.map(renderGatewayDest)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { BlockQuote, Box, Button, ByondUi, Collapsible, Input, LabeledList, NumberInput, ProgressBar, Section, Tabs, Tooltip } from '../components';
|
||||
import { BlockQuote, Box, Button, ByondUi, Collapsible, Input, LabeledList, NumberInput, ProgressBar, Section, Tabs, Tooltip, Slider, Icon, Knob } from '../components';
|
||||
import { DraggableControl } from '../components/DraggableControl';
|
||||
|
||||
const COLORS_ARBITRARY = [
|
||||
'red',
|
||||
@@ -47,7 +48,7 @@ const PAGES = [
|
||||
component: () => KitchenSinkTooltip,
|
||||
},
|
||||
{
|
||||
title: 'Input',
|
||||
title: 'Input / Slider',
|
||||
component: () => KitchenSinkInput,
|
||||
},
|
||||
{
|
||||
@@ -123,8 +124,9 @@ const KitchenSinkButton = props => {
|
||||
<Box inline
|
||||
mx="7px"
|
||||
key={color}
|
||||
color={color}
|
||||
content={color} />
|
||||
color={color}>
|
||||
{color}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -134,14 +136,30 @@ const KitchenSinkButton = props => {
|
||||
const KitchenSinkBox = props => {
|
||||
return (
|
||||
<Box>
|
||||
<Box bold content="bold" />
|
||||
<Box italic content="italic" />
|
||||
<Box opacity={0.5} content="opacity 0.5" />
|
||||
<Box opacity={0.25} content="opacity 0.25" />
|
||||
<Box m={2} content="m: 2" />
|
||||
<Box textAlign="left" content="left" />
|
||||
<Box textAlign="center" content="center" />
|
||||
<Box textAlign="right" content="right" />
|
||||
<Box bold>
|
||||
bold
|
||||
</Box>
|
||||
<Box italic>
|
||||
italic
|
||||
</Box>
|
||||
<Box opacity={0.5}>
|
||||
opacity 0.5
|
||||
</Box>
|
||||
<Box opacity={0.25}>
|
||||
opacity 0.25
|
||||
</Box>
|
||||
<Box m={2}>
|
||||
m: 2
|
||||
</Box>
|
||||
<Box textAlign="left">
|
||||
left
|
||||
</Box>
|
||||
<Box textAlign="center">
|
||||
center
|
||||
</Box>
|
||||
<Box textAlign="right">
|
||||
right
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -166,8 +184,9 @@ class KitchenSinkProgressBar extends Component {
|
||||
}}
|
||||
minValue={-1}
|
||||
maxValue={1}
|
||||
value={progress}
|
||||
content={`value: ${Number(progress).toFixed(1)}`} />
|
||||
value={progress}>
|
||||
Value: {Number(progress).toFixed(1)}
|
||||
</ProgressBar>
|
||||
<Box mt={1}>
|
||||
<Button
|
||||
content="-0.1"
|
||||
@@ -199,7 +218,8 @@ class KitchenSinkTabs extends Component {
|
||||
return (
|
||||
<Box>
|
||||
{'Vertical: '}
|
||||
<Button inline
|
||||
<Button
|
||||
inline
|
||||
content={String(vertical)}
|
||||
onClick={() => this.setState(prevState => ({
|
||||
vertical: !prevState.vertical,
|
||||
@@ -273,7 +293,21 @@ class KitchenSinkInput extends Component {
|
||||
return (
|
||||
<Box>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="NumberInput">
|
||||
<LabeledList.Item label="Input (onChange)">
|
||||
<Input
|
||||
value={text}
|
||||
onChange={(e, value) => this.setState({
|
||||
text: value,
|
||||
})} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Input (onInput)">
|
||||
<Input
|
||||
value={text}
|
||||
onInput={(e, value) => this.setState({
|
||||
text: value,
|
||||
})} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="NumberInput (onChange)">
|
||||
<NumberInput
|
||||
animated
|
||||
width={10}
|
||||
@@ -285,6 +319,8 @@ class KitchenSinkInput extends Component {
|
||||
onChange={(e, value) => this.setState({
|
||||
number: value,
|
||||
})} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="NumberInput (onDrag)">
|
||||
<NumberInput
|
||||
animated
|
||||
width={10}
|
||||
@@ -297,17 +333,67 @@ class KitchenSinkInput extends Component {
|
||||
number: value,
|
||||
})} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Input">
|
||||
<Input
|
||||
value={text}
|
||||
onChange={(e, value) => this.setState({
|
||||
text: value,
|
||||
<LabeledList.Item label="Slider (onDrag)">
|
||||
<Slider
|
||||
step={1}
|
||||
stepPixelSize={5}
|
||||
value={number}
|
||||
minValue={-100}
|
||||
maxValue={100}
|
||||
onDrag={(e, value) => this.setState({
|
||||
number: value,
|
||||
})} />
|
||||
<Input
|
||||
value={text}
|
||||
onInput={(e, value) => this.setState({
|
||||
text: value,
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Knob (onDrag)">
|
||||
<Knob
|
||||
inline
|
||||
size={1}
|
||||
step={1}
|
||||
stepPixelSize={2}
|
||||
value={number}
|
||||
minValue={-100}
|
||||
maxValue={100}
|
||||
onDrag={(e, value) => this.setState({
|
||||
number: value,
|
||||
})} />
|
||||
<Knob
|
||||
ml={1}
|
||||
inline
|
||||
bipolar
|
||||
size={1}
|
||||
step={1}
|
||||
stepPixelSize={2}
|
||||
value={number}
|
||||
minValue={-100}
|
||||
maxValue={100}
|
||||
onDrag={(e, value) => this.setState({
|
||||
number: value,
|
||||
})} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Rotating Icon">
|
||||
<Box inline position="relative">
|
||||
<DraggableControl
|
||||
value={number}
|
||||
minValue={-100}
|
||||
maxValue={100}
|
||||
dragMatrix={[0, -1]}
|
||||
step={1}
|
||||
stepPixelSize={5}
|
||||
onDrag={(e, value) => this.setState({
|
||||
number: value,
|
||||
})}>
|
||||
{control => (
|
||||
<Box onMouseDown={control.handleDragStart}>
|
||||
<Icon
|
||||
size={4}
|
||||
color="yellow"
|
||||
name="times"
|
||||
rotation={control.displayValue * 4} />
|
||||
{control.inputElement}
|
||||
</Box>
|
||||
)}
|
||||
</DraggableControl>
|
||||
</Box>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Box>
|
||||
@@ -338,7 +424,7 @@ const BoxOfSampleText = props => {
|
||||
<Box mt={1} bold>
|
||||
The wide electrification of the southern
|
||||
provinces will give a powerful impetus to the
|
||||
growth of soviet agriculture.
|
||||
growth of agriculture.
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -57,19 +57,17 @@ export const OperatingComputer = props => {
|
||||
value={patient.health}
|
||||
minValue={patient.minHealth}
|
||||
maxValue={patient.maxHealth}
|
||||
color={patient.health >= 0 ? 'good' : 'average'}
|
||||
content={(
|
||||
<AnimatedNumber value={patient.health} />
|
||||
)} />
|
||||
color={patient.health >= 0 ? 'good' : 'average'}>
|
||||
<AnimatedNumber value={patient.health} />
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
{damageTypes.map(type => (
|
||||
<LabeledList.Item key={type.type} label={type.label}>
|
||||
<ProgressBar
|
||||
value={patient[type.type] / patient.maxHealth}
|
||||
color="bad"
|
||||
content={(
|
||||
<AnimatedNumber value={patient[type.type]} />
|
||||
)} />
|
||||
color="bad">
|
||||
<AnimatedNumber value={patient[type.type]} />
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</LabeledList>
|
||||
|
||||
@@ -57,16 +57,18 @@ export class PowerMonitor extends Component {
|
||||
value={supply}
|
||||
minValue={0}
|
||||
maxValue={maxValue}
|
||||
color="teal"
|
||||
content={toFixed(supply / 1000) + ' kW'} />
|
||||
color="teal">
|
||||
{toFixed(supply / 1000) + ' kW'}
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Draw">
|
||||
<ProgressBar
|
||||
value={demand}
|
||||
minValue={0}
|
||||
maxValue={maxValue}
|
||||
color="pink"
|
||||
content={toFixed(demand / 1000) + ' kW'} />
|
||||
color="pink">
|
||||
{toFixed(demand / 1000) + ' kW'}
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
|
||||
@@ -93,10 +93,12 @@ export const RapidPipeDispenser = props => {
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item
|
||||
label="Color">
|
||||
<Box inline
|
||||
<Box
|
||||
inline
|
||||
width="64px"
|
||||
color={PAINT_COLORS[selected_color]}
|
||||
content={selected_color} />
|
||||
color={PAINT_COLORS[selected_color]}>
|
||||
{selected_color}
|
||||
</Box>
|
||||
{Object.keys(PAINT_COLORS)
|
||||
.map(colorName => (
|
||||
<ColorBox
|
||||
|
||||
@@ -1,36 +1,43 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, NumberInput, LabeledList, ProgressBar, Section } from '../components';
|
||||
import { Box, Button, Flex, LabeledList, ProgressBar, Section, Slider } from '../components';
|
||||
import { formatPower } from '../format';
|
||||
|
||||
// Common power multiplier
|
||||
const POWER_MUL = 1e3;
|
||||
|
||||
export const Smes = props => {
|
||||
const { act, data } = useBackend(props);
|
||||
|
||||
let inputState;
|
||||
if (data.capacityPercent >= 100) {
|
||||
inputState = 'good';
|
||||
}
|
||||
else if (data.inputting) {
|
||||
inputState = 'average';
|
||||
}
|
||||
else {
|
||||
inputState = 'bad';
|
||||
}
|
||||
let outputState;
|
||||
if (data.outputting) {
|
||||
outputState = 'good';
|
||||
}
|
||||
else if (data.charge > 0) {
|
||||
outputState = 'average';
|
||||
}
|
||||
else {
|
||||
outputState = 'bad';
|
||||
}
|
||||
|
||||
const {
|
||||
capacityPercent,
|
||||
capacity,
|
||||
charge,
|
||||
inputAttempt,
|
||||
inputting,
|
||||
inputLevel,
|
||||
inputLevelMax,
|
||||
inputAvailable,
|
||||
outputAttempt,
|
||||
outputting,
|
||||
outputLevel,
|
||||
outputLevelMax,
|
||||
outputUsed,
|
||||
} = data;
|
||||
const inputState = (
|
||||
capacityPercent >= 100 && 'good'
|
||||
|| inputting && 'average'
|
||||
|| 'bad'
|
||||
);
|
||||
const outputState = (
|
||||
outputting && 'good'
|
||||
|| charge > 0 && 'average'
|
||||
|| 'bad'
|
||||
);
|
||||
return (
|
||||
<Fragment>
|
||||
<Section title="Stored Energy">
|
||||
<ProgressBar
|
||||
value={data.capacityPercent * 0.01}
|
||||
value={capacityPercent * 0.01}
|
||||
ranges={{
|
||||
good: [0.5, Infinity],
|
||||
average: [0.15, 0.5],
|
||||
@@ -43,64 +50,65 @@ export const Smes = props => {
|
||||
label="Charge Mode"
|
||||
buttons={
|
||||
<Button
|
||||
icon={data.inputAttempt ? 'sync-alt' : 'times'}
|
||||
selected={data.inputAttempt}
|
||||
icon={inputAttempt ? 'sync-alt' : 'times'}
|
||||
selected={inputAttempt}
|
||||
onClick={() => act('tryinput')}>
|
||||
{data.inputAttempt ? 'Auto' : 'Off'}
|
||||
{inputAttempt ? 'Auto' : 'Off'}
|
||||
</Button>
|
||||
}>
|
||||
<Box color={inputState}>
|
||||
{data.capacityPercent >= 100
|
||||
? 'Fully Charged'
|
||||
: data.inputting
|
||||
? 'Charging'
|
||||
: 'Not Charging'}
|
||||
{capacityPercent >= 100 && 'Fully Charged'
|
||||
|| inputting && 'Charging'
|
||||
|| 'Not Charging'}
|
||||
</Box>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Target Input">
|
||||
<ProgressBar
|
||||
value={data.inputLevel/data.inputLevelMax}
|
||||
content={data.inputLevel_text} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Adjust Input">
|
||||
<Button
|
||||
icon="fast-backward"
|
||||
disabled={data.inputLevel === 0}
|
||||
onClick={() => act('input', {
|
||||
target: 'min',
|
||||
})} />
|
||||
<Button
|
||||
icon="backward"
|
||||
disabled={data.inputLevel === 0}
|
||||
onClick={() => act('input', {
|
||||
adjust: -10000,
|
||||
})} />
|
||||
<NumberInput
|
||||
value={Math.round(data.inputLevel/1000)}
|
||||
unit="kW"
|
||||
width="65px"
|
||||
minValue={0}
|
||||
maxValue={data.inputLevelMax/1000}
|
||||
onChange={(e, value) => {
|
||||
return act('input', {
|
||||
target: value*1000,
|
||||
});
|
||||
}} />
|
||||
<Button
|
||||
icon="forward"
|
||||
disabled={data.inputLevel === data.inputLevelMax}
|
||||
onClick={() => act('input', {
|
||||
adjust: 10000,
|
||||
})} />
|
||||
<Button
|
||||
icon="fast-forward"
|
||||
disabled={data.inputLevel === data.inputLevelMax}
|
||||
onClick={() => act('input', {
|
||||
target: 'max',
|
||||
})} />
|
||||
<Flex inline width="100%">
|
||||
<Flex.Item>
|
||||
<Button
|
||||
icon="fast-backward"
|
||||
disabled={inputLevel === 0}
|
||||
onClick={() => act('input', {
|
||||
target: 'min',
|
||||
})} />
|
||||
<Button
|
||||
icon="backward"
|
||||
disabled={inputLevel === 0}
|
||||
onClick={() => act('input', {
|
||||
adjust: -10000,
|
||||
})} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} mx={1}>
|
||||
<Slider
|
||||
value={inputLevel / POWER_MUL}
|
||||
fillValue={inputAvailable / POWER_MUL}
|
||||
minValue={0}
|
||||
maxValue={inputLevelMax / POWER_MUL}
|
||||
step={5}
|
||||
stepPixelSize={4}
|
||||
format={value => formatPower(value * POWER_MUL, 1)}
|
||||
onDrag={(e, value) => act('input', {
|
||||
target: value * POWER_MUL,
|
||||
})} />
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Button
|
||||
icon="forward"
|
||||
disabled={inputLevel === inputLevelMax}
|
||||
onClick={() => act('input', {
|
||||
adjust: 10000,
|
||||
})} />
|
||||
<Button
|
||||
icon="fast-forward"
|
||||
disabled={inputLevel === inputLevelMax}
|
||||
onClick={() => act('input', {
|
||||
target: 'max',
|
||||
})} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Available">
|
||||
{data.inputAvailable}
|
||||
{formatPower(inputAvailable)}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
@@ -110,64 +118,66 @@ export const Smes = props => {
|
||||
label="Output Mode"
|
||||
buttons={
|
||||
<Button
|
||||
icon={data.outputAttempt ? 'power-off' : 'times'}
|
||||
selected={data.outputAttempt}
|
||||
icon={outputAttempt ? 'power-off' : 'times'}
|
||||
selected={outputAttempt}
|
||||
onClick={() => act('tryoutput')}>
|
||||
{data.outputAttempt ? 'On' : 'Off'}
|
||||
{outputAttempt ? 'On' : 'Off'}
|
||||
</Button>
|
||||
}>
|
||||
<Box color={outputState}>
|
||||
{data.outputting
|
||||
{outputting
|
||||
? 'Sending'
|
||||
: data.charge > 0
|
||||
: charge > 0
|
||||
? 'Not Sending'
|
||||
: 'No Charge'}
|
||||
</Box>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Target Output">
|
||||
<ProgressBar
|
||||
value={data.outputLevel/data.outputLevelMax}
|
||||
content={data.outputLevel_text} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Adjust Output">
|
||||
<Button
|
||||
icon="fast-backward"
|
||||
disabled={data.outputLevel === 0}
|
||||
onClick={() => act('output', {
|
||||
target: 'min',
|
||||
})} />
|
||||
<Button
|
||||
icon="backward"
|
||||
disabled={data.outputLevel === 0}
|
||||
onClick={() => act('output', {
|
||||
adjust: -10000,
|
||||
})} />
|
||||
<NumberInput
|
||||
value={Math.round(data.outputLevel/1000)}
|
||||
unit="kW"
|
||||
width="65px"
|
||||
minValue={0}
|
||||
maxValue={data.outputLevelMax/1000}
|
||||
onChange={(e, value) => {
|
||||
return act('output', {
|
||||
target: value*1000,
|
||||
});
|
||||
}} />
|
||||
<Button
|
||||
icon="forward"
|
||||
disabled={data.outputLevel === data.outputLevelMax}
|
||||
onClick={() => act('output', {
|
||||
adjust: 10000,
|
||||
})} />
|
||||
<Button
|
||||
icon="fast-forward"
|
||||
disabled={data.outputLevel === data.outputLevelMax}
|
||||
onClick={() => act('output', {
|
||||
target: 'max',
|
||||
})} />
|
||||
<Flex inline width="100%">
|
||||
<Flex.Item>
|
||||
<Button
|
||||
icon="fast-backward"
|
||||
disabled={outputLevel === 0}
|
||||
onClick={() => act('output', {
|
||||
target: 'min',
|
||||
})} />
|
||||
<Button
|
||||
icon="backward"
|
||||
disabled={outputLevel === 0}
|
||||
onClick={() => act('output', {
|
||||
adjust: -10000,
|
||||
})} />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1} mx={1}>
|
||||
<Slider
|
||||
value={outputLevel / POWER_MUL}
|
||||
minValue={0}
|
||||
maxValue={outputLevelMax / POWER_MUL}
|
||||
step={5}
|
||||
stepPixelSize={4}
|
||||
format={value => formatPower(value * POWER_MUL, 1)}
|
||||
onDrag={(e, value) => act('output', {
|
||||
target: value * POWER_MUL,
|
||||
})} />
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Button
|
||||
icon="forward"
|
||||
disabled={outputLevel === outputLevelMax}
|
||||
onClick={() => act('output', {
|
||||
adjust: 10000,
|
||||
})} />
|
||||
<Button
|
||||
icon="fast-forward"
|
||||
disabled={outputLevel === outputLevelMax}
|
||||
onClick={() => act('output', {
|
||||
target: 'max',
|
||||
})} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Outputting">
|
||||
{data.outputUsed}
|
||||
{formatPower(outputUsed)}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
|
||||
@@ -35,13 +35,14 @@ export const SmokeMachine = props => {
|
||||
<Box mt={1}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Range">
|
||||
{ [1, 2, 3, 4, 5].map(amount => (
|
||||
<Button key={amount}
|
||||
{[1, 2, 3, 4, 5].map(amount => (
|
||||
<Button
|
||||
key={amount}
|
||||
selected={setting === amount}
|
||||
icon="plus"
|
||||
content={amount * 3}
|
||||
disabled={maxSetting < amount}
|
||||
onClick={() => act('setting', { amount: amount })} />
|
||||
onClick={() => act('setting', { amount })} />
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
|
||||
@@ -51,8 +51,9 @@ export const SolarControl = props => {
|
||||
}}
|
||||
minValue={0}
|
||||
maxValue={1}
|
||||
value={generated_ratio}
|
||||
content={generated + ' W'} />
|
||||
value={generated_ratio}>
|
||||
{generated + ' W'}
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Grid.Column>
|
||||
|
||||
@@ -30,12 +30,13 @@ export const SpaceHeater = props => {
|
||||
{data.hasPowercell && (
|
||||
<ProgressBar
|
||||
value={data.powerLevel / 100}
|
||||
content={data.powerLevel + '%'}
|
||||
ranges={{
|
||||
good: [0.6, Infinity],
|
||||
average: [0.3, 0.6],
|
||||
bad: [-Infinity, 0.3],
|
||||
}} />
|
||||
}}>
|
||||
{data.powerLevel + '%'}
|
||||
</ProgressBar>
|
||||
) || 'None'}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
|
||||
@@ -9,12 +9,13 @@ export const Tank = props => {
|
||||
<LabeledList.Item label="Pressure">
|
||||
<ProgressBar
|
||||
value={data.tankPressure / 1013}
|
||||
content={data.tankPressure + ' kPa'}
|
||||
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">
|
||||
<Button
|
||||
|
||||
@@ -36,7 +36,7 @@ export const Teleporter = props => {
|
||||
buttons={(
|
||||
<Button
|
||||
icon="tools"
|
||||
content={"Set Target"}
|
||||
content="Set Target"
|
||||
onClick={() => act('settarget')} />
|
||||
)}>
|
||||
{target}
|
||||
@@ -45,7 +45,7 @@ export const Teleporter = props => {
|
||||
buttons={(
|
||||
<Button
|
||||
icon="tools"
|
||||
content={"Calibrate Hub"}
|
||||
content="Calibrate Hub"
|
||||
onClick={() => act('calibrate')} />
|
||||
)}>
|
||||
{calibrating && (
|
||||
|
||||
@@ -18,13 +18,14 @@ export const VaultController = props => {
|
||||
<LabeledList.Item label="Charge">
|
||||
<ProgressBar
|
||||
value={data.stored / data.max}
|
||||
content={toFixed(data.stored/1000)
|
||||
+ ' / ' + toFixed(data.max/1000) + ' kW'}
|
||||
ranges={{
|
||||
good: [1, Infinity],
|
||||
average: [0.30, 1],
|
||||
bad: [-Infinity, 0.30],
|
||||
}} />
|
||||
}}>
|
||||
{toFixed(data.stored / 1000) + ' / '
|
||||
+ toFixed(data.max / 1000) + ' kW'}
|
||||
</ProgressBar>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { act } from '../byond';
|
||||
import { Section, Box, Button, Table } from '../components';
|
||||
import { classes } from 'common/react';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Section, Table } from '../components';
|
||||
|
||||
export const Vending = props => {
|
||||
const { state } = props;
|
||||
const { config, data } = state;
|
||||
const { ref } = config;
|
||||
const { act, data } = useBackend(props);
|
||||
let inventory;
|
||||
let custom = false;
|
||||
if (data.vending_machine_input) {
|
||||
@@ -59,7 +57,7 @@ export const Vending = props => {
|
||||
);
|
||||
return (
|
||||
<Table.Row key={product.name}>
|
||||
<Table.Cell>
|
||||
<Table.Cell collapsing>
|
||||
{product.base64 ? (
|
||||
<img
|
||||
src={`data:image/jpeg;base64,${product.img}`}
|
||||
@@ -69,30 +67,36 @@ export const Vending = props => {
|
||||
}} />
|
||||
) : (
|
||||
<span
|
||||
className={classes(['vending32x32', product.path])}
|
||||
className={classes([
|
||||
'vending32x32',
|
||||
product.path,
|
||||
])}
|
||||
style={{
|
||||
'vertical-align': 'middle',
|
||||
'horizontal-align': 'middle',
|
||||
}} />
|
||||
)}
|
||||
<b>{product.name}</b>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Box color={custom
|
||||
? 'good'
|
||||
: data.stock[product.name] <= 0
|
||||
? 'bad'
|
||||
: data.stock[product.name] <= (product.max_amount / 2)
|
||||
? 'average'
|
||||
: 'good'}>
|
||||
<Table.Cell bold>
|
||||
{product.name}
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsing textAlign="center">
|
||||
<Box
|
||||
color={custom
|
||||
? 'good'
|
||||
: data.stock[product.name] <= 0
|
||||
? 'bad'
|
||||
: data.stock[product.name] <= (product.max_amount / 2)
|
||||
? 'average'
|
||||
: 'good'}>
|
||||
{data.stock[product.name]} in stock
|
||||
</Box>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Table.Cell collapsing textAlign="center">
|
||||
{custom && (
|
||||
<Button
|
||||
content={data.access ? 'FREE' : product.price + ' cr'}
|
||||
onClick={() => act(ref, 'dispense', {
|
||||
onClick={() => act('dispense', {
|
||||
'item': product.name,
|
||||
})} />
|
||||
) || (
|
||||
@@ -100,15 +104,14 @@ export const Vending = props => {
|
||||
disabled={(
|
||||
data.stock[product.namename] === 0
|
||||
|| (
|
||||
!free
|
||||
&& (
|
||||
!free && (
|
||||
!data.user
|
||||
|| product.price > data.user.cash
|
||||
)
|
||||
)
|
||||
)}
|
||||
content={free ? 'FREE' : product.price + ' cr'}
|
||||
onClick={() => act(ref, 'vend', {
|
||||
onClick={() => act('vend', {
|
||||
'ref': product.ref,
|
||||
})} />
|
||||
)}
|
||||
|
||||
@@ -36,7 +36,7 @@ export class Layout extends Component {
|
||||
render() {
|
||||
const { props } = this;
|
||||
const { state, dispatch } = props;
|
||||
const { config } = state;
|
||||
const { config, debugLayout } = state;
|
||||
const route = getRoute(state);
|
||||
const { scrollable, resizable, theme } = route || {};
|
||||
let contentElement;
|
||||
@@ -86,7 +86,11 @@ export class Layout extends Component {
|
||||
winset(config.window, 'is-visible', false);
|
||||
runCommand(`uiclose ${config.ref}`);
|
||||
}} />
|
||||
<div className="Layout__rest">
|
||||
<div
|
||||
className={classes([
|
||||
'Layout__rest',
|
||||
debugLayout && 'debug-layout',
|
||||
])}>
|
||||
{contentElement}
|
||||
{showDimmer && (
|
||||
<div className="Layout__dimmer" />
|
||||
|
||||
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,12 @@
|
||||
.debug-layout,
|
||||
.debug-layout *:not(g):not(path) {
|
||||
color: rgba(255, 255, 255, 0.9) !important;
|
||||
background: transparent !important;
|
||||
outline: 1px solid rgba(255, 255, 255, 0.5) !important;
|
||||
box-shadow: none !important;
|
||||
filter: none !important;
|
||||
|
||||
&:hover {
|
||||
outline-color: rgba(255, 255, 255, 0.8) !important;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Flex--inline {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
@for $i from 1 through 2 {
|
||||
.Flex--spacing--#{$i} {
|
||||
margin: 0 -3px * $i;
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
@use '../base.scss';
|
||||
@use '../colors.scss';
|
||||
@use '../functions.scss' as *;
|
||||
|
||||
$bg-map: colors.$bg-map !default;
|
||||
$fg-map: colors.$fg-map !default;
|
||||
$ring-color: #6a96c9 !default;
|
||||
$knob-color: #333333 !default;
|
||||
|
||||
$inner-padding: 0.1em;
|
||||
|
||||
.Knob {
|
||||
position: relative;
|
||||
font-size: 1rem;
|
||||
width: 2.6em;
|
||||
height: 2.6em;
|
||||
margin: 0 auto;
|
||||
margin-bottom: -0.2em;
|
||||
cursor: n-resize;
|
||||
|
||||
// Adjusts a baseline in a way, that makes knob middle-aligned
|
||||
// when it flows with the text.
|
||||
&:after {
|
||||
content: '.';
|
||||
color: transparent;
|
||||
line-height: 2.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.Knob__circle {
|
||||
position: absolute;
|
||||
top: $inner-padding;
|
||||
bottom: $inner-padding;
|
||||
left: $inner-padding;
|
||||
right: $inner-padding;
|
||||
margin: 0.3em;
|
||||
background-color: $knob-color;
|
||||
background-image: linear-gradient(to bottom,
|
||||
rgba(255, 255, 255, 0.15) 0%,
|
||||
rgba(255, 255, 255, 0) 100%);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0.05em 0.5em 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.Knob__cursorBox {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.Knob__cursor {
|
||||
position: relative;
|
||||
top: 0.05em;
|
||||
margin: 0 auto;
|
||||
width: 0.2em;
|
||||
height: 0.8em;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.Knob__popupValue {
|
||||
position: absolute;
|
||||
top: -2rem;
|
||||
right: 50%;
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background-color: #000;
|
||||
transform: translateX(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.Knob__ring {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: $inner-padding;
|
||||
}
|
||||
|
||||
$pi: 3.1416;
|
||||
|
||||
.Knob__ringTrackPivot {
|
||||
transform: rotateZ(135deg);
|
||||
}
|
||||
|
||||
.Knob__ringTrack {
|
||||
// transform-origin: 50% 50%;
|
||||
fill: transparent;
|
||||
stroke: rgba(255, 255, 255, 0.1);
|
||||
stroke-width: 8;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 75 * $pi;
|
||||
}
|
||||
|
||||
.Knob__ringFillPivot {
|
||||
transform: rotateZ(135deg);
|
||||
}
|
||||
|
||||
.Knob--bipolar .Knob__ringFillPivot {
|
||||
transform: rotateZ(270deg);
|
||||
}
|
||||
|
||||
.Knob__ringFill {
|
||||
fill: transparent;
|
||||
stroke: $ring-color;
|
||||
stroke-width: 8;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 100 * $pi;
|
||||
transition: stroke 50ms;
|
||||
}
|
||||
|
||||
@each $color-name, $color-value in $fg-map {
|
||||
.Knob--color--#{$color-name} {
|
||||
.Knob__ringFill {
|
||||
stroke: $color-value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .Slider__cursorOffset {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// bottom: 0;
|
||||
// transition: none !important;
|
||||
// }
|
||||
|
||||
// .Slider__cursor {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// right: -1px;
|
||||
// bottom: 0;
|
||||
// width: 0;
|
||||
// border-left: 2px solid #fff;
|
||||
// }
|
||||
|
||||
// .Slider__pointer {
|
||||
// position: absolute;
|
||||
// right: -5px;
|
||||
// bottom: -4px;
|
||||
// width: 0;
|
||||
// height: 0;
|
||||
// border-left: 5px solid transparent;
|
||||
// border-right: 5px solid transparent;
|
||||
// border-bottom: 5px solid #fff;
|
||||
// }
|
||||
|
||||
// .Slider__popupValue {
|
||||
// position: absolute;
|
||||
// right: 0;
|
||||
// top: -22px;
|
||||
// padding: 2px 4px;
|
||||
// background-color: #000;
|
||||
// transform: translateX(50%);
|
||||
// white-space: nowrap;
|
||||
// }
|
||||
@@ -22,6 +22,9 @@ $bg-map: colors.$bg-map !default;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.ProgressBar__fill--animated {
|
||||
transition: background-color 500ms, width 500ms;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
.Slider {
|
||||
cursor: e-resize;
|
||||
}
|
||||
|
||||
.Slider__cursorOffset {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.Slider__cursor {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -1px;
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
border-left: 2px solid #fff;
|
||||
}
|
||||
|
||||
.Slider__pointer {
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
bottom: -4px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-bottom: 5px solid #fff;
|
||||
}
|
||||
|
||||
.Slider__popupValue {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -22px;
|
||||
padding: 2px 4px;
|
||||
background-color: #000;
|
||||
transform: translateX(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -7,11 +7,12 @@
|
||||
// Atomic classes
|
||||
@include meta.load-css('./atomic/candystripe.scss');
|
||||
@include meta.load-css('./atomic/color.scss');
|
||||
@include meta.load-css('./atomic/debug-layout.scss');
|
||||
@include meta.load-css('./atomic/display.scss');
|
||||
@include meta.load-css('./atomic/margin.scss');
|
||||
@include meta.load-css('./atomic/outline.scss');
|
||||
@include meta.load-css('./atomic/position.scss');
|
||||
@include meta.load-css('./atomic/text.scss');
|
||||
@include meta.load-css('./atomic/outline.scss');
|
||||
|
||||
// Components
|
||||
@include meta.load-css('./components/BlockQuote.scss');
|
||||
@@ -24,6 +25,7 @@
|
||||
@include meta.load-css('./components/FatalError.scss');
|
||||
@include meta.load-css('./components/Flex.scss');
|
||||
@include meta.load-css('./components/Input.scss');
|
||||
@include meta.load-css('./components/Knob.scss');
|
||||
@include meta.load-css('./components/LabeledList.scss');
|
||||
@include meta.load-css('./components/Layout.scss');
|
||||
@include meta.load-css('./components/NoticeBox.scss');
|
||||
@@ -34,6 +36,7 @@
|
||||
@include meta.load-css('./components/ProgressBar.scss');
|
||||
@include meta.load-css('./components/Roulette.scss');
|
||||
@include meta.load-css('./components/Section.scss');
|
||||
@include meta.load-css('./components/Slider.scss');
|
||||
@include meta.load-css('./components/Table.scss');
|
||||
@include meta.load-css('./components/Tabs.scss');
|
||||
@include meta.load-css('./components/TitleBar.scss');
|
||||
|
||||
@@ -2,6 +2,7 @@ html, body {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -12,7 +13,6 @@ html {
|
||||
body {
|
||||
overflow: auto;
|
||||
font-family: Verdana, Geneva, sans-serif;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
*, *:before, *:after {
|
||||
|
||||
Reference in New Issue
Block a user