mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-13 11:43:31 +00:00
[MIRROR] Add tgui-core and use it for ByondUi (#9119)
Co-authored-by: Heroman3003 <31296024+Heroman3003@users.noreply.github.com> Co-authored-by: CHOMPStation2 <chompsation2@gmail.com>
This commit is contained in:
@@ -24,7 +24,6 @@
|
|||||||
@include meta.load-css('~tgui/styles/atomic/text.scss');
|
@include meta.load-css('~tgui/styles/atomic/text.scss');
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
@include meta.load-css('~tgui/styles/components/BlockQuote.scss');
|
|
||||||
@include meta.load-css('~tgui/styles/components/Button.scss');
|
@include meta.load-css('~tgui/styles/components/Button.scss');
|
||||||
@include meta.load-css('~tgui/styles/components/ColorBox.scss');
|
@include meta.load-css('~tgui/styles/components/ColorBox.scss');
|
||||||
@include meta.load-css('~tgui/styles/components/Dimmer.scss');
|
@include meta.load-css('~tgui/styles/components/Dimmer.scss');
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
import { Component } from 'react';
|
|
||||||
|
|
||||||
const DEFAULT_BLINKING_INTERVAL = 1000;
|
|
||||||
const DEFAULT_BLINKING_TIME = 1000;
|
|
||||||
|
|
||||||
export class Blink extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
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() {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
visibility: this.state.hidden ? 'hidden' : 'visible',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.props.children}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file
|
|
||||||
* @copyright 2020 Aleksej Komarov
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { classes } from 'common/react';
|
|
||||||
|
|
||||||
import { Box, BoxProps } from './Box';
|
|
||||||
|
|
||||||
export function BlockQuote(props: BoxProps) {
|
|
||||||
const { className, ...rest } = props;
|
|
||||||
|
|
||||||
return <Box className={classes(['BlockQuote', className])} {...rest} />;
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file
|
|
||||||
* @copyright 2020 Aleksej Komarov
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { shallowDiffers } from 'common/react';
|
|
||||||
import { debounce } from 'common/timer';
|
|
||||||
import { Component, createRef } from 'react';
|
|
||||||
|
|
||||||
import { createLogger } from '../logging';
|
|
||||||
import { computeBoxProps } from './Box';
|
|
||||||
|
|
||||||
const logger = createLogger('ByondUi');
|
|
||||||
|
|
||||||
// Stack of currently allocated BYOND UI element ids.
|
|
||||||
const byondUiStack = [];
|
|
||||||
|
|
||||||
const createByondUiElement = (elementId) => {
|
|
||||||
// Reserve an index in the stack
|
|
||||||
const index = byondUiStack.length;
|
|
||||||
byondUiStack.push(null);
|
|
||||||
// Get a unique id
|
|
||||||
const id = elementId || 'byondui_' + index;
|
|
||||||
logger.log(`allocated '${id}'`);
|
|
||||||
// Return a control structure
|
|
||||||
return {
|
|
||||||
render: (params) => {
|
|
||||||
logger.log(`rendering '${id}'`);
|
|
||||||
byondUiStack[index] = id;
|
|
||||||
Byond.winset(id, params);
|
|
||||||
},
|
|
||||||
unmount: () => {
|
|
||||||
logger.log(`unmounting '${id}'`);
|
|
||||||
byondUiStack[index] = null;
|
|
||||||
Byond.winset(id, {
|
|
||||||
parent: '',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener('beforeunload', () => {
|
|
||||||
// Cleanly unmount all visible UI elements
|
|
||||||
for (let index = 0; index < byondUiStack.length; index++) {
|
|
||||||
const id = byondUiStack[index];
|
|
||||||
if (typeof id === 'string') {
|
|
||||||
logger.log(`unmounting '${id}' (beforeunload)`);
|
|
||||||
byondUiStack[index] = null;
|
|
||||||
Byond.winset(id, {
|
|
||||||
parent: '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the bounding box of the DOM element in display-pixels.
|
|
||||||
*/
|
|
||||||
const getBoundingBox = (element) => {
|
|
||||||
const pixelRatio = window.devicePixelRatio ?? 1;
|
|
||||||
const rect = element.getBoundingClientRect();
|
|
||||||
// prettier-ignore
|
|
||||||
return {
|
|
||||||
pos: [
|
|
||||||
rect.left * pixelRatio,
|
|
||||||
rect.top * pixelRatio,
|
|
||||||
],
|
|
||||||
size: [
|
|
||||||
(rect.right - rect.left) * pixelRatio,
|
|
||||||
(rect.bottom - rect.top) * pixelRatio,
|
|
||||||
],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ByondUi extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.containerRef = createRef();
|
|
||||||
this.byondUiElement = createByondUiElement(props.params?.id);
|
|
||||||
this.handleResize = debounce(() => {
|
|
||||||
this.forceUpdate();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
|
||||||
const { params: prevParams = {}, ...prevRest } = this.props;
|
|
||||||
const { params: nextParams = {}, ...nextRest } = nextProps;
|
|
||||||
return (
|
|
||||||
shallowDiffers(prevParams, nextParams) ||
|
|
||||||
shallowDiffers(prevRest, nextRest)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
window.addEventListener('resize', this.handleResize);
|
|
||||||
this.componentDidUpdate();
|
|
||||||
this.handleResize();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
const { params = {} } = this.props;
|
|
||||||
const box = getBoundingBox(this.containerRef.current);
|
|
||||||
logger.debug('bounding box', box);
|
|
||||||
this.byondUiElement.render({
|
|
||||||
parent: Byond.windowId,
|
|
||||||
...params,
|
|
||||||
pos: box.pos[0] + ',' + box.pos[1],
|
|
||||||
size: box.size[0] + 'x' + box.size[1],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
window.removeEventListener('resize', this.handleResize);
|
|
||||||
this.byondUiElement.unmount();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { params, ...rest } = this.props;
|
|
||||||
return (
|
|
||||||
<div ref={this.containerRef} {...computeBoxProps(rest)}>
|
|
||||||
{/* Filler */}
|
|
||||||
<div style={{ minHeight: '22px' }} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import { ReactNode, useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
import { resolveAsset } from '../assets';
|
|
||||||
import { fetchRetry } from '../http';
|
|
||||||
import { BoxProps } from './Box';
|
|
||||||
import { Image } from './Image';
|
|
||||||
|
|
||||||
enum Direction {
|
|
||||||
NORTH = 1,
|
|
||||||
SOUTH = 2,
|
|
||||||
EAST = 4,
|
|
||||||
WEST = 8,
|
|
||||||
NORTHEAST = NORTH | EAST,
|
|
||||||
NORTHWEST = NORTH | WEST,
|
|
||||||
SOUTHEAST = SOUTH | EAST,
|
|
||||||
SOUTHWEST = SOUTH | WEST,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
/** Required: The path of the icon */
|
|
||||||
icon: string;
|
|
||||||
/** Required: The state of the icon */
|
|
||||||
icon_state: string;
|
|
||||||
} & Partial<{
|
|
||||||
/** Facing direction. See direction enum. Default is South */
|
|
||||||
direction: Direction;
|
|
||||||
/** Fallback icon. */
|
|
||||||
fallback: ReactNode;
|
|
||||||
/** Frame number. Default is 1 */
|
|
||||||
frame: number;
|
|
||||||
/** Movement state. Default is false */
|
|
||||||
movement: boolean;
|
|
||||||
}> &
|
|
||||||
BoxProps;
|
|
||||||
|
|
||||||
let refMap: Record<string, string> | undefined;
|
|
||||||
|
|
||||||
export function DmIcon(props: Props) {
|
|
||||||
const {
|
|
||||||
className,
|
|
||||||
direction = Direction.SOUTH,
|
|
||||||
fallback,
|
|
||||||
frame = 1,
|
|
||||||
icon_state,
|
|
||||||
icon,
|
|
||||||
movement = false,
|
|
||||||
...rest
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [iconRef, setIconRef] = useState('');
|
|
||||||
|
|
||||||
const query = `${iconRef}?state=${icon_state}&dir=${direction}&movement=${movement}&frame=${frame}`;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function fetchRefMap() {
|
|
||||||
const response = await fetchRetry(resolveAsset('icon_ref_map.json'));
|
|
||||||
const data = await response.json();
|
|
||||||
refMap = data;
|
|
||||||
setIconRef(data[icon]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!refMap) {
|
|
||||||
fetchRefMap();
|
|
||||||
} else {
|
|
||||||
setIconRef(refMap[icon]);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (!iconRef) return fallback;
|
|
||||||
|
|
||||||
return <Image fixErrors src={query} {...rest} />;
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { Component, Fragment } from 'react';
|
|
||||||
|
|
||||||
import { Box } from './Box';
|
|
||||||
|
|
||||||
export class FakeTerminal extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.timer = null;
|
|
||||||
this.state = {
|
|
||||||
currentIndex: 0,
|
|
||||||
currentDisplay: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
tick() {
|
|
||||||
const { props, state } = this;
|
|
||||||
if (state.currentIndex <= props.allMessages.length) {
|
|
||||||
this.setState((prevState) => {
|
|
||||||
return {
|
|
||||||
currentIndex: prevState.currentIndex + 1,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const { currentDisplay } = state;
|
|
||||||
currentDisplay.push(props.allMessages[state.currentIndex]);
|
|
||||||
} else {
|
|
||||||
clearTimeout(this.timer);
|
|
||||||
setTimeout(props.onFinished, props.finishedTimeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const { linesPerSecond = 2.5 } = this.props;
|
|
||||||
this.timer = setInterval(() => this.tick(), 1000 / linesPerSecond);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
clearTimeout(this.timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Box m={1}>
|
|
||||||
{this.state.currentDisplay.map((value) => (
|
|
||||||
<Fragment key={value}>
|
|
||||||
{value}
|
|
||||||
<br />
|
|
||||||
</Fragment>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
import {
|
|
||||||
Component,
|
|
||||||
createRef,
|
|
||||||
HTMLAttributes,
|
|
||||||
PropsWithChildren,
|
|
||||||
RefObject,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
const DEFAULT_ACCEPTABLE_DIFFERENCE = 5;
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
acceptableDifference?: number;
|
|
||||||
maxWidth: number;
|
|
||||||
maxFontSize: number;
|
|
||||||
native?: HTMLAttributes<HTMLDivElement>;
|
|
||||||
} & PropsWithChildren;
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
fontSize: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class FitText extends Component<Props, State> {
|
|
||||||
ref: RefObject<HTMLDivElement> = createRef();
|
|
||||||
state: State = {
|
|
||||||
fontSize: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.resize = this.resize.bind(this);
|
|
||||||
|
|
||||||
window.addEventListener('resize', this.resize);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
if (prevProps.children !== this.props.children) {
|
|
||||||
this.resize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
window.removeEventListener('resize', this.resize);
|
|
||||||
}
|
|
||||||
|
|
||||||
resize() {
|
|
||||||
const element = this.ref.current;
|
|
||||||
if (!element) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxWidth = this.props.maxWidth;
|
|
||||||
|
|
||||||
let start = 0;
|
|
||||||
let end = this.props.maxFontSize;
|
|
||||||
|
|
||||||
for (let _ = 0; _ < 10; _++) {
|
|
||||||
const middle = Math.round((start + end) / 2);
|
|
||||||
element.style.fontSize = `${middle}px`;
|
|
||||||
|
|
||||||
const difference = element.offsetWidth - maxWidth;
|
|
||||||
|
|
||||||
if (difference > 0) {
|
|
||||||
end = middle;
|
|
||||||
} else if (
|
|
||||||
difference <
|
|
||||||
(this.props.acceptableDifference ?? DEFAULT_ACCEPTABLE_DIFFERENCE)
|
|
||||||
) {
|
|
||||||
start = middle;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
fontSize: Math.round((start + end) / 2),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.resize();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
ref={this.ref}
|
|
||||||
style={{
|
|
||||||
fontSize: `${this.state.fontSize}px`,
|
|
||||||
...(typeof this.props.native?.style === 'object'
|
|
||||||
? this.props.native.style
|
|
||||||
: {}),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.props.children}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file
|
|
||||||
* @copyright 2020 Aleksej Komarov
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { PropsWithChildren } from 'react';
|
|
||||||
|
|
||||||
import { logger } from '../logging';
|
|
||||||
import { BoxProps } from './Box';
|
|
||||||
import { Table } from './Table';
|
|
||||||
|
|
||||||
/** @deprecated Do not use. Use stack instead. */
|
|
||||||
export function Grid(props: PropsWithChildren<BoxProps>) {
|
|
||||||
const { children, ...rest } = props;
|
|
||||||
logger.error('Grid component is deprecated. Use a Stack instead.');
|
|
||||||
return (
|
|
||||||
<Table {...rest}>
|
|
||||||
<Table.Row>{children}</Table.Row>
|
|
||||||
</Table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = Partial<{
|
|
||||||
/** Width of the column in percentage. */
|
|
||||||
size: number;
|
|
||||||
}> &
|
|
||||||
BoxProps;
|
|
||||||
|
|
||||||
/** @deprecated Do not use. Use stack instead. */
|
|
||||||
export function GridColumn(props: Props) {
|
|
||||||
const { size = 1, style, ...rest } = props;
|
|
||||||
return (
|
|
||||||
<Table.Cell
|
|
||||||
style={{
|
|
||||||
width: size + '%',
|
|
||||||
...style,
|
|
||||||
}}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Grid.Column = GridColumn;
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
import { Component } from 'react';
|
|
||||||
|
|
||||||
import { computeBoxProps } from './Box';
|
|
||||||
import { Button } from './Button';
|
|
||||||
import { ProgressBar } from './ProgressBar';
|
|
||||||
import { Stack } from './Stack';
|
|
||||||
|
|
||||||
const ZOOM_MIN_VAL = 0.5;
|
|
||||||
const ZOOM_MAX_VAL = 1.5;
|
|
||||||
|
|
||||||
const ZOOM_INCREMENT = 0.1;
|
|
||||||
|
|
||||||
export class InfinitePlane extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
mouseDown: false,
|
|
||||||
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
|
|
||||||
lastLeft: 0,
|
|
||||||
lastTop: 0,
|
|
||||||
|
|
||||||
zoom: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handleMouseDown = this.handleMouseDown.bind(this);
|
|
||||||
this.handleMouseMove = this.handleMouseMove.bind(this);
|
|
||||||
this.handleZoomIncrease = this.handleZoomIncrease.bind(this);
|
|
||||||
this.handleZoomDecrease = this.handleZoomDecrease.bind(this);
|
|
||||||
this.onMouseUp = this.onMouseUp.bind(this);
|
|
||||||
|
|
||||||
this.doOffsetMouse = this.doOffsetMouse.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
window.addEventListener('mouseup', this.onMouseUp);
|
|
||||||
|
|
||||||
window.addEventListener('mousedown', this.doOffsetMouse);
|
|
||||||
window.addEventListener('mousemove', this.doOffsetMouse);
|
|
||||||
window.addEventListener('mouseup', this.doOffsetMouse);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
window.removeEventListener('mouseup', this.onMouseUp);
|
|
||||||
|
|
||||||
window.removeEventListener('mousedown', this.doOffsetMouse);
|
|
||||||
window.removeEventListener('mousemove', this.doOffsetMouse);
|
|
||||||
window.removeEventListener('mouseup', this.doOffsetMouse);
|
|
||||||
}
|
|
||||||
|
|
||||||
doOffsetMouse(event) {
|
|
||||||
const { zoom } = this.state;
|
|
||||||
event.screenZoomX = event.screenX * Math.pow(zoom, -1);
|
|
||||||
event.screenZoomY = event.screenY * Math.pow(zoom, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseDown(event) {
|
|
||||||
this.setState((state) => {
|
|
||||||
return {
|
|
||||||
mouseDown: true,
|
|
||||||
lastLeft: event.clientX - state.left,
|
|
||||||
lastTop: event.clientY - state.top,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMouseUp() {
|
|
||||||
this.setState({
|
|
||||||
mouseDown: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleZoomIncrease(event) {
|
|
||||||
const { onZoomChange } = this.props;
|
|
||||||
const { zoom } = this.state;
|
|
||||||
const newZoomValue = Math.min(zoom + ZOOM_INCREMENT, ZOOM_MAX_VAL);
|
|
||||||
this.setState({
|
|
||||||
zoom: newZoomValue,
|
|
||||||
});
|
|
||||||
if (onZoomChange) {
|
|
||||||
onZoomChange(newZoomValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleZoomDecrease(event) {
|
|
||||||
const { onZoomChange } = this.props;
|
|
||||||
const { zoom } = this.state;
|
|
||||||
const newZoomValue = Math.max(zoom - ZOOM_INCREMENT, ZOOM_MIN_VAL);
|
|
||||||
this.setState({
|
|
||||||
zoom: newZoomValue,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (onZoomChange) {
|
|
||||||
onZoomChange(newZoomValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseMove(event) {
|
|
||||||
const { onBackgroundMoved, initialLeft = 0, initialTop = 0 } = this.props;
|
|
||||||
if (this.state.mouseDown) {
|
|
||||||
let newX, newY;
|
|
||||||
this.setState((state) => {
|
|
||||||
newX = event.clientX - state.lastLeft;
|
|
||||||
newY = event.clientY - state.lastTop;
|
|
||||||
return {
|
|
||||||
left: newX,
|
|
||||||
top: newY,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
if (onBackgroundMoved) {
|
|
||||||
onBackgroundMoved(newX + initialLeft, newY + initialTop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
children,
|
|
||||||
backgroundImage,
|
|
||||||
imageWidth,
|
|
||||||
initialLeft = 0,
|
|
||||||
initialTop = 0,
|
|
||||||
...rest
|
|
||||||
} = this.props;
|
|
||||||
const { left, top, zoom } = this.state;
|
|
||||||
|
|
||||||
const finalLeft = initialLeft + left;
|
|
||||||
const finalTop = initialTop + top;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={this.ref}
|
|
||||||
{...computeBoxProps({
|
|
||||||
...rest,
|
|
||||||
style: {
|
|
||||||
...rest.style,
|
|
||||||
overflow: 'hidden',
|
|
||||||
position: 'relative',
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
onMouseDown={this.handleMouseDown}
|
|
||||||
onMouseMove={this.handleMouseMove}
|
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
backgroundImage: `url("${backgroundImage}")`,
|
|
||||||
backgroundPosition: `${finalLeft}px ${finalTop}px`,
|
|
||||||
backgroundRepeat: 'repeat',
|
|
||||||
backgroundSize: `${zoom * imageWidth}px`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
onMouseDown={this.handleMouseDown}
|
|
||||||
onMouseMove={this.handleMouseMove}
|
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
|
||||||
transform: `translate(${finalLeft}px, ${finalTop}px) scale(${zoom})`,
|
|
||||||
transformOrigin: 'top left',
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Stack position="absolute" width="100%">
|
|
||||||
<Stack.Item>
|
|
||||||
<Button icon="minus" onClick={this.handleZoomDecrease} />
|
|
||||||
</Stack.Item>
|
|
||||||
<Stack.Item grow={1}>
|
|
||||||
<ProgressBar
|
|
||||||
minValue={ZOOM_MIN_VAL}
|
|
||||||
value={zoom}
|
|
||||||
maxValue={ZOOM_MAX_VAL}
|
|
||||||
>
|
|
||||||
{zoom}x
|
|
||||||
</ProgressBar>
|
|
||||||
</Stack.Item>
|
|
||||||
<Stack.Item>
|
|
||||||
<Button icon="plus" onClick={this.handleZoomIncrease} />
|
|
||||||
</Stack.Item>
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { Component } from 'react';
|
|
||||||
|
|
||||||
import { KeyEvent } from '../events';
|
|
||||||
import { listenForKeyEvents } from '../hotkeys';
|
|
||||||
|
|
||||||
type KeyListenerProps = Partial<{
|
|
||||||
onKey: (key: KeyEvent) => void;
|
|
||||||
onKeyDown: (key: KeyEvent) => void;
|
|
||||||
onKeyUp: (key: KeyEvent) => void;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export class KeyListener extends Component<KeyListenerProps> {
|
|
||||||
dispose: () => void;
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.dispose = listenForKeyEvents((key) => {
|
|
||||||
if (this.props.onKey) {
|
|
||||||
this.props.onKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key.isDown() && this.props.onKeyDown) {
|
|
||||||
this.props.onKeyDown(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key.isUp() && this.props.onKeyUp) {
|
|
||||||
this.props.onKeyUp(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file
|
|
||||||
* @copyright 2022 Aleksej Komarov
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { classes } from 'common/react';
|
|
||||||
import { Component, createRef, ReactNode, RefObject } from 'react';
|
|
||||||
|
|
||||||
import { logger } from '../logging';
|
|
||||||
import { Box } from './Box';
|
|
||||||
import { Icon } from './Icon';
|
|
||||||
|
|
||||||
type MenuProps = {
|
|
||||||
children: any;
|
|
||||||
width: string;
|
|
||||||
menuRef: RefObject<HTMLElement>;
|
|
||||||
onOutsideClick: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Menu extends Component<MenuProps> {
|
|
||||||
private readonly handleClick: (event) => void;
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.handleClick = (event) => {
|
|
||||||
if (!this.props.menuRef.current) {
|
|
||||||
logger.log(`Menu.handleClick(): No ref`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.menuRef.current.contains(event.target)) {
|
|
||||||
logger.log(`Menu.handleClick(): Inside`);
|
|
||||||
} else {
|
|
||||||
logger.log(`Menu.handleClick(): Outside`);
|
|
||||||
this.props.onOutsideClick();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line react/no-deprecated
|
|
||||||
componentWillMount() {
|
|
||||||
window.addEventListener('click', this.handleClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
window.removeEventListener('click', this.handleClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { width, children } = this.props;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={'MenuBar__menu'}
|
|
||||||
style={{
|
|
||||||
width: width,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MenuBarDropdownProps = {
|
|
||||||
open: boolean;
|
|
||||||
openWidth: string;
|
|
||||||
children: any;
|
|
||||||
disabled?: boolean;
|
|
||||||
display: any;
|
|
||||||
onMouseOver: () => void;
|
|
||||||
onClick: () => void;
|
|
||||||
onOutsideClick: () => void;
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MenuBarButton extends Component<MenuBarDropdownProps> {
|
|
||||||
private readonly menuRef: RefObject<HTMLDivElement>;
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.menuRef = createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { props } = this;
|
|
||||||
const {
|
|
||||||
open,
|
|
||||||
openWidth,
|
|
||||||
children,
|
|
||||||
disabled,
|
|
||||||
display,
|
|
||||||
onMouseOver,
|
|
||||||
onClick,
|
|
||||||
onOutsideClick,
|
|
||||||
...boxProps
|
|
||||||
} = props;
|
|
||||||
const { className, ...rest } = boxProps;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={this.menuRef}>
|
|
||||||
<Box
|
|
||||||
className={classes([
|
|
||||||
'MenuBar__MenuBarButton',
|
|
||||||
'MenuBar__font',
|
|
||||||
'MenuBar__hover',
|
|
||||||
className,
|
|
||||||
])}
|
|
||||||
{...rest}
|
|
||||||
onClick={disabled ? () => null : onClick}
|
|
||||||
onMouseOver={onMouseOver}
|
|
||||||
>
|
|
||||||
<span className="MenuBar__MenuBarButton-text">{display}</span>
|
|
||||||
</Box>
|
|
||||||
{open && (
|
|
||||||
<Menu
|
|
||||||
width={openWidth}
|
|
||||||
menuRef={this.menuRef}
|
|
||||||
onOutsideClick={onOutsideClick}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Menu>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type MenuBarItemProps = {
|
|
||||||
entry: string;
|
|
||||||
children: any;
|
|
||||||
openWidth: string;
|
|
||||||
display: ReactNode;
|
|
||||||
setOpenMenuBar: (entry: string | null) => void;
|
|
||||||
openMenuBar: string | null;
|
|
||||||
setOpenOnHover: (flag: boolean) => void;
|
|
||||||
openOnHover: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
className?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Dropdown = (props: MenuBarItemProps) => {
|
|
||||||
const {
|
|
||||||
entry,
|
|
||||||
children,
|
|
||||||
openWidth,
|
|
||||||
display,
|
|
||||||
setOpenMenuBar,
|
|
||||||
openMenuBar,
|
|
||||||
setOpenOnHover,
|
|
||||||
openOnHover,
|
|
||||||
disabled,
|
|
||||||
className,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MenuBarButton
|
|
||||||
openWidth={openWidth}
|
|
||||||
display={display}
|
|
||||||
disabled={disabled}
|
|
||||||
open={openMenuBar === entry}
|
|
||||||
className={className}
|
|
||||||
onClick={() => {
|
|
||||||
const open = openMenuBar === entry ? null : entry;
|
|
||||||
setOpenMenuBar(open);
|
|
||||||
setOpenOnHover(!openOnHover);
|
|
||||||
}}
|
|
||||||
onOutsideClick={() => {
|
|
||||||
setOpenMenuBar(null);
|
|
||||||
setOpenOnHover(false);
|
|
||||||
}}
|
|
||||||
onMouseOver={() => {
|
|
||||||
if (openOnHover) {
|
|
||||||
setOpenMenuBar(entry);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</MenuBarButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MenuItemToggle = (props) => {
|
|
||||||
const { value, displayText, onClick, checked } = props;
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
className={classes([
|
|
||||||
'MenuBar__font',
|
|
||||||
'MenuBar__MenuItem',
|
|
||||||
'MenuBar__MenuItemToggle',
|
|
||||||
'MenuBar__hover',
|
|
||||||
])}
|
|
||||||
onClick={() => onClick(value)}
|
|
||||||
>
|
|
||||||
<div className="MenuBar__MenuItemToggle__check">
|
|
||||||
{checked && <Icon size={1.3} name="check" />}
|
|
||||||
</div>
|
|
||||||
{displayText}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Dropdown.MenuItemToggle = MenuItemToggle;
|
|
||||||
|
|
||||||
const MenuItem = (props) => {
|
|
||||||
const { value, displayText, onClick } = props;
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
className={classes([
|
|
||||||
'MenuBar__font',
|
|
||||||
'MenuBar__MenuItem',
|
|
||||||
'MenuBar__hover',
|
|
||||||
])}
|
|
||||||
onClick={() => onClick(value)}
|
|
||||||
>
|
|
||||||
{displayText}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Dropdown.MenuItem = MenuItem;
|
|
||||||
|
|
||||||
const Separator = () => {
|
|
||||||
return <div className="MenuBar__Separator" />;
|
|
||||||
};
|
|
||||||
|
|
||||||
Dropdown.Separator = Separator;
|
|
||||||
|
|
||||||
type MenuBarProps = {
|
|
||||||
children: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MenuBar = (props: MenuBarProps) => {
|
|
||||||
const { children } = props;
|
|
||||||
return <Box className="MenuBar">{children}</Box>;
|
|
||||||
};
|
|
||||||
|
|
||||||
MenuBar.Dropdown = Dropdown;
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
/**
|
|
||||||
* @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) => {
|
|
||||||
const {
|
|
||||||
value,
|
|
||||||
minValue = 1,
|
|
||||||
maxValue = 1,
|
|
||||||
ranges,
|
|
||||||
alertAfter,
|
|
||||||
alertBefore,
|
|
||||||
format,
|
|
||||||
size = 1,
|
|
||||||
className,
|
|
||||||
style,
|
|
||||||
...rest
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const scaledValue = scale(value, minValue, maxValue);
|
|
||||||
const clampedValue = clamp01(scaledValue);
|
|
||||||
const 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),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const shouldShowAlert = () => {
|
|
||||||
// If both after and before alert props are set, attempt to interpret both
|
|
||||||
// in a helpful way.
|
|
||||||
if (alertAfter && alertBefore && alertAfter < alertBefore) {
|
|
||||||
// If alertAfter is before alertBefore, only display an alert if
|
|
||||||
// we're between them.
|
|
||||||
if (alertAfter < value && alertBefore > value) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (alertAfter < value || alertBefore > value) {
|
|
||||||
// Otherwise, we have distint ranges, or only one or neither are set.
|
|
||||||
// Either way, being on the active side of either is sufficient.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// prettier-ignore
|
|
||||||
const alertColor = shouldShowAlert()
|
|
||||||
&& keyOfMatchingRange(clampedValue, scaledRanges);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box inline>
|
|
||||||
<div
|
|
||||||
className={classes([
|
|
||||||
'RoundGauge',
|
|
||||||
className,
|
|
||||||
computeBoxClassName(rest),
|
|
||||||
])}
|
|
||||||
{...computeBoxProps({
|
|
||||||
style: {
|
|
||||||
fontSize: size + 'em',
|
|
||||||
...style,
|
|
||||||
},
|
|
||||||
...rest,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<svg viewBox="0 0 100 50">
|
|
||||||
{(alertAfter || alertBefore) && (
|
|
||||||
<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={{
|
|
||||||
strokeDashoffset: 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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { PropsWithChildren, ReactNode } from 'react';
|
|
||||||
|
|
||||||
import { Box } from './Box';
|
|
||||||
|
|
||||||
type Props = Partial<{
|
|
||||||
style: Record<string, any>;
|
|
||||||
titleStyle: Record<string, any>;
|
|
||||||
textStyle: Record<string, any>;
|
|
||||||
title: ReactNode;
|
|
||||||
titleSubtext: string;
|
|
||||||
}> &
|
|
||||||
PropsWithChildren;
|
|
||||||
|
|
||||||
// The cost of flexibility and prettiness.
|
|
||||||
export const StyleableSection = (props: Props) => {
|
|
||||||
return (
|
|
||||||
<Box style={props.style}>
|
|
||||||
{/* Yes, this box (line above) is missing the "Section" class. This is very intentional, as the layout looks *ugly* with it.*/}
|
|
||||||
<Box className="Section__title" style={props.titleStyle}>
|
|
||||||
<Box className="Section__titleText" style={props.textStyle}>
|
|
||||||
{props.title}
|
|
||||||
</Box>
|
|
||||||
<div className="Section__buttons">{props.titleSubtext}</div>
|
|
||||||
</Box>
|
|
||||||
<Box className="Section__rest">
|
|
||||||
<Box className="Section__content">{props.children}</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import { Component } from 'react';
|
|
||||||
|
|
||||||
import { formatTime } from '../format';
|
|
||||||
|
|
||||||
// AnimatedNumber Copypaste
|
|
||||||
const isSafeNumber = (value) => {
|
|
||||||
return (
|
|
||||||
typeof value === 'number' && Number.isFinite(value) && !Number.isNaN(value)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export class TimeDisplay extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.timer = null;
|
|
||||||
this.last_seen_value = undefined;
|
|
||||||
this.state = {
|
|
||||||
value: 0,
|
|
||||||
};
|
|
||||||
// Set initial state with value provided in props
|
|
||||||
if (isSafeNumber(props.value)) {
|
|
||||||
this.state.value = Number(props.value);
|
|
||||||
this.last_seen_value = Number(props.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
if (this.props.auto !== undefined) {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
this.timer = setInterval(() => this.tick(), 1000); // every 1 s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tick() {
|
|
||||||
let current = Number(this.state.value);
|
|
||||||
if (this.props.value !== this.last_seen_value) {
|
|
||||||
this.last_seen_value = this.props.value;
|
|
||||||
current = this.props.value;
|
|
||||||
}
|
|
||||||
const mod = this.props.auto === 'up' ? 10 : -10; // Time down by default.
|
|
||||||
const value = Math.max(0, current + mod); // one sec tick
|
|
||||||
this.setState({ value });
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
if (this.props.auto !== undefined) {
|
|
||||||
this.timer = setInterval(() => this.tick(), 1000); // every 1 s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const val = this.state.value;
|
|
||||||
// Directly display weird stuff
|
|
||||||
if (!isSafeNumber(val)) {
|
|
||||||
return this.state.value || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatTime(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { Component, createRef, PropsWithChildren } from 'react';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
onOutsideClick: () => void;
|
|
||||||
} & PropsWithChildren;
|
|
||||||
|
|
||||||
export class TrackOutsideClicks extends Component<Props> {
|
|
||||||
ref = createRef<HTMLDivElement>();
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.handleOutsideClick = this.handleOutsideClick.bind(this);
|
|
||||||
|
|
||||||
document.addEventListener('click', this.handleOutsideClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
document.removeEventListener('click', this.handleOutsideClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOutsideClick(event: MouseEvent) {
|
|
||||||
if (!(event.target instanceof Node)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.ref.current && !this.ref.current.contains(event.target)) {
|
|
||||||
this.props.onOutsideClick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return <div ref={this.ref}>{this.props.children}</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import {
|
|
||||||
PropsWithChildren,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A vertical list that renders items to fill space up to the extents of the
|
|
||||||
* current window, and then defers rendering of other items until they come
|
|
||||||
* into view.
|
|
||||||
*/
|
|
||||||
export const VirtualList = (props: PropsWithChildren) => {
|
|
||||||
const { children } = props;
|
|
||||||
const containerRef = useRef(null as HTMLDivElement | null);
|
|
||||||
const [visibleElements, setVisibleElements] = useState(1);
|
|
||||||
const [padding, setPadding] = useState(0);
|
|
||||||
|
|
||||||
const adjustExtents = useCallback(() => {
|
|
||||||
const { current } = containerRef;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!children ||
|
|
||||||
!Array.isArray(children) ||
|
|
||||||
!current ||
|
|
||||||
visibleElements >= children.length
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const unusedArea =
|
|
||||||
document.body.offsetHeight - current.getBoundingClientRect().bottom;
|
|
||||||
|
|
||||||
const averageItemHeight = Math.ceil(current.offsetHeight / visibleElements);
|
|
||||||
|
|
||||||
if (unusedArea > 0) {
|
|
||||||
const newVisibleElements = Math.min(
|
|
||||||
children.length,
|
|
||||||
visibleElements +
|
|
||||||
Math.max(1, Math.ceil(unusedArea / averageItemHeight)),
|
|
||||||
);
|
|
||||||
|
|
||||||
setVisibleElements(newVisibleElements);
|
|
||||||
|
|
||||||
setPadding((children.length - newVisibleElements) * averageItemHeight);
|
|
||||||
}
|
|
||||||
}, [containerRef, visibleElements, children]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
adjustExtents();
|
|
||||||
|
|
||||||
const interval = setInterval(adjustExtents, 100);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, [adjustExtents]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={'VirtualList'}>
|
|
||||||
<div className={'VirtualList__Container'} ref={containerRef}>
|
|
||||||
{Array.isArray(children) ? children.slice(0, visibleElements) : null}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={'VirtualList__Padding'}
|
|
||||||
style={{ paddingBottom: `${padding}px` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -6,32 +6,23 @@
|
|||||||
|
|
||||||
export { AnimatedNumber } from './AnimatedNumber';
|
export { AnimatedNumber } from './AnimatedNumber';
|
||||||
export { Autofocus } from './Autofocus';
|
export { Autofocus } from './Autofocus';
|
||||||
export { Blink } from './Blink';
|
|
||||||
export { BlockQuote } from './BlockQuote';
|
|
||||||
export { Box } from './Box';
|
export { Box } from './Box';
|
||||||
export { Button } from './Button';
|
export { Button } from './Button';
|
||||||
export { ByondUi } from './ByondUi';
|
|
||||||
export { Chart } from './Chart';
|
export { Chart } from './Chart';
|
||||||
export { Collapsible } from './Collapsible';
|
export { Collapsible } from './Collapsible';
|
||||||
export { ColorBox } from './ColorBox';
|
export { ColorBox } from './ColorBox';
|
||||||
export { Dialog } from './Dialog';
|
export { Dialog } from './Dialog';
|
||||||
export { Dimmer } from './Dimmer';
|
export { Dimmer } from './Dimmer';
|
||||||
export { Divider } from './Divider';
|
export { Divider } from './Divider';
|
||||||
export { DmIcon } from './DmIcon';
|
|
||||||
export { DraggableControl } from './DraggableControl';
|
export { DraggableControl } from './DraggableControl';
|
||||||
export { Dropdown } from './Dropdown';
|
export { Dropdown } from './Dropdown';
|
||||||
export { FitText } from './FitText';
|
|
||||||
export { Flex } from './Flex';
|
export { Flex } from './Flex';
|
||||||
export { Grid } from './Grid';
|
|
||||||
export { Icon } from './Icon';
|
export { Icon } from './Icon';
|
||||||
export { Image } from './Image';
|
export { Image } from './Image';
|
||||||
export { InfinitePlane } from './InfinitePlane';
|
|
||||||
export { Input } from './Input';
|
export { Input } from './Input';
|
||||||
export { KeyListener } from './KeyListener';
|
|
||||||
export { Knob } from './Knob';
|
export { Knob } from './Knob';
|
||||||
export { LabeledControls } from './LabeledControls';
|
export { LabeledControls } from './LabeledControls';
|
||||||
export { LabeledList } from './LabeledList';
|
export { LabeledList } from './LabeledList';
|
||||||
export { MenuBar } from './MenuBar';
|
|
||||||
export { Modal } from './Modal';
|
export { Modal } from './Modal';
|
||||||
export { NanoMap } from './NanoMap';
|
export { NanoMap } from './NanoMap';
|
||||||
export { NoticeBox } from './NoticeBox';
|
export { NoticeBox } from './NoticeBox';
|
||||||
@@ -39,15 +30,10 @@ export { NumberInput } from './NumberInput';
|
|||||||
export { Popper } from './Popper';
|
export { Popper } from './Popper';
|
||||||
export { ProgressBar } from './ProgressBar';
|
export { ProgressBar } from './ProgressBar';
|
||||||
export { RestrictedInput } from './RestrictedInput';
|
export { RestrictedInput } from './RestrictedInput';
|
||||||
export { RoundGauge } from './RoundGauge';
|
|
||||||
export { Section } from './Section';
|
export { Section } from './Section';
|
||||||
export { Slider } from './Slider';
|
export { Slider } from './Slider';
|
||||||
export { Stack } from './Stack';
|
export { Stack } from './Stack';
|
||||||
export { StyleableSection } from './StyleableSection';
|
|
||||||
export { Table } from './Table';
|
export { Table } from './Table';
|
||||||
export { Tabs } from './Tabs';
|
export { Tabs } from './Tabs';
|
||||||
export { TextArea } from './TextArea';
|
export { TextArea } from './TextArea';
|
||||||
export { TimeDisplay } from './TimeDisplay';
|
|
||||||
export { Tooltip } from './Tooltip';
|
export { Tooltip } from './Tooltip';
|
||||||
export { TrackOutsideClicks } from './TrackOutsideClicks';
|
|
||||||
export { VirtualList } from './VirtualList';
|
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
import { capitalize, decodeHtmlEntities } from 'common/string';
|
import { capitalize, decodeHtmlEntities } from 'common/string';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useBackend } from 'tgui/backend';
|
||||||
|
import { Box, Flex, LabeledList, Section, Tabs } from 'tgui/components';
|
||||||
|
import { Window } from 'tgui/layouts';
|
||||||
|
import { ByondUi } from 'tgui-core/components';
|
||||||
|
|
||||||
import { useBackend } from '../../backend';
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
ByondUi,
|
|
||||||
Flex,
|
|
||||||
LabeledList,
|
|
||||||
Section,
|
|
||||||
Tabs,
|
|
||||||
} from '../../components';
|
|
||||||
import { Window } from '../../layouts';
|
|
||||||
import {
|
import {
|
||||||
AppearanceChangerEars,
|
AppearanceChangerEars,
|
||||||
AppearanceChangerGender,
|
AppearanceChangerGender,
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { capitalize } from 'common/string';
|
import { capitalize } from 'common/string';
|
||||||
|
import { useBackend } from 'tgui/backend';
|
||||||
import { useBackend } from '../../backend';
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
ByondUi,
|
|
||||||
ColorBox,
|
ColorBox,
|
||||||
Flex,
|
Flex,
|
||||||
LabeledList,
|
LabeledList,
|
||||||
Section,
|
Section,
|
||||||
} from '../../components';
|
} from 'tgui/components';
|
||||||
|
import { ByondUi } from 'tgui-core/components';
|
||||||
|
|
||||||
import { activeBodyRecord } from './types';
|
import { activeBodyRecord } from './types';
|
||||||
|
|
||||||
export const BodyDesignerSpecificRecord = (props: {
|
export const BodyDesignerSpecificRecord = (props: {
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { flow } from 'common/fp';
|
|||||||
import { BooleanLike, classes } from 'common/react';
|
import { BooleanLike, classes } from 'common/react';
|
||||||
import { createSearch } from 'common/string';
|
import { createSearch } from 'common/string';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useBackend } from 'tgui/backend';
|
||||||
import { useBackend } from '../backend';
|
import { Button, Dropdown, Flex, Input, Section } from 'tgui/components';
|
||||||
import { Button, ByondUi, Dropdown, Flex, Input, Section } from '../components';
|
import { Window } from 'tgui/layouts';
|
||||||
import { Window } from '../layouts';
|
import { ByondUi } from 'tgui-core/components';
|
||||||
|
|
||||||
type activeCamera = { name: string; status: BooleanLike } | null;
|
type activeCamera = { name: string; status: BooleanLike } | null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { decodeHtmlEntities } from 'common/string';
|
import { decodeHtmlEntities } from 'common/string';
|
||||||
|
import { useBackend } from 'tgui/backend';
|
||||||
|
import { Box, Button, Flex, Icon, Section } from 'tgui/components';
|
||||||
|
import { ByondUi } from 'tgui-core/components';
|
||||||
|
|
||||||
import { useBackend } from '../../backend';
|
|
||||||
import { Box, Button, ByondUi, Flex, Icon, Section } from '../../components';
|
|
||||||
import { HOMETAB } from './constants';
|
import { HOMETAB } from './constants';
|
||||||
import { Data } from './types';
|
import { Data } from './types';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { useBackend } from '../backend';
|
import { useBackend } from 'tgui/backend';
|
||||||
import { Button, ByondUi } from '../components';
|
import { Button } from 'tgui/components';
|
||||||
import { NtosWindow } from '../layouts';
|
import { NtosWindow } from 'tgui/layouts';
|
||||||
|
import { ByondUi } from 'tgui-core/components';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
camera,
|
camera,
|
||||||
CameraConsoleContent,
|
CameraConsoleContent,
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import { useBackend } from '../backend';
|
|
||||||
import { ByondUi } from '../components';
|
|
||||||
import { Window } from '../layouts';
|
|
||||||
|
|
||||||
type Data = { mapRef: string };
|
|
||||||
|
|
||||||
export const StationBlueprints = (props) => {
|
|
||||||
return (
|
|
||||||
<Window width={870} height={708}>
|
|
||||||
<StationBlueprintsContent />
|
|
||||||
</Window>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const StationBlueprintsContent = (props) => {
|
|
||||||
const { data } = useBackend<Data>();
|
|
||||||
|
|
||||||
const { mapRef /* areas, turfs */ } = data;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="CameraConsole__left">
|
|
||||||
<Window.Content scrollable>Honk!</Window.Content>
|
|
||||||
</div>
|
|
||||||
<div className="CameraConsole__right">
|
|
||||||
<ByondUi
|
|
||||||
className="CameraConsole__map"
|
|
||||||
params={{
|
|
||||||
id: mapRef,
|
|
||||||
type: 'map',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-popper": "^2.3.0",
|
"react-popper": "^2.3.0",
|
||||||
|
"tgui-core": "^1.2.0",
|
||||||
"tgui-dev-server": "workspace:*",
|
"tgui-dev-server": "workspace:*",
|
||||||
"tgui-polyfill": "workspace:*"
|
"tgui-polyfill": "workspace:*"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file
|
|
||||||
* @copyright 2021 Aleksej Komarov
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Blink, Section } from '../components';
|
|
||||||
|
|
||||||
export const meta = {
|
|
||||||
title: 'Blink',
|
|
||||||
render: () => <Story />,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Story = (props) => {
|
|
||||||
return (
|
|
||||||
<Section>
|
|
||||||
<Blink>Blink</Blink>
|
|
||||||
</Section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file
|
|
||||||
* @copyright 2021 Aleksej Komarov
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { BlockQuote, Section } from '../components';
|
|
||||||
import { BoxWithSampleText } from './common';
|
|
||||||
|
|
||||||
export const meta = {
|
|
||||||
title: 'BlockQuote',
|
|
||||||
render: () => <Story />,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Story = (props) => {
|
|
||||||
return (
|
|
||||||
<Section>
|
|
||||||
<BlockQuote>
|
|
||||||
<BoxWithSampleText />
|
|
||||||
</BlockQuote>
|
|
||||||
</Section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -5,8 +5,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { ByondUi } from 'tgui-core/components';
|
||||||
|
|
||||||
import { Box, Button, ByondUi, Section } from '../components';
|
import { Box, Button, Section } from '../components';
|
||||||
import { logger } from '../logging';
|
import { logger } from '../logging';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2020 Aleksej Komarov
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../base.scss';
|
|
||||||
@use '../colors.scss';
|
|
||||||
|
|
||||||
$color-default: colors.fg(colors.$label) !default;
|
|
||||||
|
|
||||||
.BlockQuote {
|
|
||||||
color: $color-default;
|
|
||||||
border-left: base.em(2px) solid $color-default;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2020 Aleksej Komarov
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
@use '../base.scss';
|
|
||||||
|
|
||||||
$separator-color: base.$color-bg-section;
|
|
||||||
$background-color: base.$color-bg !default;
|
|
||||||
$dropdown-z-index: 5;
|
|
||||||
|
|
||||||
.MenuBar {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.MenuBar__font {
|
|
||||||
font-family: Verdana, sans-serif;
|
|
||||||
font-size: base.em(12px);
|
|
||||||
line-height: base.em(17px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.MenuBar__hover {
|
|
||||||
&:hover {
|
|
||||||
background-color: lighten($background-color, 30%);
|
|
||||||
transition: background-color 0ms;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.MenuBar__MenuBarButton {
|
|
||||||
padding: 0.2rem 0.5rem 0.2rem 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.MenuBar__menu {
|
|
||||||
position: absolute;
|
|
||||||
z-index: $dropdown-z-index;
|
|
||||||
background-color: $background-color;
|
|
||||||
padding: 0.3rem 0.3rem 0.3rem 0.3rem;
|
|
||||||
box-shadow: 4px 6px 5px -2px rgba(0, 0, 0, 0.55);
|
|
||||||
}
|
|
||||||
|
|
||||||
.MenuBar__MenuItem {
|
|
||||||
z-index: $dropdown-z-index;
|
|
||||||
transition: background-color 100ms ease-out;
|
|
||||||
background-color: $background-color;
|
|
||||||
white-space: nowrap;
|
|
||||||
padding: 0.3rem 2rem 0.3rem 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.MenuBar__MenuItemToggle {
|
|
||||||
padding: 0.3rem 2rem 0.3rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.MenuBar__MenuItemToggle__check {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
min-width: 3rem;
|
|
||||||
margin-left: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.MenuBar__over {
|
|
||||||
top: auto;
|
|
||||||
bottom: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.MenuBar__MenuBarButton-text {
|
|
||||||
text-overflow: clip;
|
|
||||||
white-space: nowrap;
|
|
||||||
height: base.em(17px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.MenuBar__Separator {
|
|
||||||
display: block;
|
|
||||||
margin: 0.3rem 0.3rem 0.3rem 2.3rem;
|
|
||||||
border-top: 1px solid $separator-color;
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
@include meta.load-css('./atomic/text.scss');
|
@include meta.load-css('./atomic/text.scss');
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
@include meta.load-css('./components/BlockQuote.scss');
|
|
||||||
@include meta.load-css('./components/Button.scss');
|
@include meta.load-css('./components/Button.scss');
|
||||||
@include meta.load-css('./components/ColorBox.scss');
|
@include meta.load-css('./components/ColorBox.scss');
|
||||||
@include meta.load-css('./components/Dialog.scss');
|
@include meta.load-css('./components/Dialog.scss');
|
||||||
@@ -32,13 +31,11 @@
|
|||||||
@include meta.load-css('./components/Input.scss');
|
@include meta.load-css('./components/Input.scss');
|
||||||
@include meta.load-css('./components/Knob.scss');
|
@include meta.load-css('./components/Knob.scss');
|
||||||
@include meta.load-css('./components/LabeledList.scss');
|
@include meta.load-css('./components/LabeledList.scss');
|
||||||
@include meta.load-css('./components/MenuBar.scss');
|
|
||||||
@include meta.load-css('./components/Modal.scss');
|
@include meta.load-css('./components/Modal.scss');
|
||||||
@include meta.load-css('./components/NanoMap.scss');
|
@include meta.load-css('./components/NanoMap.scss');
|
||||||
@include meta.load-css('./components/NoticeBox.scss');
|
@include meta.load-css('./components/NoticeBox.scss');
|
||||||
@include meta.load-css('./components/NumberInput.scss');
|
@include meta.load-css('./components/NumberInput.scss');
|
||||||
@include meta.load-css('./components/ProgressBar.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/Section.scss');
|
||||||
@include meta.load-css('./components/Slider.scss');
|
@include meta.load-css('./components/Slider.scss');
|
||||||
@include meta.load-css('./components/Stack.scss');
|
@include meta.load-css('./components/Stack.scss');
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ module.exports = (env = {}, argv) => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.scss$/,
|
test: /\.(s)?css$/,
|
||||||
use: [
|
use: [
|
||||||
{
|
{
|
||||||
loader: ExtractCssPlugin.loader,
|
loader: ExtractCssPlugin.loader,
|
||||||
|
|||||||
@@ -8236,6 +8236,16 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"tgui-core@npm:^1.2.0":
|
||||||
|
version: 1.2.0
|
||||||
|
resolution: "tgui-core@npm:1.2.0"
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.2.0
|
||||||
|
react-dom: ^18.2.0
|
||||||
|
checksum: 10c0/1eead0edbe0df5c49bfa88f0d2caa5df743057be75e9c526d0afd838b8def072c67fe60435c66cba52551c4ef70749d60d68094fa103542187008998002714f7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"tgui-dev-server@workspace:*, tgui-dev-server@workspace:packages/tgui-dev-server":
|
"tgui-dev-server@workspace:*, tgui-dev-server@workspace:packages/tgui-dev-server":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "tgui-dev-server@workspace:packages/tgui-dev-server"
|
resolution: "tgui-dev-server@workspace:packages/tgui-dev-server"
|
||||||
@@ -8344,6 +8354,7 @@ __metadata:
|
|||||||
react: "npm:^18.2.0"
|
react: "npm:^18.2.0"
|
||||||
react-dom: "npm:^18.2.0"
|
react-dom: "npm:^18.2.0"
|
||||||
react-popper: "npm:^2.3.0"
|
react-popper: "npm:^2.3.0"
|
||||||
|
tgui-core: "npm:^1.2.0"
|
||||||
tgui-dev-server: "workspace:*"
|
tgui-dev-server: "workspace:*"
|
||||||
tgui-polyfill: "workspace:*"
|
tgui-polyfill: "workspace:*"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
|
|||||||
Reference in New Issue
Block a user