actual tgui4
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { loadCSS as fgLoadCSS } from 'fg-loadcss';
|
||||
import { createLogger } from './logging';
|
||||
|
||||
const logger = createLogger('assets');
|
||||
|
||||
const EXCLUDED_PATTERNS = [
|
||||
/v4shim/i,
|
||||
];
|
||||
|
||||
const loadedStyles = [];
|
||||
const loadedMappings = {};
|
||||
|
||||
export const loadCSS = url => {
|
||||
if (loadedStyles.includes(url)) {
|
||||
return;
|
||||
}
|
||||
loadedStyles.push(url);
|
||||
logger.log(`loading stylesheet '${url}'`);
|
||||
fgLoadCSS(url);
|
||||
};
|
||||
|
||||
export const resolveAsset = name => (
|
||||
loadedMappings[name] || name
|
||||
);
|
||||
|
||||
export const assetMiddleware = store => next => action => {
|
||||
const { type, payload } = action;
|
||||
if (type === 'asset/stylesheet') {
|
||||
loadCSS(payload);
|
||||
return;
|
||||
}
|
||||
if (type === 'asset/mappings') {
|
||||
for (let name of Object.keys(payload)) {
|
||||
// Skip anything that matches excluded patterns
|
||||
if (EXCLUDED_PATTERNS.some(regex => regex.test(name))) {
|
||||
continue;
|
||||
}
|
||||
const url = payload[name];
|
||||
const ext = name.split('.').pop();
|
||||
loadedMappings[name] = url;
|
||||
if (ext === 'css') {
|
||||
loadCSS(url);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
next(action);
|
||||
};
|
||||
+194
-27
@@ -11,8 +11,12 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { perf } from 'common/perf';
|
||||
import { UI_DISABLED, UI_INTERACTIVE } from './constants';
|
||||
import { callByond } from './byond';
|
||||
import { releaseHeldKeys } from './hotkeys';
|
||||
import { createLogger } from './logging';
|
||||
|
||||
const logger = createLogger('backend');
|
||||
|
||||
export const backendUpdate = state => ({
|
||||
type: 'backend/update',
|
||||
@@ -24,7 +28,23 @@ export const backendSetSharedState = (key, nextState) => ({
|
||||
payload: { key, nextState },
|
||||
});
|
||||
|
||||
export const backendReducer = (state, action) => {
|
||||
export const backendSuspendStart = () => ({
|
||||
type: 'backend/suspendStart',
|
||||
});
|
||||
|
||||
export const backendSuspendSuccess = () => ({
|
||||
type: 'backend/suspendSuccess',
|
||||
payload: {
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
const initialState = {
|
||||
config: {},
|
||||
data: {},
|
||||
};
|
||||
|
||||
export const backendReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
if (type === 'backend/update') {
|
||||
@@ -63,6 +83,7 @@ export const backendReducer = (state, action) => {
|
||||
shared,
|
||||
visible,
|
||||
interactive,
|
||||
suspended: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,30 +98,175 @@ export const backendReducer = (state, action) => {
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'backend/suspendStart') {
|
||||
return {
|
||||
...state,
|
||||
suspending: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'backend/suspendSuccess') {
|
||||
const { timestamp } = payload;
|
||||
return {
|
||||
...state,
|
||||
data: {},
|
||||
shared: {},
|
||||
config: {
|
||||
...state.config,
|
||||
title: '',
|
||||
status: 1,
|
||||
},
|
||||
suspending: false,
|
||||
suspended: timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
export const backendMiddleware = store => {
|
||||
let fancyState;
|
||||
let suspendInterval;
|
||||
|
||||
return next => action => {
|
||||
const { config, suspended } = selectBackend(store.getState());
|
||||
const { type, payload } = action;
|
||||
|
||||
if (type === 'backend/suspendStart' && !suspendInterval) {
|
||||
logger.log(`suspending (${window.__windowId__})`);
|
||||
// Keep sending suspend messages until it succeeds.
|
||||
// It may fail multiple times due to topic rate limiting.
|
||||
const suspendFn = () => sendMessage({
|
||||
type: 'suspend',
|
||||
});
|
||||
suspendFn();
|
||||
suspendInterval = setInterval(suspendFn, 2000);
|
||||
}
|
||||
|
||||
if (type === 'backend/suspendSuccess') {
|
||||
clearInterval(suspendInterval);
|
||||
suspendInterval = undefined;
|
||||
releaseHeldKeys();
|
||||
Byond.winset(window.__windowId__, {
|
||||
'is-visible': false,
|
||||
});
|
||||
}
|
||||
|
||||
if (type === 'backend/update') {
|
||||
const fancy = payload.config?.window?.fancy;
|
||||
// Initialize fancy state
|
||||
if (fancyState === undefined) {
|
||||
fancyState = fancy;
|
||||
}
|
||||
// React to changes in fancy
|
||||
else if (fancyState !== fancy) {
|
||||
logger.log('changing fancy mode to', fancy);
|
||||
fancyState = fancy;
|
||||
Byond.winset(window.__windowId__, {
|
||||
titlebar: !fancy,
|
||||
'can-resize': !fancy,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'backend/update' && suspended) {
|
||||
// We schedule this for the next tick here because resizing and unhiding
|
||||
// during the same tick will flash with a white background.
|
||||
setImmediate(() => {
|
||||
perf.mark('resume/start');
|
||||
// Doublecheck if we are not re-suspended.
|
||||
const { suspended } = selectBackend(store.getState());
|
||||
if (suspended) {
|
||||
return;
|
||||
}
|
||||
Byond.winset(window.__windowId__, {
|
||||
'is-visible': true,
|
||||
});
|
||||
perf.mark('resume/finish');
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logger.log('visible in',
|
||||
perf.measure('render/finish', 'resume/finish'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return next(action);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a message to /datum/tgui_window.
|
||||
*/
|
||||
export const sendMessage = (message = {}) => {
|
||||
const { payload, ...rest } = message;
|
||||
const data = {
|
||||
// Message identifying header
|
||||
tgui: 1,
|
||||
window_id: window.__windowId__,
|
||||
// Message body
|
||||
...rest,
|
||||
};
|
||||
// JSON-encode the payload
|
||||
if (payload !== null && payload !== undefined) {
|
||||
data.payload = JSON.stringify(payload);
|
||||
}
|
||||
Byond.topic(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends an action to `ui_act` on `src_object` that this tgui window
|
||||
* is associated with.
|
||||
*/
|
||||
export const sendAct = (action, payload = {}) => {
|
||||
// Validate that payload is an object
|
||||
const isObject = typeof payload === 'object'
|
||||
&& payload !== null
|
||||
&& !Array.isArray(payload);
|
||||
if (!isObject) {
|
||||
logger.error(`Payload for act() must be an object, got this:`, payload);
|
||||
return;
|
||||
}
|
||||
sendMessage({
|
||||
type: 'act/' + action,
|
||||
payload,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef BackendState
|
||||
* @type {{
|
||||
* config: {
|
||||
* title: string,
|
||||
* status: number,
|
||||
* screen: string,
|
||||
* style: string,
|
||||
* interface: string,
|
||||
* fancy: number,
|
||||
* locked: number,
|
||||
* observer: number,
|
||||
* window: string,
|
||||
* ref: string,
|
||||
* user: {
|
||||
* name: string,
|
||||
* ckey: string,
|
||||
* observer: number,
|
||||
* },
|
||||
* window: {
|
||||
* key: string,
|
||||
* size: [number, number],
|
||||
* fancy: boolean,
|
||||
* locked: boolean,
|
||||
* },
|
||||
* },
|
||||
* data: any,
|
||||
* shared: any,
|
||||
* visible: boolean,
|
||||
* interactive: boolean,
|
||||
* suspending: boolean,
|
||||
* suspended: boolean,
|
||||
* }}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Selects a backend-related slice of Redux state
|
||||
*
|
||||
* @return {BackendState}
|
||||
*/
|
||||
export const selectBackend = state => state.backend || {};
|
||||
|
||||
/**
|
||||
* A React hook (sort of) for getting tgui state and related functions.
|
||||
*
|
||||
@@ -108,21 +274,16 @@ export const backendReducer = (state, action) => {
|
||||
* be used in functional components.
|
||||
*
|
||||
* @return {BackendState & {
|
||||
* act: (action: string, params?: object) => void,
|
||||
* act: sendAct,
|
||||
* }}
|
||||
*/
|
||||
export const useBackend = context => {
|
||||
const { store } = context;
|
||||
const state = store.getState();
|
||||
const ref = state.config.ref;
|
||||
const act = (action, params = {}) => {
|
||||
callByond('', {
|
||||
src: ref,
|
||||
action,
|
||||
...params,
|
||||
});
|
||||
const state = selectBackend(store.getState());
|
||||
return {
|
||||
...state,
|
||||
act: sendAct,
|
||||
};
|
||||
return { ...state, act };
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -140,7 +301,7 @@ export const useBackend = context => {
|
||||
*/
|
||||
export const useLocalState = (context, key, initialState) => {
|
||||
const { store } = context;
|
||||
const state = store.getState();
|
||||
const state = selectBackend(store.getState());
|
||||
const sharedStates = state.shared ?? {};
|
||||
const sharedState = (key in sharedStates)
|
||||
? sharedStates[key]
|
||||
@@ -148,7 +309,11 @@ export const useLocalState = (context, key, initialState) => {
|
||||
return [
|
||||
sharedState,
|
||||
nextState => {
|
||||
store.dispatch(backendSetSharedState(key, nextState));
|
||||
store.dispatch(backendSetSharedState(key, (
|
||||
typeof nextState === 'function'
|
||||
? nextState(sharedState)
|
||||
: nextState
|
||||
)));
|
||||
},
|
||||
];
|
||||
};
|
||||
@@ -169,8 +334,7 @@ export const useLocalState = (context, key, initialState) => {
|
||||
*/
|
||||
export const useSharedState = (context, key, initialState) => {
|
||||
const { store } = context;
|
||||
const state = store.getState();
|
||||
const ref = state.config.ref;
|
||||
const state = selectBackend(store.getState());
|
||||
const sharedStates = state.shared ?? {};
|
||||
const sharedState = (key in sharedStates)
|
||||
? sharedStates[key]
|
||||
@@ -178,11 +342,14 @@ export const useSharedState = (context, key, initialState) => {
|
||||
return [
|
||||
sharedState,
|
||||
nextState => {
|
||||
callByond('', {
|
||||
src: ref,
|
||||
action: 'tgui:setSharedState',
|
||||
sendMessage({
|
||||
type: 'setSharedState',
|
||||
key,
|
||||
value: JSON.stringify(nextState) || '',
|
||||
value: JSON.stringify(
|
||||
typeof nextState === 'function'
|
||||
? nextState(sharedState)
|
||||
: nextState
|
||||
) || '',
|
||||
});
|
||||
},
|
||||
];
|
||||
|
||||
@@ -9,17 +9,22 @@ import { createVNode } from 'inferno';
|
||||
import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags';
|
||||
import { CSS_COLORS } from '../constants';
|
||||
|
||||
const UNIT_PX = 12;
|
||||
|
||||
/**
|
||||
* Coverts our rem-like spacing unit into a CSS unit.
|
||||
*/
|
||||
export const unit = value => {
|
||||
if (typeof value === 'string') {
|
||||
// Transparently convert pixels into rem units
|
||||
if (value.endsWith('px') && !Byond.IS_LTE_IE8) {
|
||||
return parseFloat(value) / 12 + 'rem';
|
||||
}
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return (value * UNIT_PX) + 'px';
|
||||
if (Byond.IS_LTE_IE8) {
|
||||
return value * 12 + 'px';
|
||||
}
|
||||
return value + 'rem';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -28,10 +33,10 @@ export const unit = value => {
|
||||
*/
|
||||
export const halfUnit = value => {
|
||||
if (typeof value === 'string') {
|
||||
return value;
|
||||
return unit(value);
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return (value * UNIT_PX * 0.5) + 'px';
|
||||
return unit(value * 0.5);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -90,7 +95,13 @@ const styleMapperByPropName = {
|
||||
maxHeight: mapUnitPropTo('max-height', unit),
|
||||
fontSize: mapUnitPropTo('font-size', unit),
|
||||
fontFamily: mapRawPropTo('font-family'),
|
||||
lineHeight: mapRawPropTo('line-height'),
|
||||
lineHeight: (style, value) => {
|
||||
if (!isFalsy(value)) {
|
||||
style['line-height'] = typeof value === 'number'
|
||||
? value
|
||||
: unit(value);
|
||||
}
|
||||
},
|
||||
opacity: mapRawPropTo('opacity'),
|
||||
textAlign: mapRawPropTo('text-align'),
|
||||
verticalAlign: mapRawPropTo('vertical-align'),
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import { classes, pureComponentHooks } from 'common/react';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { IS_IE8 } from '../byond';
|
||||
import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from '../hotkeys';
|
||||
import { refocusLayout } from '../layouts';
|
||||
import { createLogger } from '../logging';
|
||||
@@ -61,7 +60,7 @@ export const Button = props => {
|
||||
className,
|
||||
])}
|
||||
tabIndex={!disabled && '0'}
|
||||
unselectable={IS_IE8}
|
||||
unselectable={Byond.IS_LTE_IE8}
|
||||
onclick={e => {
|
||||
refocusLayout();
|
||||
if (!disabled && onClick) {
|
||||
@@ -87,7 +86,10 @@ export const Button = props => {
|
||||
}}
|
||||
{...rest}>
|
||||
{icon && (
|
||||
<Icon name={icon} rotation={iconRotation} spin={iconSpin} />
|
||||
<Icon
|
||||
name={icon}
|
||||
rotation={iconRotation}
|
||||
spin={iconSpin} />
|
||||
)}
|
||||
{content}
|
||||
{children}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import { shallowDiffers } from 'common/react';
|
||||
import { debounce } from 'common/timer';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { callByond, IS_IE8 } from '../byond';
|
||||
import { createLogger } from '../logging';
|
||||
import { computeBoxProps } from './Box';
|
||||
|
||||
@@ -28,16 +27,12 @@ const createByondUiElement = elementId => {
|
||||
render: params => {
|
||||
logger.log(`rendering '${id}'`);
|
||||
byondUiStack[index] = id;
|
||||
callByond('winset', {
|
||||
...params,
|
||||
id,
|
||||
});
|
||||
Byond.winset(id, params);
|
||||
},
|
||||
unmount: () => {
|
||||
logger.log(`unmounting '${id}'`);
|
||||
byondUiStack[index] = null;
|
||||
callByond('winset', {
|
||||
id,
|
||||
Byond.winset(id, {
|
||||
parent: '',
|
||||
});
|
||||
},
|
||||
@@ -51,8 +46,7 @@ window.addEventListener('beforeunload', () => {
|
||||
if (typeof id === 'string') {
|
||||
logger.log(`unmounting '${id}' (beforeunload)`);
|
||||
byondUiStack[index] = null;
|
||||
callByond('winset', {
|
||||
id,
|
||||
Byond.winset(id, {
|
||||
parent: '',
|
||||
});
|
||||
}
|
||||
@@ -83,7 +77,7 @@ export class ByondUi extends Component {
|
||||
this.byondUiElement = createByondUiElement(props.params?.id);
|
||||
this.handleResize = debounce(() => {
|
||||
this.forceUpdate();
|
||||
}, 500);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
@@ -101,16 +95,17 @@ export class ByondUi extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
// IE8: It probably works, but fuck you anyway.
|
||||
if (IS_IE8) {
|
||||
if (Byond.IS_LTE_IE10) {
|
||||
return;
|
||||
}
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
return this.componentDidUpdate();
|
||||
this.componentDidUpdate();
|
||||
this.handleResize();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
// IE8: It probably works, but fuck you anyway.
|
||||
if (IS_IE8) {
|
||||
if (Byond.IS_LTE_IE10) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
@@ -119,6 +114,7 @@ export class ByondUi extends Component {
|
||||
const box = getBoundingBox(this.containerRef.current);
|
||||
logger.log('bounding box', box);
|
||||
this.byondUiElement.render({
|
||||
parent: window.__windowId__,
|
||||
...params,
|
||||
pos: box.pos[0] + ',' + box.pos[1],
|
||||
size: box.size[0] + 'x' + box.size[1],
|
||||
@@ -127,7 +123,7 @@ export class ByondUi extends Component {
|
||||
|
||||
componentWillUnmount() {
|
||||
// IE8: It probably works, but fuck you anyway.
|
||||
if (IS_IE8) {
|
||||
if (Byond.IS_LTE_IE10) {
|
||||
return;
|
||||
}
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
@@ -135,26 +131,16 @@ export class ByondUi extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
parent,
|
||||
params,
|
||||
...rest
|
||||
} = this.props;
|
||||
const { params, ...rest } = this.props;
|
||||
const type = params?.type;
|
||||
const boxProps = computeBoxProps(rest);
|
||||
return (
|
||||
<div
|
||||
ref={this.containerRef}
|
||||
{...boxProps}>
|
||||
{type === 'button' && <ButtonMock />}
|
||||
{/* Filler */}
|
||||
<div style={{ 'min-height': '22px' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ButtonMock = () => (
|
||||
<div
|
||||
style={{
|
||||
'min-height': '22px',
|
||||
}} />
|
||||
);
|
||||
|
||||
+1
-2
@@ -7,7 +7,6 @@
|
||||
import { map, zipWith } from 'common/collections';
|
||||
import { pureComponentHooks } from 'common/react';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { IS_IE8 } from '../byond';
|
||||
import { Box } from './Box';
|
||||
|
||||
const normalizeData = (data, scale, rangeX, rangeY) => {
|
||||
@@ -123,5 +122,5 @@ const Stub = props => null;
|
||||
|
||||
// IE8: No inline svg support
|
||||
export const Chart = {
|
||||
Line: IS_IE8 ? Stub : LineChart,
|
||||
Line: Byond.IS_LTE_IE8 ? Stub : LineChart,
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
import { classes, pureComponentHooks } from 'common/react';
|
||||
import { IS_IE8 } from '../byond';
|
||||
import { Box, unit } from './Box';
|
||||
|
||||
export const computeFlexProps = props => {
|
||||
@@ -22,10 +21,10 @@ export const computeFlexProps = props => {
|
||||
return {
|
||||
className: classes([
|
||||
'Flex',
|
||||
IS_IE8 && (
|
||||
Byond.IS_LTE_IE10 && (
|
||||
direction === 'column'
|
||||
? 'Flex--ie8--column'
|
||||
: 'Flex--ie8'
|
||||
? 'Flex--iefix--column'
|
||||
: 'Flex--iefix'
|
||||
),
|
||||
inline && 'Flex--inline',
|
||||
spacing > 0 && 'Flex--spacing--' + spacing,
|
||||
@@ -63,7 +62,7 @@ export const computeFlexItemProps = props => {
|
||||
return {
|
||||
className: classes([
|
||||
'Flex__item',
|
||||
IS_IE8 && 'Flex__item--ie8',
|
||||
Byond.IS_LTE_IE10 && 'Flex__item--iefix',
|
||||
className,
|
||||
]),
|
||||
style: {
|
||||
|
||||
@@ -83,6 +83,10 @@ export class Input extends Component {
|
||||
if (input) {
|
||||
input.value = toInputValue(nextValue);
|
||||
}
|
||||
|
||||
if (this.props.autoFocus) {
|
||||
setTimeout(() => input.focus(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import { keyOfMatchingRange, scale } from 'common/math';
|
||||
import { classes } from 'common/react';
|
||||
import { IS_IE8 } from '../byond';
|
||||
import { computeBoxClassName, computeBoxProps } from './Box';
|
||||
import { DraggableControl } from './DraggableControl';
|
||||
import { NumberInput } from './NumberInput';
|
||||
@@ -14,7 +13,7 @@ import { NumberInput } from './NumberInput';
|
||||
export const Knob = props => {
|
||||
// IE8: I don't want to support a yet another component on IE8.
|
||||
// IE8: It also can't handle SVG.
|
||||
if (IS_IE8) {
|
||||
if (Byond.IS_LTE_IE8) {
|
||||
return (
|
||||
<NumberInput {...props} />
|
||||
);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import { clamp } from 'common/math';
|
||||
import { classes, pureComponentHooks } from 'common/react';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { IS_IE8 } from '../byond';
|
||||
import { AnimatedNumber } from './AnimatedNumber';
|
||||
import { Box } from './Box';
|
||||
|
||||
@@ -168,7 +167,7 @@ export class NumberInput extends Component {
|
||||
const renderContentElement = value => (
|
||||
<div
|
||||
className="NumberInput__content"
|
||||
unselectable={IS_IE8}>
|
||||
unselectable={Byond.IS_LTE_IE8}>
|
||||
{value + (unit ? ' ' + unit : '')}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { classes, isFalsy, pureComponentHooks } from 'common/react';
|
||||
import { Box } from './Box';
|
||||
import { computeBoxClassName, computeBoxProps } from './Box';
|
||||
|
||||
export const Section = props => {
|
||||
const {
|
||||
@@ -13,20 +13,22 @@ export const Section = props => {
|
||||
title,
|
||||
level = 1,
|
||||
buttons,
|
||||
content,
|
||||
fill,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
const hasTitle = !isFalsy(title) || !isFalsy(buttons);
|
||||
const hasContent = !isFalsy(content) || !isFalsy(children);
|
||||
const hasContent = !isFalsy(children);
|
||||
return (
|
||||
<Box
|
||||
<div
|
||||
className={classes([
|
||||
'Section',
|
||||
'Section--level--' + level,
|
||||
fill && 'Section--fill',
|
||||
className,
|
||||
...computeBoxClassName(rest),
|
||||
])}
|
||||
{...rest}>
|
||||
{...computeBoxProps(rest)}>
|
||||
{hasTitle && (
|
||||
<div className="Section__title">
|
||||
<span className="Section__titleText">
|
||||
@@ -39,11 +41,10 @@ export const Section = props => {
|
||||
)}
|
||||
{hasContent && (
|
||||
<div className="Section__content">
|
||||
{content}
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,14 +6,13 @@
|
||||
|
||||
import { clamp01, keyOfMatchingRange, scale } from 'common/math';
|
||||
import { classes } from 'common/react';
|
||||
import { IS_IE8 } from '../byond';
|
||||
import { computeBoxClassName, computeBoxProps } from './Box';
|
||||
import { DraggableControl } from './DraggableControl';
|
||||
import { NumberInput } from './NumberInput';
|
||||
|
||||
export const Slider = props => {
|
||||
// IE8: I don't want to support a yet another component on IE8.
|
||||
if (IS_IE8) {
|
||||
if (Byond.IS_LTE_IE8) {
|
||||
return (
|
||||
<NumberInput {...props} />
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ export { Collapsible } from './Collapsible';
|
||||
export { ColorBox } from './ColorBox';
|
||||
export { Dimmer } from './Dimmer';
|
||||
export { Divider } from './Divider';
|
||||
export { DraggableControl } from './DraggableControl';
|
||||
export { Dropdown } from './Dropdown';
|
||||
export { Flex } from './Flex';
|
||||
export { Grid } from './Grid';
|
||||
@@ -29,7 +30,7 @@ export { ProgressBar } from './ProgressBar';
|
||||
export { Section } from './Section';
|
||||
export { Slider } from './Slider';
|
||||
export { Table } from './Table';
|
||||
export { TextArea } from './TextArea';
|
||||
export { Tabs } from './Tabs';
|
||||
export { Tooltip } from './Tooltip';
|
||||
export { TimeDisplay } from './TimeDisplay';
|
||||
// export { TextArea } from './TextArea'; PAPER
|
||||
|
||||
@@ -0,0 +1,608 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { BlockQuote, Box, Button, ByondUi, Collapsible, DraggableControl, Flex, Icon, Input, Knob, LabeledList, NoticeBox, NumberInput, ProgressBar, Section, Slider, Tabs, Tooltip } from '../components';
|
||||
import { formatSiUnit } from '../format';
|
||||
import { Window } from '../layouts';
|
||||
import { createLogger } from '../logging';
|
||||
|
||||
const logger = createLogger('KitchenSink');
|
||||
|
||||
const COLORS_SPECTRUM = [
|
||||
'red',
|
||||
'orange',
|
||||
'yellow',
|
||||
'olive',
|
||||
'green',
|
||||
'teal',
|
||||
'blue',
|
||||
'violet',
|
||||
'purple',
|
||||
'pink',
|
||||
'brown',
|
||||
'grey',
|
||||
];
|
||||
|
||||
const COLORS_STATES = [
|
||||
'good',
|
||||
'average',
|
||||
'bad',
|
||||
'black',
|
||||
'white',
|
||||
];
|
||||
|
||||
const PAGES = [
|
||||
{
|
||||
title: 'Button',
|
||||
component: () => KitchenSinkButton,
|
||||
},
|
||||
{
|
||||
title: 'Box',
|
||||
component: () => KitchenSinkBox,
|
||||
},
|
||||
{
|
||||
title: 'Flex & Sections',
|
||||
component: () => KitchenSinkFlexAndSections,
|
||||
},
|
||||
{
|
||||
title: 'ProgressBar',
|
||||
component: () => KitchenSinkProgressBar,
|
||||
},
|
||||
{
|
||||
title: 'Tabs',
|
||||
component: () => KitchenSinkTabs,
|
||||
},
|
||||
{
|
||||
title: 'Tooltip',
|
||||
component: () => KitchenSinkTooltip,
|
||||
},
|
||||
{
|
||||
title: 'Input / Control',
|
||||
component: () => KitchenSinkInput,
|
||||
},
|
||||
{
|
||||
title: 'Collapsible',
|
||||
component: () => KitchenSinkCollapsible,
|
||||
},
|
||||
{
|
||||
title: 'BlockQuote',
|
||||
component: () => KitchenSinkBlockQuote,
|
||||
},
|
||||
{
|
||||
title: 'ByondUi',
|
||||
component: () => KitchenSinkByondUi,
|
||||
},
|
||||
{
|
||||
title: 'Themes',
|
||||
component: () => KitchenSinkThemes,
|
||||
},
|
||||
{
|
||||
title: 'Storage',
|
||||
component: () => KitchenSinkStorage,
|
||||
},
|
||||
];
|
||||
|
||||
export const KitchenSink = (props, context) => {
|
||||
const [theme] = useLocalState(context, 'kitchenSinkTheme');
|
||||
const [pageIndex, setPageIndex] = useLocalState(context, 'pageIndex', 0);
|
||||
const PageComponent = PAGES[pageIndex].component();
|
||||
return (
|
||||
<Window
|
||||
title="Kitchen Sink"
|
||||
width={600}
|
||||
height={500}
|
||||
theme={theme}
|
||||
resizable>
|
||||
<Flex height="100%">
|
||||
<Flex.Item m={1} mr={0}>
|
||||
<Section fill>
|
||||
{PAGES.map((page, i) => (
|
||||
<Button
|
||||
key={i}
|
||||
fluid
|
||||
color="transparent"
|
||||
selected={i === pageIndex}
|
||||
onClick={() => setPageIndex(i)}>
|
||||
{page.title}
|
||||
</Button>
|
||||
))}
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
position="relative"
|
||||
grow={1}>
|
||||
<Window.Content scrollable>
|
||||
<PageComponent />
|
||||
</Window.Content>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkButton = props => {
|
||||
return (
|
||||
<Section>
|
||||
<Box mb={1}>
|
||||
<Button content="Simple" />
|
||||
<Button selected content="Selected" />
|
||||
<Button altSelected content="Alt Selected" />
|
||||
<Button disabled content="Disabled" />
|
||||
<Button color="transparent" content="Transparent" />
|
||||
<Button icon="cog" content="Icon" />
|
||||
<Button icon="power-off" />
|
||||
<Button fluid content="Fluid" />
|
||||
<Button
|
||||
my={1}
|
||||
lineHeight={2}
|
||||
minWidth={15}
|
||||
textAlign="center"
|
||||
content="With Box props" />
|
||||
</Box>
|
||||
<Box mb={1}>
|
||||
{COLORS_STATES.map(color => (
|
||||
<Button
|
||||
key={color}
|
||||
color={color}
|
||||
content={color} />
|
||||
))}
|
||||
<br />
|
||||
{COLORS_SPECTRUM.map(color => (
|
||||
<Button
|
||||
key={color}
|
||||
color={color}
|
||||
content={color} />
|
||||
))}
|
||||
<br />
|
||||
{COLORS_SPECTRUM.map(color => (
|
||||
<Box inline
|
||||
mx="7px"
|
||||
key={color}
|
||||
color={color}>
|
||||
{color}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkBox = props => {
|
||||
return (
|
||||
<Section>
|
||||
<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>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkFlexAndSections = (props, context) => {
|
||||
const [grow, setGrow] = useLocalState(
|
||||
context, 'fs_grow', 1);
|
||||
const [direction, setDirection] = useLocalState(
|
||||
context, 'fs_direction', 'column');
|
||||
const [fill, setFill] = useLocalState(
|
||||
context, 'fs_fill', true);
|
||||
const [hasTitle, setHasTitle] = useLocalState(
|
||||
context, 'fs_title', true);
|
||||
return (
|
||||
<Flex
|
||||
height="100%"
|
||||
direction="column">
|
||||
<Flex.Item mb={1}>
|
||||
<Section>
|
||||
<Button
|
||||
fluid
|
||||
onClick={() => setDirection(
|
||||
direction === 'column' ? 'row' : 'column'
|
||||
)}>
|
||||
{`Flex direction="${direction}"`}
|
||||
</Button>
|
||||
<Button
|
||||
fluid
|
||||
onClick={() => setGrow(Number(!grow))}>
|
||||
{`Flex.Item grow={${grow}}`}
|
||||
</Button>
|
||||
<Button
|
||||
fluid
|
||||
onClick={() => setFill(!fill)}>
|
||||
{`Section fill={${String(fill)}}`}
|
||||
</Button>
|
||||
<Button
|
||||
fluid
|
||||
selected={hasTitle}
|
||||
onClick={() => setHasTitle(!hasTitle)}>
|
||||
{`Section title`}
|
||||
</Button>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>
|
||||
<Flex
|
||||
height="100%"
|
||||
direction={direction}>
|
||||
<Flex.Item
|
||||
mr={direction === 'row' && 1}
|
||||
mb={direction === 'column' && 1}
|
||||
grow={grow}>
|
||||
<Section
|
||||
title={hasTitle && "Section 1"}
|
||||
fill={fill}>
|
||||
Content
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={grow}>
|
||||
<Section
|
||||
title={hasTitle && "Section 2"}
|
||||
fill={fill}>
|
||||
Content
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkProgressBar = (props, context) => {
|
||||
const [
|
||||
progress,
|
||||
setProgress,
|
||||
] = useLocalState(context, 'progress', 0.5);
|
||||
return (
|
||||
<Section>
|
||||
<ProgressBar
|
||||
ranges={{
|
||||
good: [0.5, Infinity],
|
||||
bad: [-Infinity, 0.1],
|
||||
average: [0, 0.5],
|
||||
}}
|
||||
minValue={-1}
|
||||
maxValue={1}
|
||||
value={progress}>
|
||||
Value: {Number(progress).toFixed(1)}
|
||||
</ProgressBar>
|
||||
<Box mt={1}>
|
||||
<Button
|
||||
content="-0.1"
|
||||
onClick={() => setProgress(progress - 0.1)} />
|
||||
<Button
|
||||
content="+0.1"
|
||||
onClick={() => setProgress(progress + 0.1)} />
|
||||
</Box>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkTabs = (props, context) => {
|
||||
const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0);
|
||||
const [vertical, setVertical] = useLocalState(context, 'tabVert');
|
||||
const [altSelection, setAltSelection] = useLocalState(context, 'tabAlt');
|
||||
const TAB_RANGE = [1, 2, 3, 4, 5];
|
||||
return (
|
||||
<Section>
|
||||
<Box mb={2}>
|
||||
<Button.Checkbox
|
||||
inline
|
||||
content="vertical"
|
||||
checked={vertical}
|
||||
onClick={() => setVertical(!vertical)} />
|
||||
<Button.Checkbox
|
||||
inline
|
||||
content="altSelection"
|
||||
checked={altSelection}
|
||||
onClick={() => setAltSelection(!altSelection)} />
|
||||
</Box>
|
||||
<Tabs vertical={vertical}>
|
||||
{TAB_RANGE.map((number, i) => (
|
||||
<Tabs.Tab
|
||||
key={i}
|
||||
altSelection={altSelection}
|
||||
selected={i === tabIndex}
|
||||
onClick={() => setTabIndex(i)}>
|
||||
Tab #{number}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkTooltip = props => {
|
||||
const positions = [
|
||||
'top',
|
||||
'left',
|
||||
'right',
|
||||
'bottom',
|
||||
'bottom-left',
|
||||
'bottom-right',
|
||||
];
|
||||
return (
|
||||
<Section>
|
||||
<Box>
|
||||
<Box inline position="relative" mr={1}>
|
||||
Box (hover me).
|
||||
<Tooltip content="Tooltip text." />
|
||||
</Box>
|
||||
<Button
|
||||
tooltip="Tooltip text."
|
||||
content="Button" />
|
||||
</Box>
|
||||
<Box mt={1}>
|
||||
{positions.map(position => (
|
||||
<Button
|
||||
key={position}
|
||||
color="transparent"
|
||||
tooltip="Tooltip text."
|
||||
tooltipPosition={position}
|
||||
content={position} />
|
||||
))}
|
||||
</Box>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkInput = (props, context) => {
|
||||
const [
|
||||
number,
|
||||
setNumber,
|
||||
] = useLocalState(context, 'number', 0);
|
||||
const [
|
||||
text,
|
||||
setText,
|
||||
] = useLocalState(context, 'text', "Sample text");
|
||||
return (
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Input (onChange)">
|
||||
<Input
|
||||
value={text}
|
||||
onChange={(e, value) => setText(value)} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Input (onInput)">
|
||||
<Input
|
||||
value={text}
|
||||
onInput={(e, value) => setText(value)} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="NumberInput (onChange)">
|
||||
<NumberInput
|
||||
animated
|
||||
width="40px"
|
||||
step={1}
|
||||
stepPixelSize={5}
|
||||
value={number}
|
||||
minValue={-100}
|
||||
maxValue={100}
|
||||
onChange={(e, value) => setNumber(value)} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="NumberInput (onDrag)">
|
||||
<NumberInput
|
||||
animated
|
||||
width="40px"
|
||||
step={1}
|
||||
stepPixelSize={5}
|
||||
value={number}
|
||||
minValue={-100}
|
||||
maxValue={100}
|
||||
onDrag={(e, value) => setNumber(value)} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Slider (onDrag)">
|
||||
<Slider
|
||||
step={1}
|
||||
stepPixelSize={5}
|
||||
value={number}
|
||||
minValue={-100}
|
||||
maxValue={100}
|
||||
onDrag={(e, value) => setNumber(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) => setNumber(value)} />
|
||||
<Knob
|
||||
ml={1}
|
||||
inline
|
||||
bipolar
|
||||
size={1}
|
||||
step={1}
|
||||
stepPixelSize={2}
|
||||
value={number}
|
||||
minValue={-100}
|
||||
maxValue={100}
|
||||
onDrag={(e, value) => setNumber(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) => setNumber(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>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkCollapsible = props => {
|
||||
return (
|
||||
<Section>
|
||||
<Collapsible
|
||||
title="Collapsible Demo"
|
||||
buttons={(
|
||||
<Button icon="cog" />
|
||||
)}>
|
||||
<BoxWithSampleText />
|
||||
</Collapsible>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const BoxWithSampleText = props => {
|
||||
return (
|
||||
<Box {...props}>
|
||||
<Box italic>
|
||||
Jackdaws love my big sphinx of quartz.
|
||||
</Box>
|
||||
<Box mt={1} bold>
|
||||
The wide electrification of the southern
|
||||
provinces will give a powerful impetus to the
|
||||
growth of agriculture.
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkBlockQuote = props => {
|
||||
return (
|
||||
<Section>
|
||||
<BlockQuote>
|
||||
<BoxWithSampleText />
|
||||
</BlockQuote>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkByondUi = (props, context) => {
|
||||
const { config } = useBackend(context);
|
||||
const [code, setCode] = useLocalState(context,
|
||||
'byondUiEvalCode',
|
||||
`Byond.winset('${window.__windowId__}', {\n 'is-visible': true,\n})`);
|
||||
return (
|
||||
<Fragment>
|
||||
<Section title="Button">
|
||||
<ByondUi
|
||||
params={{
|
||||
type: 'button',
|
||||
text: 'Button',
|
||||
}} />
|
||||
</Section>
|
||||
<Section
|
||||
title="Make BYOND calls"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="chevron-right"
|
||||
onClick={() => setImmediate(() => {
|
||||
try {
|
||||
const result = new Function('return (' + code + ')')();
|
||||
if (result && result.then) {
|
||||
logger.log('Promise');
|
||||
result.then(logger.log);
|
||||
}
|
||||
else {
|
||||
logger.log(result);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
logger.log(err);
|
||||
}
|
||||
})}>
|
||||
Evaluate
|
||||
</Button>
|
||||
)}>
|
||||
<Box
|
||||
as="textarea"
|
||||
width="100%"
|
||||
height="10em"
|
||||
onChange={e => setCode(e.target.value)}>
|
||||
{code}
|
||||
</Box>
|
||||
</Section>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkThemes = (props, context) => {
|
||||
const [theme, setTheme] = useLocalState(context, 'kitchenSinkTheme');
|
||||
return (
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Use theme">
|
||||
<Input
|
||||
placeholder="theme_name"
|
||||
value={theme}
|
||||
onInput={(e, value) => setTheme(value)} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const KitchenSinkStorage = (props, context) => {
|
||||
if (!window.localStorage) {
|
||||
return (
|
||||
<NoticeBox>
|
||||
Local storage is not available.
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Section
|
||||
title="Local Storage"
|
||||
buttons={(
|
||||
<Button
|
||||
icon="recycle"
|
||||
onClick={() => {
|
||||
localStorage.clear();
|
||||
}}>
|
||||
Clear
|
||||
</Button>
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Keys in use">
|
||||
{localStorage.length}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Remaining space">
|
||||
{formatSiUnit(localStorage.remainingSpace, 0, 'B')}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { subscribeToHotKey } from '../hotkeys';
|
||||
|
||||
export const toggleKitchenSink = () => ({
|
||||
type: 'debug/toggleKitchenSink',
|
||||
});
|
||||
|
||||
export const toggleDebugLayout = () => ({
|
||||
type: 'debug/toggleDebugLayout',
|
||||
});
|
||||
|
||||
subscribeToHotKey('F11', () => toggleDebugLayout());
|
||||
subscribeToHotKey('F12', () => toggleKitchenSink());
|
||||
subscribeToHotKey('Ctrl+Alt+[8]', () => {
|
||||
// NOTE: We need to call this in a timeout, because we need a clean
|
||||
// stack in order for this to be a fatal error.
|
||||
setTimeout(() => {
|
||||
throw new Error(
|
||||
'OOPSIE WOOPSIE!! UwU We made a fucky wucky!! A wittle'
|
||||
+ ' fucko boingo! The code monkeys at our headquarters are'
|
||||
+ ' working VEWY HAWD to fix this!');
|
||||
});
|
||||
});
|
||||
|
||||
export const selectDebug = state => state.debug;
|
||||
|
||||
export const useDebug = context => selectDebug(context.store.getState());
|
||||
|
||||
export const debugReducer = (state = {}, action) => {
|
||||
const { type, payload } = action;
|
||||
if (type === 'debug/toggleKitchenSink') {
|
||||
return {
|
||||
...state,
|
||||
kitchenSink: !state.kitchenSink,
|
||||
};
|
||||
}
|
||||
if (type === 'debug/toggleDebugLayout') {
|
||||
return {
|
||||
...state,
|
||||
debugLayout: !state.debugLayout,
|
||||
};
|
||||
}
|
||||
return state;
|
||||
};
|
||||
+144
-51
@@ -4,79 +4,171 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { vecAdd, vecInverse, vecMultiply } from 'common/vector';
|
||||
import { winget, winset } from './byond';
|
||||
import { storage } from 'common/storage';
|
||||
import { vecAdd, vecInverse, vecMultiply, vecScale } from 'common/vector';
|
||||
import { createLogger } from './logging';
|
||||
|
||||
const logger = createLogger('drag');
|
||||
|
||||
let ref;
|
||||
let windowKey = window.__windowId__;
|
||||
let dragging = false;
|
||||
let resizing = false;
|
||||
let screenOffset = [0, 0];
|
||||
let screenOffsetPromise;
|
||||
let dragPointOffset;
|
||||
let resizeMatrix;
|
||||
let initialSize;
|
||||
let size;
|
||||
|
||||
const getWindowPosition = ref => {
|
||||
return winget(ref, 'pos').then(pos => [pos.x, pos.y]);
|
||||
export const setWindowKey = key => {
|
||||
windowKey = key;
|
||||
};
|
||||
|
||||
const setWindowPosition = (ref, vec) => {
|
||||
return winset(ref, 'pos', vec[0] + ',' + vec[1]);
|
||||
export const getWindowPosition = () => [
|
||||
window.screenLeft,
|
||||
window.screenTop,
|
||||
];
|
||||
|
||||
export const getWindowSize = () => [
|
||||
window.innerWidth,
|
||||
window.innerHeight,
|
||||
];
|
||||
|
||||
export const setWindowPosition = vec => {
|
||||
const byondPos = vecAdd(vec, screenOffset);
|
||||
return Byond.winset(window.__windowId__, {
|
||||
pos: byondPos[0] + ',' + byondPos[1],
|
||||
});
|
||||
};
|
||||
|
||||
const setWindowSize = (ref, vec) => {
|
||||
return winset(ref, 'size', vec[0] + ',' + vec[1]);
|
||||
export const setWindowSize = vec => {
|
||||
return Byond.winset(window.__windowId__, {
|
||||
size: vec[0] + 'x' + vec[1],
|
||||
});
|
||||
};
|
||||
|
||||
export const setupDrag = async state => {
|
||||
logger.log('setting up');
|
||||
ref = state.config.window;
|
||||
// Calculate offset caused by windows taskbar
|
||||
const realPosition = await getWindowPosition(ref);
|
||||
screenOffset = [
|
||||
realPosition[0] - window.screenLeft,
|
||||
realPosition[1] - window.screenTop,
|
||||
];
|
||||
// Constraint window position
|
||||
const [relocated, safePosition] = constraintPosition(realPosition);
|
||||
if (relocated) {
|
||||
setWindowPosition(ref, safePosition);
|
||||
export const getScreenPosition = () => [
|
||||
0 - screenOffset[0],
|
||||
0 - screenOffset[1],
|
||||
];
|
||||
|
||||
export const getScreenSize = () => [
|
||||
window.screen.availWidth,
|
||||
window.screen.availHeight,
|
||||
];
|
||||
|
||||
/**
|
||||
* Moves an item to the top of the recents array, and keeps its length
|
||||
* limited to the number in `limit` argument.
|
||||
*
|
||||
* Uses a strict equality check for comparisons.
|
||||
*
|
||||
* Returns new recents and an item which was trimmed.
|
||||
*/
|
||||
const touchRecents = (recents, touchedItem, limit = 50) => {
|
||||
const nextRecents = [touchedItem];
|
||||
let trimmedItem;
|
||||
for (let i = 0; i < recents.length; i++) {
|
||||
const item = recents[i];
|
||||
if (item === touchedItem) {
|
||||
continue;
|
||||
}
|
||||
if (nextRecents.length < limit) {
|
||||
nextRecents.push(item);
|
||||
}
|
||||
else {
|
||||
trimmedItem = item;
|
||||
}
|
||||
}
|
||||
logger.debug('current state', { ref, screenOffset });
|
||||
return [nextRecents, trimmedItem];
|
||||
};
|
||||
|
||||
export const storeWindowGeometry = windowKey => {
|
||||
logger.log('storing geometry');
|
||||
const geometry = {
|
||||
pos: getWindowPosition(),
|
||||
size: getWindowSize(),
|
||||
};
|
||||
storage.set(windowKey, geometry);
|
||||
// Update the list of stored geometries
|
||||
const [geometries, trimmedKey] = touchRecents(
|
||||
storage.get('geometries') || [],
|
||||
windowKey);
|
||||
if (trimmedKey) {
|
||||
storage.remove(trimmedKey);
|
||||
}
|
||||
storage.set('geometries', geometries);
|
||||
};
|
||||
|
||||
export const recallWindowGeometry = async (windowKey, options = {}) => {
|
||||
// Only recall geometry in fancy mode
|
||||
const geometry = options.fancy && storage.get(windowKey);
|
||||
if (geometry) {
|
||||
logger.log('recalled geometry:', geometry);
|
||||
}
|
||||
let pos = geometry?.pos || options.pos;
|
||||
const size = options.size;
|
||||
// Set window size
|
||||
if (size) {
|
||||
setWindowSize(size);
|
||||
}
|
||||
// Set window position
|
||||
if (pos) {
|
||||
await screenOffsetPromise;
|
||||
// Constraint window position if monitor lock was set in preferences.
|
||||
if (size && options.locked) {
|
||||
pos = constraintPosition(pos, size)[1];
|
||||
}
|
||||
setWindowPosition(pos);
|
||||
}
|
||||
// Set window position at the center of the screen.
|
||||
else if (size) {
|
||||
await screenOffsetPromise;
|
||||
const areaAvailable = [
|
||||
window.screen.availWidth - Math.abs(screenOffset[0]),
|
||||
window.screen.availHeight - Math.abs(screenOffset[1]),
|
||||
];
|
||||
const pos = vecAdd(
|
||||
vecScale(areaAvailable, 0.5),
|
||||
vecScale(size, -0.5),
|
||||
vecScale(screenOffset, -1.0));
|
||||
setWindowPosition(pos);
|
||||
}
|
||||
};
|
||||
|
||||
export const setupDrag = async () => {
|
||||
// Calculate screen offset caused by the windows taskbar
|
||||
screenOffsetPromise = Byond.winget(window.__windowId__, 'pos')
|
||||
.then(pos => [
|
||||
pos.x - window.screenLeft,
|
||||
pos.y - window.screenTop,
|
||||
]);
|
||||
screenOffset = await screenOffsetPromise;
|
||||
logger.debug('screen offset', screenOffset);
|
||||
};
|
||||
|
||||
/**
|
||||
* Constraints window position to safe screen area, accounting for safe
|
||||
* margins which could be a system taskbar.
|
||||
*/
|
||||
const constraintPosition = position => {
|
||||
let x = position[0];
|
||||
let y = position[1];
|
||||
const constraintPosition = (pos, size) => {
|
||||
const screenPos = getScreenPosition();
|
||||
const screenSize = getScreenSize();
|
||||
const nextPos = [pos[0], pos[1]];
|
||||
let relocated = false;
|
||||
// Left
|
||||
if (x < 0) {
|
||||
x = 0;
|
||||
relocated = true;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const leftBoundary = screenPos[i];
|
||||
const rightBoundary = screenPos[i] + screenSize[i];
|
||||
if (pos[i] < leftBoundary) {
|
||||
nextPos[i] = leftBoundary;
|
||||
relocated = true;
|
||||
}
|
||||
else if (pos[i] + size[i] > rightBoundary) {
|
||||
nextPos[i] = rightBoundary - size[i];
|
||||
relocated = true;
|
||||
}
|
||||
}
|
||||
// Right
|
||||
else if (x + window.innerWidth > window.screen.availWidth) {
|
||||
x = window.screen.availWidth - window.innerWidth;
|
||||
relocated = true;
|
||||
}
|
||||
// Top
|
||||
if (y < 0) {
|
||||
y = 0;
|
||||
relocated = true;
|
||||
}
|
||||
// Bottom
|
||||
else if (y + window.innerHeight > window.screen.availHeight) {
|
||||
y = window.screen.availHeight - window.innerHeight;
|
||||
relocated = true;
|
||||
}
|
||||
return [relocated, [x, y]];
|
||||
return [relocated, nextPos];
|
||||
};
|
||||
|
||||
export const dragStartHandler = event => {
|
||||
@@ -97,6 +189,7 @@ const dragEndHandler = event => {
|
||||
document.removeEventListener('mousemove', dragMoveHandler);
|
||||
document.removeEventListener('mouseup', dragEndHandler);
|
||||
dragging = false;
|
||||
storeWindowGeometry(windowKey);
|
||||
};
|
||||
|
||||
const dragMoveHandler = event => {
|
||||
@@ -104,9 +197,8 @@ const dragMoveHandler = event => {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
setWindowPosition(ref, vecAdd(
|
||||
setWindowPosition(vecAdd(
|
||||
[event.screenX, event.screenY],
|
||||
screenOffset,
|
||||
dragPointOffset));
|
||||
};
|
||||
|
||||
@@ -133,6 +225,7 @@ const resizeEndHandler = event => {
|
||||
document.removeEventListener('mousemove', resizeMoveHandler);
|
||||
document.removeEventListener('mouseup', resizeEndHandler);
|
||||
resizing = false;
|
||||
storeWindowGeometry(windowKey);
|
||||
};
|
||||
|
||||
const resizeMoveHandler = event => {
|
||||
@@ -146,7 +239,7 @@ const resizeMoveHandler = event => {
|
||||
dragPointOffset,
|
||||
[1, 1])));
|
||||
// Sane window size values
|
||||
size[0] = Math.max(size[0], 250);
|
||||
size[1] = Math.max(size[1], 120);
|
||||
setWindowSize(ref, size);
|
||||
size[0] = Math.max(size[0], 150);
|
||||
size[1] = Math.max(size[1], 50);
|
||||
setWindowSize(size);
|
||||
};
|
||||
|
||||
@@ -39,6 +39,9 @@ export const formatSiUnit = (
|
||||
minBase1000 = -SI_BASE_INDEX,
|
||||
unit = ''
|
||||
) => {
|
||||
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
const realBase10 = Math.floor(Math.log10(value));
|
||||
const base10 = Math.floor(Math.max(minBase1000 * 3, realBase10));
|
||||
const realBase1000 = Math.floor(realBase10 / 3);
|
||||
|
||||
Vendored
+95
@@ -0,0 +1,95 @@
|
||||
interface ByondType {
|
||||
/**
|
||||
* True if javascript is running in BYOND.
|
||||
*/
|
||||
IS_BYOND: boolean;
|
||||
|
||||
/**
|
||||
* True if browser is IE8 or lower.
|
||||
*/
|
||||
IS_LTE_IE8: boolean;
|
||||
|
||||
/**
|
||||
* True if browser is IE9 or lower.
|
||||
*/
|
||||
IS_LTE_IE9: boolean;
|
||||
|
||||
/**
|
||||
* True if browser is IE10 or lower.
|
||||
*/
|
||||
IS_LTE_IE10: boolean;
|
||||
|
||||
/**
|
||||
* True if browser is IE11 or lower.
|
||||
*/
|
||||
IS_LTE_IE11: boolean;
|
||||
|
||||
/**
|
||||
* Makes a BYOND call.
|
||||
*
|
||||
* If path is empty, this will trigger a Topic call.
|
||||
* You can reference a specific object by setting the "src" parameter.
|
||||
*
|
||||
* See: https://secure.byond.com/docs/ref/skinparams.html
|
||||
*/
|
||||
call(path: string, params: object): void;
|
||||
|
||||
/**
|
||||
* Makes an asynchronous BYOND call. Returns a promise.
|
||||
*/
|
||||
callAsync(path: string, params: object): Promise<any>;
|
||||
|
||||
/**
|
||||
* Makes a Topic call.
|
||||
*
|
||||
* You can reference a specific object by setting the "src" parameter.
|
||||
*/
|
||||
topic(params: object): void;
|
||||
|
||||
/**
|
||||
* Runs a command or a verb.
|
||||
*/
|
||||
command(command: string): void;
|
||||
|
||||
/**
|
||||
* Retrieves all properties of the BYOND skin element.
|
||||
*
|
||||
* Returns a promise with a key-value object containing all properties.
|
||||
*/
|
||||
winget(id: string): Promise<object>;
|
||||
|
||||
/**
|
||||
* Retrieves all properties of the BYOND skin element.
|
||||
*
|
||||
* Returns a promise with a key-value object containing all properties.
|
||||
*/
|
||||
winget(id: string, propName: '*'): Promise<object>;
|
||||
|
||||
/**
|
||||
* Retrieves an exactly one property of the BYOND skin element,
|
||||
* as defined in `propName`.
|
||||
*
|
||||
* Returns a promise with the value of that property.
|
||||
*/
|
||||
winget(id: string, propName: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* Retrieves multiple properties of the BYOND skin element,
|
||||
* as listen in the `propNames` array.
|
||||
*
|
||||
* Returns a promise with a key-value object containing listed properties.
|
||||
*/
|
||||
winget(id: string, propNames: string[]): Promise<object>;
|
||||
|
||||
/**
|
||||
* Assigns properties to the BYOND skin element.
|
||||
*/
|
||||
winset(id: string, props: object): void;
|
||||
|
||||
/**
|
||||
* Sets a property on the BYOND skin element to a certain value.
|
||||
*/
|
||||
winset(id: string, propName: string, propValue: any): void;
|
||||
};
|
||||
|
||||
declare const Byond: ByondType;
|
||||
@@ -4,7 +4,6 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { callByond, IS_IE8 } from './byond';
|
||||
import { createLogger } from './logging';
|
||||
|
||||
const logger = createLogger('hotkeys');
|
||||
@@ -54,6 +53,18 @@ export const KEY_W = 87;
|
||||
export const KEY_X = 88;
|
||||
export const KEY_Y = 89;
|
||||
export const KEY_Z = 90;
|
||||
export const KEY_F1 = 112;
|
||||
export const KEY_F2 = 113;
|
||||
export const KEY_F3 = 114;
|
||||
export const KEY_F4 = 115;
|
||||
export const KEY_F5 = 116;
|
||||
export const KEY_F6 = 117;
|
||||
export const KEY_F7 = 118;
|
||||
export const KEY_F8 = 119;
|
||||
export const KEY_F9 = 120;
|
||||
export const KEY_F10 = 121;
|
||||
export const KEY_F11 = 122;
|
||||
export const KEY_F12 = 123;
|
||||
export const KEY_EQUAL = 187;
|
||||
export const KEY_MINUS = 189;
|
||||
|
||||
@@ -70,6 +81,18 @@ const NO_PASSTHROUGH_KEYS = [
|
||||
KEY_TAB,
|
||||
KEY_CTRL,
|
||||
KEY_SHIFT,
|
||||
KEY_F1,
|
||||
KEY_F2,
|
||||
KEY_F3,
|
||||
KEY_F4,
|
||||
KEY_F5,
|
||||
KEY_F6,
|
||||
KEY_F7,
|
||||
KEY_F8,
|
||||
KEY_F9,
|
||||
KEY_F10,
|
||||
KEY_F11,
|
||||
KEY_F12,
|
||||
];
|
||||
|
||||
// Tracks the "pressed" state of keys
|
||||
@@ -89,6 +112,9 @@ const createHotkeyString = (ctrlKey, altKey, shiftKey, keyCode) => {
|
||||
if (keyCode >= 48 && keyCode <= 90) {
|
||||
str += String.fromCharCode(keyCode);
|
||||
}
|
||||
else if (keyCode >= KEY_F1 && keyCode <= KEY_F12) {
|
||||
str += 'F' + (keyCode - 111);
|
||||
}
|
||||
else {
|
||||
str += '[' + keyCode + ']';
|
||||
}
|
||||
@@ -135,11 +161,11 @@ const handlePassthrough = (e, eventType) => {
|
||||
// Send this keypress to BYOND
|
||||
if (eventType === 'keydown' && !keyState[keyCode]) {
|
||||
logger.debug('passthrough', eventType, keyData);
|
||||
return callByond('', { __keydown: keyCode });
|
||||
return Byond.topic({ __keydown: keyCode });
|
||||
}
|
||||
if (eventType === 'keyup' && keyState[keyCode]) {
|
||||
logger.debug('passthrough', eventType, keyData);
|
||||
return callByond('', { __keyup: keyCode });
|
||||
return Byond.topic({ __keyup: keyCode });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -152,41 +178,45 @@ export const releaseHeldKeys = () => {
|
||||
if (keyState[keyCode]) {
|
||||
logger.log(`releasing [${keyCode}] key`);
|
||||
keyState[keyCode] = false;
|
||||
callByond('', { __keyup: keyCode });
|
||||
Byond.topic({ __keyup: keyCode });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleHotKey = (e, eventType, dispatch) => {
|
||||
const hotKeySubscribers = [];
|
||||
|
||||
/**
|
||||
* Subscribes to a certain hotkey, and dispatches a redux action returned
|
||||
* by the callback function.
|
||||
*/
|
||||
export const subscribeToHotKey = (keyString, fn) => {
|
||||
hotKeySubscribers.push((store, keyData) => {
|
||||
if (keyData.keyString === keyString) {
|
||||
const action = fn(store);
|
||||
if (action) {
|
||||
store.dispatch(action);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleHotKey = (e, eventType, store) => {
|
||||
if (eventType !== 'keyup') {
|
||||
return;
|
||||
}
|
||||
const keyData = getKeyData(e);
|
||||
const {
|
||||
ctrlKey,
|
||||
altKey,
|
||||
keyCode,
|
||||
hasModifierKeys,
|
||||
keyString,
|
||||
} = keyData;
|
||||
// Dispatch a detected hotkey as a store action
|
||||
if (hasModifierKeys && !MODIFIER_KEYS.includes(keyCode)) {
|
||||
if (hasModifierKeys && !MODIFIER_KEYS.includes(keyCode)
|
||||
|| keyCode >= KEY_F1 && keyCode <= KEY_F12) {
|
||||
logger.log(keyString);
|
||||
// Fun stuff
|
||||
if (ctrlKey && altKey && keyCode === KEY_BACKSPACE) {
|
||||
// NOTE: We need to call this in a timeout, because we need a clean
|
||||
// stack in order for this to be a fatal error.
|
||||
setTimeout(() => {
|
||||
throw new Error(
|
||||
'OOPSIE WOOPSIE!! UwU We made a fucky wucky!! A wittle'
|
||||
+ ' fucko boingo! The code monkeys at our headquarters are'
|
||||
+ ' working VEWY HAWD to fix this!');
|
||||
});
|
||||
for (let subscriberFn of hotKeySubscribers) {
|
||||
subscriberFn(store, keyData);
|
||||
}
|
||||
dispatch({
|
||||
type: 'hotKey',
|
||||
payload: keyData,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -224,18 +254,17 @@ const subscribeToKeyPresses = listenerFn => {
|
||||
|
||||
// Middleware
|
||||
export const hotKeyMiddleware = store => {
|
||||
const { dispatch } = store;
|
||||
// Subscribe to key events
|
||||
subscribeToKeyPresses((e, eventType) => {
|
||||
// IE8: Can't determine the focused element, so by extension it passes
|
||||
// keypresses when inputs are focused.
|
||||
if (!IS_IE8) {
|
||||
if (!Byond.IS_LTE_IE8) {
|
||||
handlePassthrough(e, eventType);
|
||||
}
|
||||
handleHotKey(e, eventType, dispatch);
|
||||
handleHotKey(e, eventType, store);
|
||||
});
|
||||
// IE8: focusin/focusout only available on IE9+
|
||||
if (!IS_IE8) {
|
||||
if (!Byond.IS_LTE_IE8) {
|
||||
// Clean up when browser window completely loses focus
|
||||
subscribeToLossOfFocus(() => {
|
||||
releaseHeldKeys();
|
||||
@@ -244,27 +273,3 @@ export const hotKeyMiddleware = store => {
|
||||
// Pass through store actions (do nothing)
|
||||
return next => action => next(action);
|
||||
};
|
||||
|
||||
// Reducer
|
||||
export const hotKeyReducer = (state, action) => {
|
||||
const { type, payload } = action;
|
||||
if (type === 'hotKey') {
|
||||
const { ctrlKey, altKey, keyCode } = payload;
|
||||
// Toggle kitchen sink mode
|
||||
if (ctrlKey && altKey && keyCode === KEY_EQUAL) {
|
||||
return {
|
||||
...state,
|
||||
showKitchenSink: !state.showKitchenSink,
|
||||
};
|
||||
}
|
||||
// Toggle layout debugger
|
||||
if (ctrlKey && altKey && keyCode === KEY_MINUS) {
|
||||
return {
|
||||
...state,
|
||||
debugLayout: !state.debugLayout,
|
||||
};
|
||||
}
|
||||
return state;
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
+96
-62
@@ -18,78 +18,79 @@ import './polyfills/inferno';
|
||||
|
||||
// Themes
|
||||
import './styles/main.scss';
|
||||
import './styles/themes/abductor.scss';
|
||||
import './styles/themes/cardtable.scss';
|
||||
import './styles/themes/hackerman.scss';
|
||||
import './styles/themes/malfunction.scss';
|
||||
import './styles/themes/ntos.scss';
|
||||
import './styles/themes/hackerman.scss';
|
||||
import './styles/themes/paper.scss';
|
||||
import './styles/themes/retro.scss';
|
||||
import './styles/themes/syndicate.scss';
|
||||
import './styles/themes/clockcult.scss';
|
||||
// import './styles/themes/paper.scss'; PAPER
|
||||
// "marked": "^1.1.0", PAPER - package.json
|
||||
// "dompurify": "^2.0.11"
|
||||
|
||||
import { loadCSS } from 'fg-loadcss';
|
||||
import { perf } from 'common/perf';
|
||||
import { render } from 'inferno';
|
||||
import { setupHotReloading } from 'tgui-dev-server/link/client';
|
||||
import { backendUpdate } from './backend';
|
||||
import { IS_IE8 } from './byond';
|
||||
import { backendUpdate, backendSuspendSuccess, selectBackend, sendMessage } from './backend';
|
||||
import { setupDrag } from './drag';
|
||||
import { logger } from './logging';
|
||||
import { createStore, StoreProvider } from './store';
|
||||
|
||||
const enteredBundleAt = Date.now();
|
||||
perf.mark('inception', window.__inception__);
|
||||
perf.mark('init');
|
||||
|
||||
const store = createStore();
|
||||
let reactRoot;
|
||||
let initialRender = true;
|
||||
|
||||
const renderLayout = () => {
|
||||
// Mark the beginning of the render
|
||||
let startedAt;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
startedAt = Date.now();
|
||||
}
|
||||
try {
|
||||
const state = store.getState();
|
||||
// Initial render setup
|
||||
if (initialRender) {
|
||||
logger.log('initial render', state);
|
||||
// Setup dragging
|
||||
setupDrag(state);
|
||||
perf.mark('render/start');
|
||||
const state = store.getState();
|
||||
const { suspended, assets } = selectBackend(state);
|
||||
// Initial render setup
|
||||
if (initialRender) {
|
||||
logger.log('initial render', state);
|
||||
// Setup dragging
|
||||
if (initialRender !== 'recycled') {
|
||||
setupDrag();
|
||||
}
|
||||
// Start rendering
|
||||
const { getRoutedComponent } = require('./routes');
|
||||
const Component = getRoutedComponent(state);
|
||||
const element = (
|
||||
<StoreProvider store={store}>
|
||||
<Component />
|
||||
</StoreProvider>
|
||||
);
|
||||
if (!reactRoot) {
|
||||
reactRoot = document.getElementById('react-root');
|
||||
}
|
||||
render(element, reactRoot);
|
||||
}
|
||||
catch (err) {
|
||||
logger.error('rendering error', err);
|
||||
throw err;
|
||||
// Start rendering
|
||||
const { getRoutedComponent } = require('./routes');
|
||||
const Component = getRoutedComponent(state);
|
||||
const element = (
|
||||
<StoreProvider store={store}>
|
||||
<Component />
|
||||
</StoreProvider>
|
||||
);
|
||||
if (!reactRoot) {
|
||||
reactRoot = document.getElementById('react-root');
|
||||
}
|
||||
render(element, reactRoot);
|
||||
if (suspended) {
|
||||
return;
|
||||
}
|
||||
perf.mark('render/finish');
|
||||
// Report rendering time
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const finishedAt = Date.now();
|
||||
if (initialRender) {
|
||||
if (initialRender === 'recycled') {
|
||||
logger.log('rendered in',
|
||||
perf.measure('render/start', 'render/finish'));
|
||||
}
|
||||
else if (initialRender) {
|
||||
logger.debug('serving from:', location.href);
|
||||
logger.debug('bundle entered in', timeDiff(
|
||||
window.__inception__, enteredBundleAt));
|
||||
logger.debug('initialized in', timeDiff(
|
||||
enteredBundleAt, startedAt));
|
||||
logger.log('rendered in', timeDiff(
|
||||
startedAt, finishedAt));
|
||||
logger.log('fully loaded in', timeDiff(
|
||||
window.__inception__, finishedAt));
|
||||
logger.debug('bundle entered in',
|
||||
perf.measure('inception', 'init'));
|
||||
logger.debug('initialized in',
|
||||
perf.measure('init', 'render/start'));
|
||||
logger.log('rendered in',
|
||||
perf.measure('render/start', 'render/finish'));
|
||||
logger.log('fully loaded in',
|
||||
perf.measure('inception', 'render/finish'));
|
||||
}
|
||||
else {
|
||||
logger.debug('rendered in', timeDiff(startedAt, finishedAt));
|
||||
logger.debug('rendered in',
|
||||
perf.measure('render/start', 'render/finish'));
|
||||
}
|
||||
}
|
||||
if (initialRender) {
|
||||
@@ -97,12 +98,6 @@ const renderLayout = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const timeDiff = (startedAt, finishedAt) => {
|
||||
const diff = finishedAt - startedAt;
|
||||
const diffFrames = (diff / 16.6667).toFixed(2);
|
||||
return `${diff}ms (${diffFrames} frames)`;
|
||||
};
|
||||
|
||||
// Parse JSON and report all abnormal JSON strings coming from BYOND
|
||||
const parseStateJson = json => {
|
||||
let reviver = (key, value) => {
|
||||
@@ -115,7 +110,7 @@ const parseStateJson = json => {
|
||||
};
|
||||
// IE8: No reviver for you!
|
||||
// See: https://stackoverflow.com/questions/1288962
|
||||
if (IS_IE8) {
|
||||
if (Byond.IS_LTE_IE8) {
|
||||
reviver = undefined;
|
||||
}
|
||||
try {
|
||||
@@ -136,14 +131,36 @@ const setupApp = () => {
|
||||
});
|
||||
|
||||
// Subscribe for bankend updates
|
||||
window.update = stateJson => {
|
||||
// NOTE: stateJson can be an object only if called manually from console.
|
||||
window.update = messageJson => {
|
||||
const { suspended } = selectBackend(store.getState());
|
||||
// NOTE: messageJson can be an object only if called manually from console.
|
||||
// This is useful for debugging tgui in external browsers, like Chrome.
|
||||
const state = typeof stateJson === 'string'
|
||||
? parseStateJson(stateJson)
|
||||
: stateJson;
|
||||
// Backend update dispatches a store action
|
||||
store.dispatch(backendUpdate(state));
|
||||
const message = typeof messageJson === 'string'
|
||||
? parseStateJson(messageJson)
|
||||
: messageJson;
|
||||
logger.debug(`received message '${message?.type}'`);
|
||||
const { type, payload } = message;
|
||||
if (type === 'update') {
|
||||
if (suspended) {
|
||||
logger.log('resuming');
|
||||
initialRender = 'recycled';
|
||||
}
|
||||
// Backend update dispatches a store action
|
||||
store.dispatch(backendUpdate(payload));
|
||||
return;
|
||||
}
|
||||
if (type === 'suspend') {
|
||||
store.dispatch(backendSuspendSuccess());
|
||||
return;
|
||||
}
|
||||
if (type === 'ping') {
|
||||
sendMessage({
|
||||
type: 'pingReply',
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Pass the message directly to the store
|
||||
store.dispatch(message);
|
||||
};
|
||||
|
||||
// Enable hot module reloading
|
||||
@@ -166,9 +183,26 @@ const setupApp = () => {
|
||||
}
|
||||
window.update(stateJson);
|
||||
}
|
||||
};
|
||||
|
||||
// Dynamically load font-awesome from browser's cache
|
||||
loadCSS('font-awesome.css');
|
||||
// Setup a fatal error reporter
|
||||
window.__logger__ = {
|
||||
fatal: (error, stack) => {
|
||||
// Get last state for debugging purposes
|
||||
const backendState = selectBackend(store.getState());
|
||||
const reportedState = {
|
||||
config: backendState.config,
|
||||
suspended: backendState.suspended,
|
||||
suspending: backendState.suspending,
|
||||
};
|
||||
// Send to development server
|
||||
logger.log('FatalError:', error || stack);
|
||||
logger.log('State:', reportedState);
|
||||
// Append this data to the stack
|
||||
stack += '\nState: ' + JSON.stringify(reportedState);
|
||||
// Return an updated stack
|
||||
return stack;
|
||||
},
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { classes } from 'common/react';
|
||||
import { IS_IE8 } from '../byond';
|
||||
import { computeBoxProps, computeBoxClassName } from '../components/Box';
|
||||
|
||||
/**
|
||||
* Brings Layout__content DOM element back to focus.
|
||||
@@ -14,7 +14,7 @@ import { IS_IE8 } from '../byond';
|
||||
*/
|
||||
export const refocusLayout = () => {
|
||||
// IE8: Focus method is seemingly fucked.
|
||||
if (IS_IE8) {
|
||||
if (Byond.IS_LTE_IE8) {
|
||||
return;
|
||||
}
|
||||
const element = document.getElementById('Layout__content');
|
||||
@@ -47,6 +47,7 @@ const LayoutContent = props => {
|
||||
className,
|
||||
scrollable,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<div
|
||||
@@ -55,7 +56,9 @@ const LayoutContent = props => {
|
||||
'Layout__content',
|
||||
scrollable && 'Layout__content--scrollable',
|
||||
className,
|
||||
])}>
|
||||
...computeBoxClassName(rest),
|
||||
])}
|
||||
{...computeBoxProps(rest)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { resolveAsset } from '../assets';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button } from '../components';
|
||||
import { refocusLayout } from './Layout';
|
||||
@@ -11,12 +12,16 @@ import { Window } from './Window';
|
||||
|
||||
export const NtosWindow = (props, context) => {
|
||||
const {
|
||||
title,
|
||||
width = 575,
|
||||
height = 700,
|
||||
resizable,
|
||||
theme = 'ntos',
|
||||
children,
|
||||
} = props;
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
PC_device_theme,
|
||||
PC_batteryicon,
|
||||
PC_showbatteryicon,
|
||||
PC_batterypercent,
|
||||
@@ -28,6 +33,9 @@ export const NtosWindow = (props, context) => {
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
title={title}
|
||||
width={width}
|
||||
height={height}
|
||||
theme={theme}
|
||||
resizable={resizable}>
|
||||
<div className="NtosWindow">
|
||||
@@ -41,7 +49,8 @@ export const NtosWindow = (props, context) => {
|
||||
{PC_stationtime}
|
||||
</Box>
|
||||
<Box inline italic mr={2} opacity={0.33}>
|
||||
NtOS
|
||||
{PC_device_theme === 'ntos' && 'NtOS'}
|
||||
{PC_device_theme === 'syndicate' && 'Syndix'}
|
||||
</Box>
|
||||
</div>
|
||||
<div className="NtosHeader__right">
|
||||
@@ -49,14 +58,14 @@ export const NtosWindow = (props, context) => {
|
||||
<Box key={header.icon} inline mr={1}>
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={header.icon} />
|
||||
src={resolveAsset(header.icon)} />
|
||||
</Box>
|
||||
))}
|
||||
<Box inline>
|
||||
{PC_ntneticon && (
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={PC_ntneticon} />
|
||||
src={resolveAsset(PC_ntneticon)} />
|
||||
)}
|
||||
</Box>
|
||||
{!!PC_showbatteryicon && PC_batteryicon && (
|
||||
@@ -64,7 +73,7 @@ export const NtosWindow = (props, context) => {
|
||||
{PC_batteryicon && (
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={PC_batteryicon} />
|
||||
src={resolveAsset(PC_batteryicon)} />
|
||||
)}
|
||||
{PC_batterypercent && (
|
||||
PC_batterypercent
|
||||
@@ -75,7 +84,7 @@ export const NtosWindow = (props, context) => {
|
||||
<Box inline mr={1}>
|
||||
<img
|
||||
className="NtosHeader__icon"
|
||||
src={PC_apclinkicon} />
|
||||
src={resolveAsset(PC_apclinkicon)} />
|
||||
</Box>
|
||||
)}
|
||||
{!!PC_showexitprogram && (
|
||||
|
||||
@@ -7,19 +7,35 @@
|
||||
import { classes } from 'common/react';
|
||||
import { decodeHtmlEntities, toTitleCase } from 'common/string';
|
||||
import { Component, Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { IS_IE8, runCommand, winset } from '../byond';
|
||||
import { Box, Icon } from '../components';
|
||||
import { backendSuspendStart, useBackend } from '../backend';
|
||||
import { Icon } from '../components';
|
||||
import { UI_DISABLED, UI_INTERACTIVE, UI_UPDATE } from '../constants';
|
||||
import { dragStartHandler, resizeStartHandler } from '../drag';
|
||||
import { releaseHeldKeys } from '../hotkeys';
|
||||
import { toggleKitchenSink, useDebug } from '../debug';
|
||||
import { dragStartHandler, recallWindowGeometry, resizeStartHandler, setWindowKey } from '../drag';
|
||||
import { createLogger } from '../logging';
|
||||
import { useDispatch } from '../store';
|
||||
import { Layout, refocusLayout } from './Layout';
|
||||
|
||||
const logger = createLogger('Window');
|
||||
|
||||
const DEFAULT_SIZE = [400, 600];
|
||||
|
||||
export class Window extends Component {
|
||||
componentDidMount() {
|
||||
const { config, suspended } = useBackend(this.context);
|
||||
if (suspended) {
|
||||
return;
|
||||
}
|
||||
logger.log('mounting');
|
||||
const options = {
|
||||
size: DEFAULT_SIZE,
|
||||
...config.window,
|
||||
};
|
||||
if (this.props.width && this.props.height) {
|
||||
options.size = [this.props.width, this.props.height];
|
||||
}
|
||||
setWindowKey(config.window.key);
|
||||
recallWindowGeometry(config.window.key, options);
|
||||
refocusLayout();
|
||||
}
|
||||
|
||||
@@ -27,14 +43,18 @@ export class Window extends Component {
|
||||
const {
|
||||
resizable,
|
||||
theme,
|
||||
title,
|
||||
children,
|
||||
} = this.props;
|
||||
const {
|
||||
config,
|
||||
debugLayout,
|
||||
suspended,
|
||||
} = useBackend(this.context);
|
||||
const { debugLayout } = useDebug(this.context);
|
||||
const dispatch = useDispatch(this.context);
|
||||
const fancy = config.window?.fancy;
|
||||
// Determine when to show dimmer
|
||||
const showDimmer = config.observer
|
||||
const showDimmer = config.user.observer
|
||||
? config.status < UI_DISABLED
|
||||
: config.status < UI_INTERACTIVE;
|
||||
return (
|
||||
@@ -43,27 +63,25 @@ export class Window extends Component {
|
||||
theme={theme}>
|
||||
<TitleBar
|
||||
className="Window__titleBar"
|
||||
title={decodeHtmlEntities(config.title)}
|
||||
title={!suspended && (title || decodeHtmlEntities(config.title))}
|
||||
status={config.status}
|
||||
fancy={config.fancy}
|
||||
fancy={fancy}
|
||||
onDragStart={dragStartHandler}
|
||||
onClose={() => {
|
||||
logger.log('pressed close');
|
||||
releaseHeldKeys();
|
||||
winset(config.window, 'is-visible', false);
|
||||
runCommand(`uiclose ${config.ref}`);
|
||||
dispatch(backendSuspendStart());
|
||||
}} />
|
||||
<div
|
||||
className={classes([
|
||||
'Window__rest',
|
||||
debugLayout && 'debug-layout',
|
||||
])}>
|
||||
{children}
|
||||
{!suspended && children}
|
||||
{showDimmer && (
|
||||
<div className="Window__dimmer" />
|
||||
)}
|
||||
</div>
|
||||
{config.fancy && resizable && (
|
||||
{fancy && resizable && (
|
||||
<Fragment>
|
||||
<div className="Window__resizeHandle__e"
|
||||
onMousedown={resizeStartHandler(1, 0)} />
|
||||
@@ -79,15 +97,26 @@ export class Window extends Component {
|
||||
}
|
||||
|
||||
const WindowContent = props => {
|
||||
const { scrollable, children } = props;
|
||||
const {
|
||||
className,
|
||||
fitted,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
// A bit lazy to actually write styles for it,
|
||||
// so we simply include a Box with margins.
|
||||
return (
|
||||
<Layout.Content
|
||||
scrollable={scrollable}>
|
||||
<Box m={1}>
|
||||
{children}
|
||||
</Box>
|
||||
className={classes([
|
||||
'Window__content',
|
||||
className,
|
||||
])}
|
||||
{...rest}>
|
||||
{fitted && children || (
|
||||
<div className="Window__contentPadding">
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</Layout.Content>
|
||||
);
|
||||
};
|
||||
@@ -106,7 +135,7 @@ const statusToColor = status => {
|
||||
}
|
||||
};
|
||||
|
||||
const TitleBar = props => {
|
||||
const TitleBar = (props, context) => {
|
||||
const {
|
||||
className,
|
||||
title,
|
||||
@@ -115,6 +144,7 @@ const TitleBar = props => {
|
||||
onDragStart,
|
||||
onClose,
|
||||
} = props;
|
||||
const dispatch = useDispatch(context);
|
||||
return (
|
||||
<div
|
||||
className={classes([
|
||||
@@ -126,13 +156,21 @@ const TitleBar = props => {
|
||||
color={statusToColor(status)}
|
||||
name="eye" />
|
||||
<div className="TitleBar__title">
|
||||
{title === title.toLowerCase()
|
||||
? toTitleCase(title)
|
||||
: title}
|
||||
{typeof title === 'string'
|
||||
&& title === title.toLowerCase()
|
||||
&& toTitleCase(title)
|
||||
|| title}
|
||||
</div>
|
||||
<div
|
||||
className="TitleBar__dragZone"
|
||||
onMousedown={e => fancy && onDragStart(e)} />
|
||||
{process.env.NODE_ENV !== 'production' && (
|
||||
<div
|
||||
className="TitleBar__devBuildIndicator"
|
||||
onClick={() => dispatch(toggleKitchenSink())}>
|
||||
<Icon name="bug" />
|
||||
</div>
|
||||
)}
|
||||
{!!fancy && (
|
||||
<div
|
||||
className="TitleBar__close TitleBar__clickable"
|
||||
@@ -140,7 +178,7 @@ const TitleBar = props => {
|
||||
// IE8: Use a plain character instead of a unicode symbol.
|
||||
// eslint-disable-next-line react/no-unknown-property
|
||||
onclick={onClose}>
|
||||
{IS_IE8 ? 'x' : '×'}
|
||||
{Byond.IS_LTE_IE8 ? 'x' : '×'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
import { sendLogEntry } from 'tgui-dev-server/link/client';
|
||||
import { callByond } from './byond';
|
||||
|
||||
const LEVEL_DEBUG = 0;
|
||||
const LEVEL_LOG = 1;
|
||||
@@ -33,10 +32,11 @@ const log = (level, ns, ...args) => {
|
||||
.filter(value => value)
|
||||
.join(' ')
|
||||
+ '\nUser Agent: ' + navigator.userAgent;
|
||||
callByond('', {
|
||||
src: window.__ref__,
|
||||
action: 'tgui:log',
|
||||
log: logEntry,
|
||||
Byond.topic({
|
||||
tgui: 1,
|
||||
window_id: window.__windowId__,
|
||||
type: 'log',
|
||||
message: logEntry,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "tgui",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.6.2",
|
||||
"@babel/plugin-transform-jscript": "^7.2.0",
|
||||
@@ -12,16 +12,18 @@
|
||||
"core-js": "^3.2.1",
|
||||
"css-loader": "^3.2.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"dompurify": "^2.0.11",
|
||||
"extract-css-chunks-webpack-plugin": "^4.6.0",
|
||||
"fg-loadcss": "^2.1.0",
|
||||
"file-loader": "^6.0.0",
|
||||
"inferno": "^7.3.2",
|
||||
"marked": "^1.1.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"sass": "^1.22.12",
|
||||
"sass-loader": "^8.0.0",
|
||||
"sass-loader": "^9.0.2",
|
||||
"style-loader": "^1.0.0",
|
||||
"terser-webpack-plugin": "^2.1.0",
|
||||
"terser-webpack-plugin": "^3.0.6",
|
||||
"url-loader": "^4.1.0",
|
||||
"webpack": "^4.40.2",
|
||||
"webpack-build-notifier": "^2.0.0",
|
||||
|
||||
@@ -5,29 +5,59 @@
|
||||
<meta charset="utf-8">
|
||||
|
||||
<!-- Inlined data -->
|
||||
<meta id="tgui:ref" content="[tgui:ref]">
|
||||
<meta id="tgui:windowId" content="[tgui:windowId]">
|
||||
|
||||
<!-- Early setup -->
|
||||
<script type="text/javascript">
|
||||
// Mark the beginning of initialization
|
||||
window.__inception__ = new Date().getTime();
|
||||
// Read tgui object ref into a global
|
||||
window.__ref__ = document
|
||||
.getElementById('tgui:ref')
|
||||
// Read window id into a global
|
||||
window.__windowId__ = document
|
||||
.getElementById('tgui:windowId')
|
||||
.getAttribute('content');
|
||||
// Null if template marker was not replaced
|
||||
if (window.__ref__ === '[' + 'tgui:ref' + ']') {
|
||||
window.__ref__ = null;
|
||||
if (window.__windowId__ === '[' + 'tgui:windowId' + ']') {
|
||||
window.__windowId__ = null;
|
||||
}
|
||||
|
||||
// BYOND API object
|
||||
window.Byond = (function () {
|
||||
var Byond = {};
|
||||
// Utility functions
|
||||
var hasOwn = Object.prototype.hasOwnProperty;
|
||||
var assign = function (target) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var source = arguments[i];
|
||||
for (var key in source) {
|
||||
if (hasOwn.call(source, key)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
};
|
||||
// Trident engine version
|
||||
var tridentVersion = (function () {
|
||||
var groups = navigator.userAgent.match(/Trident\/(\d+).+?;/i);
|
||||
var majorVersion = groups && groups[1];
|
||||
return majorVersion
|
||||
? parseInt(majorVersion, 10)
|
||||
: null;
|
||||
})();
|
||||
// Basic checks to detect whether this page runs in BYOND
|
||||
var isByond = !!navigator.userAgent.match(/Trident\/(\d+).+?;/i)
|
||||
var isByond = tridentVersion !== null
|
||||
&& location.hostname === '127.0.0.1'
|
||||
&& location.pathname.indexOf('/tmp') === 0;
|
||||
|
||||
// Version constants
|
||||
Byond.IS_BYOND = isByond;
|
||||
Byond.IS_LTE_IE8 = tridentVersion !== null && tridentVersion <= 4;
|
||||
Byond.IS_LTE_IE9 = tridentVersion !== null && tridentVersion <= 5;
|
||||
Byond.IS_LTE_IE10 = tridentVersion !== null && tridentVersion <= 6;
|
||||
Byond.IS_LTE_IE11 = tridentVersion !== null && tridentVersion <= 7;
|
||||
|
||||
// Callbacks for asynchronous calls
|
||||
Byond.__callbacks__ = [];
|
||||
|
||||
// Makes a BYOND call.
|
||||
// See: https://secure.byond.com/docs/ref/skinparams.html
|
||||
Byond.call = function (path, params) {
|
||||
@@ -64,8 +94,61 @@ window.Byond = (function () {
|
||||
xhr.open('GET', url);
|
||||
xhr.send();
|
||||
};
|
||||
|
||||
Byond.callAsync = function (path, params) {
|
||||
if (!window.Promise) {
|
||||
throw new Error('Async calls require API level of ES2015 or later.');
|
||||
}
|
||||
var index = Byond.__callbacks__.length;
|
||||
var promise = new window.Promise(function (resolve) {
|
||||
Byond.__callbacks__.push(resolve);
|
||||
});
|
||||
Byond.call(path, assign({}, params, {
|
||||
callback: 'Byond.__callbacks__[' + index + ']',
|
||||
}));
|
||||
return promise;
|
||||
};
|
||||
|
||||
Byond.topic = function (params) {
|
||||
return Byond.call('', params);
|
||||
};
|
||||
|
||||
Byond.command = function (command) {
|
||||
return Byond.call('winset', {
|
||||
command: command,
|
||||
});
|
||||
};
|
||||
|
||||
Byond.winget = function (id, propName) {
|
||||
var isArray = propName instanceof Array;
|
||||
var isSpecific = propName && propName !== '*' && !isArray;
|
||||
var promise = Byond.callAsync('winget', {
|
||||
id: id,
|
||||
property: isArray && propName.join(',') || propName || '*',
|
||||
});
|
||||
if (isSpecific) {
|
||||
promise = promise.then(function (props) {
|
||||
return props[propName];
|
||||
});
|
||||
}
|
||||
return promise;
|
||||
};
|
||||
|
||||
Byond.winset = function (id, propName, propValue) {
|
||||
var props = {};
|
||||
if (propName === 'string') {
|
||||
props[propName] = propValue;
|
||||
}
|
||||
else {
|
||||
assign(props, propName);
|
||||
}
|
||||
props.id = id;
|
||||
return Byond.call('winset', props);
|
||||
};
|
||||
|
||||
return Byond;
|
||||
})();
|
||||
|
||||
// Global error handling
|
||||
window.onerror = function (msg, url, line, col, error) {
|
||||
// Proper stacktrace
|
||||
@@ -78,7 +161,7 @@ window.onerror = function (msg, url, line, col, error) {
|
||||
}
|
||||
}
|
||||
// Append user agent info
|
||||
stack += '\n\nUser Agent: ' + navigator.userAgent;
|
||||
stack += '\nUser Agent: ' + navigator.userAgent;
|
||||
// Print error to the page
|
||||
var errorRoot = document.getElementById('FatalError');
|
||||
var errorStack = document.getElementById('FatalError__stack');
|
||||
@@ -91,12 +174,29 @@ window.onerror = function (msg, url, line, col, error) {
|
||||
errorStack.textContent = stack;
|
||||
}
|
||||
}
|
||||
// Send logs to the server
|
||||
Byond.call('', {
|
||||
src: window.__ref__,
|
||||
action: 'tgui:log',
|
||||
fatal: '1',
|
||||
log: stack,
|
||||
// Set window geometry
|
||||
var setFatalErrorGeometry = function () {
|
||||
Byond.winset(window.__windowId__, {
|
||||
titlebar: true,
|
||||
size: '600x600',
|
||||
'is-visible': true,
|
||||
'can-resize': true,
|
||||
});
|
||||
};
|
||||
setFatalErrorGeometry();
|
||||
setInterval(setFatalErrorGeometry, 1000);
|
||||
// Use a hook that comes with the bundle to augment the stack
|
||||
// and send errors to a dev server (if using a development build).
|
||||
if (window.__logger__) {
|
||||
stack = window.__logger__.fatal(error, stack);
|
||||
}
|
||||
// Send logs to the game server
|
||||
Byond.topic({
|
||||
tgui: 1,
|
||||
window_id: window.__windowId__,
|
||||
type: 'log',
|
||||
fatal: 1,
|
||||
message: stack,
|
||||
});
|
||||
// Short-circuit further updates
|
||||
window.__updateQueue__ = [];
|
||||
@@ -104,26 +204,24 @@ window.onerror = function (msg, url, line, col, error) {
|
||||
// Prevent default action
|
||||
return true;
|
||||
};
|
||||
|
||||
// Early initialization
|
||||
window.__updateQueue__ = [];
|
||||
window.update = function (stateJson) {
|
||||
window.__updateQueue__.push(stateJson);
|
||||
window.update = function (message) {
|
||||
window.__updateQueue__.push(message);
|
||||
};
|
||||
Byond.call('', {
|
||||
src: window.__ref__,
|
||||
action: 'tgui:initialize',
|
||||
Byond.topic({
|
||||
tgui: 1,
|
||||
window_id: window.__windowId__,
|
||||
type: 'ready',
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Styles -->
|
||||
<link rel="stylesheet" type="text/css" href="tgui.bundle.css">
|
||||
|
||||
<!-- This is processed in byond, so interfaces can override the
|
||||
html head if needed, for custom sheets of style etc. -->
|
||||
<!--customheadhtml-->
|
||||
<!-- tgui:styles -->
|
||||
|
||||
<!-- Scripts -->
|
||||
<script type="text/javascript" defer src="tgui.bundle.js"></script>
|
||||
<!-- tgui:scripts -->
|
||||
|
||||
</head>
|
||||
<body>
|
||||
@@ -161,7 +259,7 @@ A fatal exception has occurred at 002B:C562F1B7 in TGUI.
|
||||
The current application will be terminated.
|
||||
Please remain calm. Get to the nearest NTNet workstation
|
||||
and send the copy of the following stack trace to:
|
||||
github.com/Citadel-Station-13/Citadel-Station-13/issues. Thank you for your cooperation.
|
||||
https://github.com/Citadel-Station-13/Citadel-Station-13/issues Thank you for your cooperation.
|
||||
</marquee>
|
||||
<div id="FatalError__stack" class="FatalError__stack"></div>
|
||||
<div class="FatalError__footer">
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { selectBackend } from './backend';
|
||||
import { selectDebug } from './debug';
|
||||
import { Window } from './layouts';
|
||||
|
||||
const requireInterface = require.context('./interfaces', false, /\.js$/);
|
||||
@@ -23,14 +25,27 @@ const routingError = (type, name) => () => {
|
||||
);
|
||||
};
|
||||
|
||||
const SuspendedWindow = () => {
|
||||
return (
|
||||
<Window resizable>
|
||||
<Window.Content scrollable />
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export const getRoutedComponent = state => {
|
||||
const { suspended, config } = selectBackend(state);
|
||||
if (suspended) {
|
||||
return SuspendedWindow;
|
||||
}
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const debug = selectDebug(state);
|
||||
// Show a kitchen sink
|
||||
if (state.showKitchenSink) {
|
||||
return require('./interfaces/manually-routed/KitchenSink').KitchenSink;
|
||||
if (debug.kitchenSink) {
|
||||
return require('./debug/KitchenSink').KitchenSink;
|
||||
}
|
||||
}
|
||||
const name = state.config?.interface;
|
||||
const name = config?.interface;
|
||||
let esModule;
|
||||
try {
|
||||
esModule = requireInterface(`./${name}.js`);
|
||||
|
||||
@@ -5,24 +5,44 @@
|
||||
*/
|
||||
|
||||
import { flow } from 'common/fp';
|
||||
import { applyMiddleware, createStore as createReduxStore } from 'common/redux';
|
||||
import { applyMiddleware, combineReducers, createStore as createReduxStore } from 'common/redux';
|
||||
import { Component } from 'inferno';
|
||||
import { backendReducer } from './backend';
|
||||
import { hotKeyMiddleware, hotKeyReducer } from './hotkeys';
|
||||
import { backendMiddleware, backendReducer } from './backend';
|
||||
import { debugReducer } from './debug';
|
||||
import { hotKeyMiddleware } from './hotkeys';
|
||||
import { createLogger } from './logging';
|
||||
import { assetMiddleware } from './assets';
|
||||
|
||||
const logger = createLogger('store');
|
||||
|
||||
export const createStore = () => {
|
||||
const reducer = flow([
|
||||
// State initializer
|
||||
(state = {}, action) => state,
|
||||
// Global state reducers
|
||||
backendReducer,
|
||||
hotKeyReducer,
|
||||
combineReducers({
|
||||
debug: debugReducer,
|
||||
backend: backendReducer,
|
||||
}),
|
||||
]);
|
||||
const middleware = [
|
||||
// loggingMiddleware,
|
||||
process.env.NODE_ENV !== 'production' && loggingMiddleware,
|
||||
assetMiddleware,
|
||||
hotKeyMiddleware,
|
||||
backendMiddleware,
|
||||
];
|
||||
return createReduxStore(reducer, applyMiddleware(...middleware));
|
||||
return createReduxStore(reducer,
|
||||
applyMiddleware(...middleware.filter(Boolean)));
|
||||
};
|
||||
|
||||
const loggingMiddleware = store => next => action => {
|
||||
const { type, payload } = action;
|
||||
if (type === 'backend/update') {
|
||||
logger.debug('action', { type });
|
||||
}
|
||||
else {
|
||||
logger.debug('action', action);
|
||||
}
|
||||
return next(action);
|
||||
};
|
||||
|
||||
export class StoreProvider extends Component {
|
||||
|
||||
@@ -7,42 +7,34 @@
|
||||
|
||||
.outline-dotted {
|
||||
outline-style: dotted !important;
|
||||
outline-width: 2px !important;
|
||||
}
|
||||
|
||||
.outline-dashed {
|
||||
outline-style: dashed !important;
|
||||
outline-width: 2px !important;
|
||||
}
|
||||
|
||||
.outline-solid {
|
||||
outline-style: solid !important;
|
||||
outline-width: 2px !important;
|
||||
}
|
||||
|
||||
.outline-double {
|
||||
outline-style: double !important;
|
||||
outline-width: 2px !important;
|
||||
}
|
||||
|
||||
.outline-groove {
|
||||
outline-style: groove !important;
|
||||
outline-width: 2px !important;
|
||||
}
|
||||
|
||||
.outline-ridge {
|
||||
outline-style: ridge !important;
|
||||
outline-width: 2px !important;
|
||||
}
|
||||
|
||||
.outline-inset {
|
||||
outline-style: inset !important;
|
||||
outline-width: 2px !important;
|
||||
}
|
||||
|
||||
.outline-outset {
|
||||
outline-style: outset !important;
|
||||
outline-width: 2px !important;
|
||||
}
|
||||
|
||||
$fg-map: colors.$fg-map !default;
|
||||
@@ -50,6 +42,6 @@ $bg-map: colors.$bg-map !default;
|
||||
|
||||
@each $color-name, $color-value in $fg-map {
|
||||
.outline-color-#{$color-name} {
|
||||
outline-color: $color-value !important;
|
||||
outline: 0.167rem solid $color-value !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,4 +12,15 @@ $color-bg-start: color.adjust(
|
||||
$color-bg, $lightness: $color-bg-grad-spread) !default;
|
||||
$color-bg-end: color.adjust(
|
||||
$color-bg, $lightness: -$color-bg-grad-spread) !default;
|
||||
$border-radius: 2px !default;
|
||||
|
||||
$unit: 12px;
|
||||
$font-size: 1 * $unit !default;
|
||||
$border-radius: 0.16em !default;
|
||||
|
||||
@function em($px) {
|
||||
@return 1em * ($px / $unit);
|
||||
}
|
||||
|
||||
@function rem($px) {
|
||||
@return 1rem * ($px / $unit);
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ $color-default: colors.fg(colors.$label) !default;
|
||||
|
||||
.BlockQuote {
|
||||
color: $color-default;
|
||||
border-left: 2px solid $color-default;
|
||||
padding-left: 6px;
|
||||
margin-bottom: 6px;
|
||||
border-left: base.em(2px) solid $color-default;
|
||||
padding-left: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -45,13 +45,13 @@ $bg-map: colors.$bg-map !default;
|
||||
.Button {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
line-height: 20px;
|
||||
padding: 0 6px;
|
||||
margin-right: 2px;
|
||||
line-height: base.em(20px);
|
||||
padding: 0 0.5em;
|
||||
margin-right: base.em(2px);
|
||||
white-space: nowrap;
|
||||
outline: 0;
|
||||
border-radius: $border-radius;
|
||||
margin-bottom: 2px;
|
||||
margin-bottom: base.em(2px);
|
||||
// Disable selection in buttons
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
@@ -61,9 +61,9 @@ $bg-map: colors.$bg-map !default;
|
||||
}
|
||||
|
||||
.fa, .fas, .far {
|
||||
margin-left: -3px;
|
||||
margin-right: -3px;
|
||||
min-width: 16px;
|
||||
margin-left: -0.25em;
|
||||
margin-right: -0.25em;
|
||||
min-width: base.em(16px);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ $bg-map: colors.$bg-map !default;
|
||||
.Button--hasContent {
|
||||
// Add a margin to the icon to keep it separate from the text
|
||||
.fa, .fas, .far {
|
||||
margin-right: 3px;
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
.ColorBox {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
line-height: 12px;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
line-height: 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use '../base.scss';
|
||||
|
||||
$color: rgba(255, 255, 255, 0.1) !default;
|
||||
$thickness: 2px !default;
|
||||
$spacing: 6px;
|
||||
$thickness: base.em(2px) !default;
|
||||
$spacing: 0.5em;
|
||||
|
||||
.Divider--horizontal {
|
||||
margin: $spacing 0;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use '../base.scss';
|
||||
|
||||
.Dropdown {
|
||||
position: relative;
|
||||
}
|
||||
@@ -11,27 +13,27 @@
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
font-family: Verdana, sans-serif;
|
||||
font-size: 12px;
|
||||
width: 100px;
|
||||
line-height: 17px;
|
||||
font-size: base.em(12px);
|
||||
width: base.em(100px);
|
||||
line-height: base.em(17px);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.Dropdown__arrow-button {
|
||||
float: right;
|
||||
padding-left: 6px;
|
||||
border-left: 1px solid #000;
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.25);
|
||||
padding-left: 0.5em;
|
||||
border-left: base.em(1px) solid #000;
|
||||
border-left: base.em(1px) solid rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.Dropdown__menu {
|
||||
position: absolute;
|
||||
overflow-y: auto;
|
||||
z-index: 5;
|
||||
width: 100px;
|
||||
max-height: 200px;
|
||||
width: base.em(100px);
|
||||
max-height: base.em(200px);
|
||||
overflow-y: scroll;
|
||||
border-radius: 0 0 2px 2px;
|
||||
border-radius: 0 0 base.em(2px) base.em(2px);
|
||||
background-color: #000;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
@@ -40,18 +42,18 @@
|
||||
position: absolute;
|
||||
overflow-y: auto;
|
||||
z-index: 5;
|
||||
width: 100px;
|
||||
max-height: 200px;
|
||||
border-radius: 0 0 2px 2px;
|
||||
width: base.em(100px);
|
||||
max-height: base.em(200px);
|
||||
border-radius: 0 0 base.em(2px) base.em(2px);
|
||||
background-color: #000;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
.Dropdown__menuentry {
|
||||
padding: 2px 4px;
|
||||
padding: base.em(2px) base.em(4px);
|
||||
font-family: Verdana, sans-serif;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
font-size: base.em(12px);
|
||||
line-height: base.em(17px);
|
||||
transition: background-color 100ms;
|
||||
&:hover {
|
||||
background-color: #444;
|
||||
|
||||
@@ -12,30 +12,30 @@
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.Flex--ie8 {
|
||||
.Flex--iefix {
|
||||
display: table !important;
|
||||
}
|
||||
|
||||
.Flex--ie8--column {
|
||||
.Flex--iefix--column {
|
||||
display: block !important;
|
||||
|
||||
& > .Flex__item {
|
||||
display: block !important;
|
||||
margin-left: 6px;
|
||||
margin-right: 6px;
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.Flex__item--ie8 {
|
||||
.Flex__item--iefix {
|
||||
display: table-cell !important;
|
||||
}
|
||||
|
||||
@for $i from 1 through 2 {
|
||||
.Flex--spacing--#{$i} {
|
||||
margin: 0 -3px * $i;
|
||||
margin: 0 -0.25em * $i;
|
||||
|
||||
& > .Flex__item {
|
||||
margin: 0 3px * $i;
|
||||
margin: 0 0.25em * $i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,16 +12,16 @@ $border-radius: base.$border-radius !default;
|
||||
.Input {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
border: 1px solid $border-color;
|
||||
border: 1px solid rgba($border-color, 0.75);
|
||||
width: base.em(120px);
|
||||
border: base.em(1px) solid $border-color;
|
||||
border: base.em(1px) solid rgba($border-color, 0.75);
|
||||
border-radius: $border-radius;
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
padding: 0 4px;
|
||||
margin-right: 2px;
|
||||
line-height: 17px;
|
||||
padding: 0 base.em(4px);
|
||||
margin-right: base.em(2px);
|
||||
line-height: base.em(17px);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
@@ -45,11 +45,11 @@ $border-radius: base.$border-radius !default;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
height: 17px;
|
||||
font-size: base.em(12px);
|
||||
line-height: base.em(17px);
|
||||
height: base.em(17px);
|
||||
margin: 0;
|
||||
padding: 0 6px;
|
||||
padding: 0 0.5em;
|
||||
font-family: Verdana, sans-serif;
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
|
||||
@@ -124,41 +124,3 @@ $pi: 3.1416;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .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;
|
||||
// }
|
||||
|
||||
@@ -3,15 +3,17 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use '../base.scss';
|
||||
|
||||
.LabeledList {
|
||||
display: table;
|
||||
// IE8: Does not support calc
|
||||
width: 100%;
|
||||
// Compensate for negative margin
|
||||
width: calc(100% + 12px);
|
||||
width: calc(100% + 1em);
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
margin: -3px -6px;
|
||||
margin: -0.25em -0.5em;
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@@ -27,7 +29,7 @@
|
||||
.LabeledList__cell {
|
||||
display: table-cell;
|
||||
margin: 0;
|
||||
padding: 3px 6px;
|
||||
padding: 0.25em 0.5em;
|
||||
border: 0;
|
||||
text-align: left;
|
||||
vertical-align: baseline;
|
||||
@@ -36,13 +38,13 @@
|
||||
.LabeledList__label {
|
||||
width: 1%;
|
||||
white-space: nowrap;
|
||||
min-width: 60px;
|
||||
min-width: 5em;
|
||||
}
|
||||
|
||||
.LabeledList__buttons {
|
||||
width: 0.1%;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
padding-top: 1px;
|
||||
padding-top: base.em(1px);
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
@use 'sass:color';
|
||||
@use '../base.scss';
|
||||
@use '../colors.scss';
|
||||
@use '../functions.scss' as *;
|
||||
|
||||
@@ -20,8 +21,8 @@ $bg-map: colors.$bg-map !default;
|
||||
rgba(0, 0, 0, 1),
|
||||
rgba(255, 255, 255, 1));
|
||||
|
||||
padding: 4px 6px;
|
||||
margin-bottom: 6px;
|
||||
padding: 0.33em 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
box-shadow: none;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
@@ -30,9 +31,9 @@ $bg-map: colors.$bg-map !default;
|
||||
background-image: repeating-linear-gradient(
|
||||
-45deg,
|
||||
transparent,
|
||||
transparent 10px,
|
||||
$color-stripes 10px,
|
||||
$color-stripes 20px);
|
||||
transparent base.em(10px),
|
||||
$color-stripes base.em(10px),
|
||||
$color-stripes base.em(20px));
|
||||
}
|
||||
|
||||
@mixin box-color($color) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
@use 'sass:color';
|
||||
@use '../base.scss';
|
||||
@use '../functions.scss' as *;
|
||||
@use './Input.scss';
|
||||
|
||||
@@ -13,15 +14,15 @@ $border-radius: Input.$border-radius !default;
|
||||
.NumberInput {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border: 1px solid $border-color;
|
||||
border: 1px solid rgba($border-color, 0.75);
|
||||
border: base.em(1px) solid $border-color;
|
||||
border: base.em(1px) solid rgba($border-color, 0.75);
|
||||
border-radius: $border-radius;
|
||||
color: $border-color;
|
||||
background-color: #000;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
padding: 0 4px;
|
||||
margin-right: 2px;
|
||||
line-height: 17px;
|
||||
padding: 0 base.em(4px);
|
||||
margin-right: base.em(2px);
|
||||
line-height: base.em(17px);
|
||||
text-align: right;
|
||||
overflow: visible;
|
||||
cursor: n-resize;
|
||||
@@ -32,23 +33,23 @@ $border-radius: Input.$border-radius !default;
|
||||
}
|
||||
|
||||
.NumberInput__content {
|
||||
margin-left: 6px;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.NumberInput__barContainer {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
bottom: 2px;
|
||||
left: 2px;
|
||||
top: base.em(2px);
|
||||
bottom: base.em(2px);
|
||||
left: base.em(2px);
|
||||
}
|
||||
|
||||
.NumberInput__bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 3px;
|
||||
width: base.em(3px);
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid $border-color;
|
||||
border-bottom: base.em(1px) solid $border-color;
|
||||
background-color: $border-color;
|
||||
}
|
||||
|
||||
@@ -62,11 +63,11 @@ $border-radius: Input.$border-radius !default;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
height: 17px;
|
||||
font-size: base.em(12px);
|
||||
line-height: base.em(17px);
|
||||
height: base.em(17px);
|
||||
margin: 0;
|
||||
padding: 0 6px;
|
||||
padding: 0 0.5em;
|
||||
font-family: Verdana, sans-serif;
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
|
||||
@@ -16,7 +16,7 @@ $bg-map: colors.$bg-map !default;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 0 6px;
|
||||
padding: 0 0.5em;
|
||||
border-radius: $border-radius;
|
||||
background-color: $color-background;
|
||||
transition: border-color 500ms;
|
||||
@@ -24,9 +24,9 @@ $bg-map: colors.$bg-map !default;
|
||||
|
||||
.ProgressBar__fill {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
top: -0.5px;
|
||||
left: 0px;
|
||||
bottom: -0.5px;
|
||||
}
|
||||
|
||||
.ProgressBar__fill--animated {
|
||||
@@ -35,13 +35,13 @@ $bg-map: colors.$bg-map !default;
|
||||
|
||||
.ProgressBar__content {
|
||||
position: relative;
|
||||
line-height: 17px;
|
||||
line-height: base.em(17px);
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ProgressBar--color--default {
|
||||
border: 1px solid $color-default;
|
||||
border: base.em(1px) solid $color-default;
|
||||
|
||||
.ProgressBar__fill {
|
||||
background-color: $color-default;
|
||||
@@ -50,7 +50,7 @@ $bg-map: colors.$bg-map !default;
|
||||
|
||||
@each $color-name, $color-value in $bg-map {
|
||||
.ProgressBar--color--#{$color-name} {
|
||||
border: 1px solid $color-value !important;
|
||||
border: base.em(1px) solid $color-value !important;
|
||||
|
||||
.ProgressBar__fill {
|
||||
background-color: $color-value;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
@use 'sass:color';
|
||||
@use '../base.scss';
|
||||
@use '../colors.scss';
|
||||
|
||||
$color-title: #ffffff !default;
|
||||
@@ -13,7 +14,7 @@ $color-separator: colors.$primary !default;
|
||||
|
||||
.Section {
|
||||
position: relative;
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 0.5em;
|
||||
// IE8: Does not support rgba colors
|
||||
background-color: color.adjust(
|
||||
$color-background,
|
||||
@@ -30,42 +31,52 @@ $color-separator: colors.$primary !default;
|
||||
|
||||
.Section__title {
|
||||
position: relative;
|
||||
padding: 6px;
|
||||
border-bottom: 2px solid $color-separator;
|
||||
padding: 0.5em;
|
||||
border-bottom: base.em(2px) solid $color-separator;
|
||||
}
|
||||
|
||||
.Section__titleText {
|
||||
font-size: 14px;
|
||||
font-size: base.em(14px);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.Section__buttons {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
right: 6px;
|
||||
margin-top: -1px;
|
||||
right: 0.5em;
|
||||
margin-top: base.em(-1px);
|
||||
}
|
||||
|
||||
.Section__content {
|
||||
padding: 8px 6px;
|
||||
padding: 0.66em 0.5em;
|
||||
}
|
||||
|
||||
.Section--fill {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Section--fill .Section__content {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.Section--level--1 .Section__titleText {
|
||||
font-size: 14px;
|
||||
font-size: base.em(14px);
|
||||
}
|
||||
|
||||
.Section--level--2 .Section__titleText {
|
||||
font-size: 13px;
|
||||
font-size: base.em(13px);
|
||||
}
|
||||
|
||||
.Section--level--3 .Section__titleText {
|
||||
font-size: 12px;
|
||||
font-size: base.em(12px);
|
||||
}
|
||||
|
||||
.Section--level--2,
|
||||
.Section--level--3 {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
margin-left: -6px;
|
||||
margin-right: -6px;
|
||||
margin-left: -0.5em;
|
||||
margin-right: -0.5em;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use '../base.scss';
|
||||
|
||||
.Slider {
|
||||
cursor: e-resize;
|
||||
}
|
||||
@@ -18,28 +20,29 @@
|
||||
.Slider__cursor {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -1px;
|
||||
right: base.em(-1px);
|
||||
bottom: 0;
|
||||
width: 0;
|
||||
border-left: 2px solid #fff;
|
||||
border-left: base.em(2px) solid #fff;
|
||||
}
|
||||
|
||||
.Slider__pointer {
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
bottom: -4px;
|
||||
right: base.em(-5px);
|
||||
bottom: base.em(-4px);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-bottom: 5px solid #fff;
|
||||
border-left: base.em(5px) solid transparent;
|
||||
border-right: base.em(5px) solid transparent;
|
||||
border-bottom: base.em(5px) solid #fff;
|
||||
}
|
||||
|
||||
.Slider__popupValue {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -22px;
|
||||
padding: 2px 4px;
|
||||
top: -2rem;
|
||||
font-size: 1rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background-color: #000;
|
||||
transform: translateX(50%);
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
.Table__cell {
|
||||
display: table-cell;
|
||||
padding: 0 3px;
|
||||
padding: 0 0.25em;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
@@ -35,7 +35,7 @@
|
||||
.Table__row--header .Table__cell,
|
||||
.Table__cell--header {
|
||||
font-weight: bold;
|
||||
padding-bottom: 6px;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.Table__cell--collapsing {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
$border-radius: base.$border-radius !default;
|
||||
|
||||
.Tabs--horizontal {
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
||||
margin-bottom: 6px;
|
||||
border-bottom: base.em(2px) solid rgba(255, 255, 255, 0.1);
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
.Tabs__tab--altSelection::after {
|
||||
content: '';
|
||||
@@ -17,7 +17,7 @@ $border-radius: base.$border-radius !default;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
height: base.em(2px);
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: $border-radius;
|
||||
@@ -25,11 +25,10 @@ $border-radius: base.$border-radius !default;
|
||||
}
|
||||
|
||||
.Tabs--vertical {
|
||||
margin-right: 9px;
|
||||
margin-right: 0.75em;
|
||||
|
||||
.Tabs__tabBox {
|
||||
// padding-right: 2px;
|
||||
border-right: 2px solid rgba(255, 255, 255, 0.1);
|
||||
border-right: base.em(2px) solid rgba(255, 255, 255, 0.1);
|
||||
// Disable baseline alignment when doing vertical tabs
|
||||
vertical-align: top;
|
||||
}
|
||||
@@ -40,8 +39,8 @@ $border-radius: base.$border-radius !default;
|
||||
// Override to stop themed buttons from taking priority over it.
|
||||
margin-right: 0 !important;
|
||||
margin-bottom: 0;
|
||||
padding: 1px 9px 0px 6px;
|
||||
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
|
||||
padding: base.em(1px) base.em(9px) 0px 0.5em;
|
||||
border-bottom: base.em(2px) solid rgba(255, 255, 255, 0.1);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
@@ -55,7 +54,7 @@ $border-radius: base.$border-radius !default;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 3px;
|
||||
width: 0.25em;
|
||||
background-color: #fff;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
@@ -3,96 +3,96 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use '../base.scss';
|
||||
@use '../functions.scss' as *;
|
||||
@use '../base.scss';
|
||||
@use '../functions.scss' as *;
|
||||
|
||||
$text-color: #272727 !default;
|
||||
$border-color: #88bfff !default;
|
||||
$border-radius: base.$border-radius !default;
|
||||
$color-background: #bb9b68 !default;
|
||||
$color-border: #272727 !default;
|
||||
$text-color: #272727 !default;
|
||||
$border-color: #88bfff !default;
|
||||
$border-radius: base.$border-radius !default;
|
||||
$color-background: #bb9b68 !default;
|
||||
$color-border: #272727 !default;
|
||||
|
||||
.TextArea {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: "100%";
|
||||
border: 1px solid $border-color;
|
||||
border: 1px solid rgba($border-color, 0.75);
|
||||
border-radius: $border-radius;
|
||||
background-color: $color-background;
|
||||
margin-right: 2px;
|
||||
line-height: 17px;
|
||||
overflow: visible;
|
||||
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box; /* Firefox, other Gecko */
|
||||
box-sizing: border-box; /* Opera/IE 8+ */
|
||||
width:100%;
|
||||
.TextArea {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: "100%";
|
||||
border: 1px solid $border-color;
|
||||
border: 1px solid rgba($border-color, 0.75);
|
||||
border-radius: $border-radius;
|
||||
background-color: $color-background;
|
||||
margin-right: 2px;
|
||||
line-height: 17px;
|
||||
overflow: visible;
|
||||
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box; /* Firefox, other Gecko */
|
||||
box-sizing: border-box; /* Opera/IE 8+ */
|
||||
width:100%;
|
||||
|
||||
|
||||
|
||||
.TextArea_filler {
|
||||
word-wrap: break-word; /* make sure the div and the textarea wrap words in the same way */
|
||||
box-sizing: border-box;
|
||||
padding: 2px;
|
||||
width: 100%;
|
||||
.TextArea_filler {
|
||||
word-wrap: break-word; /* make sure the div and the textarea wrap words in the same way */
|
||||
box-sizing: border-box;
|
||||
padding: 2px;
|
||||
width: 100%;
|
||||
|
||||
padding-bottom: 1.5em; /* A bit more than one additional line of text. */
|
||||
visibility: hidden;
|
||||
}
|
||||
padding-bottom: 1.5em; /* A bit more than one additional line of text. */
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.TextArea--fluid {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
.TextArea--fluid {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.TextArea__baseline {
|
||||
display: inline-block;
|
||||
color: transparent;
|
||||
}
|
||||
.TextArea__baseline {
|
||||
display: inline-block;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.TextArea__textarea {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
height: 17px;
|
||||
margin: 0;
|
||||
padding: 0 6px;
|
||||
font-family: inherit;
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
.TextArea__textarea {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
height: 17px;
|
||||
margin: 0;
|
||||
padding: 0 6px;
|
||||
font-family: inherit;
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
|
||||
/* Cut and paste to auto resize the textarea */
|
||||
word-wrap: break-word; /* make sure the div and the textarea wrap words in the same way */
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box; /* Firefox, other Gecko */
|
||||
box-sizing: border-box; /* Opera/IE 8+ */
|
||||
/* Cut and paste to auto resize the textarea */
|
||||
word-wrap: break-word; /* make sure the div and the textarea wrap words in the same way */
|
||||
box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box; /* Firefox, other Gecko */
|
||||
box-sizing: border-box; /* Opera/IE 8+ */
|
||||
|
||||
padding: 2px;
|
||||
width: 100%;
|
||||
padding: 2px;
|
||||
width: 100%;
|
||||
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
|
||||
&:-ms-input-placeholder {
|
||||
font-style: italic;
|
||||
color: #777;
|
||||
/* color: rgba(255, 255, 255, 0.45);*/
|
||||
}
|
||||
}
|
||||
&:-ms-input-placeholder {
|
||||
font-style: italic;
|
||||
color: #777;
|
||||
/* color: rgba(255, 255, 255, 0.45);*/
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ $border-radius: base.$border-radius !default;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
z-index: 2;
|
||||
padding: 6px 10px;
|
||||
padding: 0.5em 0.75em;
|
||||
transform: translateX(-50%);
|
||||
pointer-events: none;
|
||||
visibility: hidden;
|
||||
@@ -32,7 +32,7 @@ $border-radius: base.$border-radius !default;
|
||||
content: attr(data-tooltip);
|
||||
transition: all 150ms;
|
||||
background-color: $color-background;
|
||||
box-shadow: 1px 1px 15px -1px rgba(0, 0, 0, 0.5);
|
||||
box-shadow: 0.1em 0.1em 1.25em -0.1em rgba(0, 0, 0, 0.5);
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ $border-radius: base.$border-radius !default;
|
||||
|
||||
.Tooltip--long {
|
||||
&::after {
|
||||
width: 250px;
|
||||
width: base.em(250px);
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
@@ -54,59 +54,59 @@ $border-radius: base.$border-radius !default;
|
||||
.Tooltip--top::after {
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(8px);
|
||||
transform: translateX(-50%) translateY(0.5em);
|
||||
}
|
||||
|
||||
.Tooltip--top:hover::after {
|
||||
transform: translateX(-50%) translateY(-8px);
|
||||
transform: translateX(-50%) translateY(-0.5em);
|
||||
}
|
||||
|
||||
.Tooltip--bottom::after {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-8px);
|
||||
transform: translateX(-50%) translateY(-0.5em);
|
||||
}
|
||||
|
||||
.Tooltip--bottom:hover::after {
|
||||
transform: translateX(-50%) translateY(8px);
|
||||
transform: translateX(-50%) translateY(0.5em);
|
||||
}
|
||||
|
||||
.Tooltip--bottom-left::after {
|
||||
top: 100%;
|
||||
right: 50%;
|
||||
transform: translateX(12px) translateY(-8px);
|
||||
transform: translateX(12px) translateY(-0.5em);
|
||||
}
|
||||
|
||||
.Tooltip--bottom-left:hover::after {
|
||||
transform: translateX(12px) translateY(8px);
|
||||
transform: translateX(12px) translateY(0.5em);
|
||||
}
|
||||
|
||||
.Tooltip--bottom-right::after {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-12px) translateY(-8px);
|
||||
transform: translateX(-12px) translateY(-0.5em);
|
||||
}
|
||||
|
||||
.Tooltip--bottom-right:hover::after {
|
||||
transform: translateX(-12px) translateY(8px);
|
||||
transform: translateX(-12px) translateY(0.5em);
|
||||
}
|
||||
|
||||
.Tooltip--left::after {
|
||||
top: 50%;
|
||||
right: 100%;
|
||||
transform: translateX(8px) translateY(-50%);
|
||||
transform: translateX(0.5em) translateY(-50%);
|
||||
}
|
||||
|
||||
.Tooltip--left:hover::after {
|
||||
transform: translateX(-8px) translateY(-50%);
|
||||
transform: translateX(-0.5em) translateY(-50%);
|
||||
}
|
||||
|
||||
.Tooltip--right::after {
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
transform: translateX(-8px) translateY(-50%);
|
||||
transform: translateX(-0.5em) translateY(-50%);
|
||||
}
|
||||
|
||||
.Tooltip--right:hover::after {
|
||||
transform: translateX(8px) translateY(-50%);
|
||||
transform: translateX(0.5em) translateY(-50%);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@use '../base.scss';
|
||||
|
||||
$color-background: rgba(0, 0, 0, 0.33) !default;
|
||||
|
||||
.CameraConsole__left {
|
||||
@@ -5,14 +7,14 @@ $color-background: rgba(0, 0, 0, 0.33) !default;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 220px;
|
||||
width: base.em(220px);
|
||||
}
|
||||
|
||||
.CameraConsole__right {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 220px;
|
||||
left: base.em(220px);
|
||||
right: 0;
|
||||
background-color: $color-background;
|
||||
}
|
||||
@@ -22,33 +24,30 @@ $color-background: rgba(0, 0, 0, 0.33) !default;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
margin: 3px 12px 0;
|
||||
// background-color: #0a0;
|
||||
height: 2em;
|
||||
line-height: 2em;
|
||||
margin: 0.25em 1em 0;
|
||||
}
|
||||
|
||||
.CameraConsole__toolbarRight {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
margin: 4px 6px 0;
|
||||
// background-color: #aa0;
|
||||
height: 2em;
|
||||
line-height: 2em;
|
||||
margin: 0.33em 0.5em 0;
|
||||
}
|
||||
|
||||
.CameraConsole__map {
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
top: base.em(26px);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
// background-color: #00a;
|
||||
margin: 6px;
|
||||
margin: 0.5em;
|
||||
text-align: center;
|
||||
|
||||
.NoticeBox {
|
||||
margin-top: calc(50% - 24px);
|
||||
margin-top: calc(50% - 2em);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,18 @@ $background-beige: #E8E4C9;
|
||||
|
||||
.NuclearBomb__displayBox {
|
||||
background-color: #002003;
|
||||
border: 4px inset $background-beige;
|
||||
border: 0.167em inset $background-beige;
|
||||
color: #03e017;
|
||||
font-size: 24px;
|
||||
font-size: 2em;
|
||||
font-family: monospace;
|
||||
padding: 6px;
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.NuclearBomb__Button {
|
||||
outline-width: 0.25rem !important;
|
||||
border-width: 0.65rem !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.NuclearBomb__Button--keypad {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@use '../base.scss';
|
||||
@use '../colors.scss';
|
||||
|
||||
.Roulette {
|
||||
|
||||
@@ -6,16 +6,8 @@
|
||||
@use 'sass:color';
|
||||
@use '../base.scss';
|
||||
|
||||
.Layout__content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
margin-bottom: -6px;
|
||||
|
||||
.Layout,
|
||||
.Layout * {
|
||||
// Fancy scrollbar
|
||||
scrollbar-base-color: color.scale(
|
||||
base.$color-bg,
|
||||
@@ -40,6 +32,16 @@
|
||||
$lightness: 10%);
|
||||
}
|
||||
|
||||
.Layout__content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.Layout__content--scrollable {
|
||||
overflow-y: scroll;
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -5,16 +5,16 @@
|
||||
|
||||
.NtosHeader__left {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
left: 1em;
|
||||
}
|
||||
|
||||
.NtosHeader__right {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
right: 1em;
|
||||
}
|
||||
|
||||
.NtosHeader__icon {
|
||||
margin-top: -9px;
|
||||
margin-bottom: -6px;
|
||||
margin-top: -0.75em;
|
||||
margin-bottom: -0.5em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -3,22 +3,24 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use '../base.scss';
|
||||
|
||||
.NtosWindow__header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 28px;
|
||||
line-height: 27px;
|
||||
height: 2em;
|
||||
line-height: 1.928em;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
font-family: Consolas, monospace;
|
||||
font-size: 14px;
|
||||
font-size: base.em(14px);
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.NtosWindow__content .Layout__content {
|
||||
margin-top: 28px;
|
||||
margin-top: 2em;
|
||||
font-family: Consolas, monospace;
|
||||
font-size: 14px;
|
||||
font-size: base.em(14px);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
*/
|
||||
|
||||
@use 'sass:color';
|
||||
@use '../base.scss';
|
||||
@use '../colors.scss';
|
||||
|
||||
$color-text: rgba(255, 255, 255, 0.75) !default;
|
||||
$color-background: #363636 !default;
|
||||
@@ -14,6 +16,7 @@ $color-shadow: rgba(0, 0, 0, 0.1) !default;
|
||||
background-color: $color-background;
|
||||
border-bottom: 1px solid $color-shadow-core;
|
||||
box-shadow: 0 2px 2px $color-shadow;
|
||||
box-shadow: 0 base.rem(2px) base.rem(2px) $color-shadow;
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
@@ -34,9 +37,12 @@ $color-shadow: rgba(0, 0, 0, 0.1) !default;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 46px;
|
||||
left: base.rem(46px);
|
||||
color: $color-text;
|
||||
font-size: 14px;
|
||||
font-size: base.rem(14px);
|
||||
line-height: 31px;
|
||||
line-height: base.rem(31px);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -46,21 +52,19 @@ $color-shadow: rgba(0, 0, 0, 0.1) !default;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 32px;
|
||||
height: base.rem(32px);
|
||||
}
|
||||
|
||||
.TitleBar__statusIcon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 12px;
|
||||
left: base.rem(12px);
|
||||
transition: color 0.5s;
|
||||
font-size: 20px;
|
||||
font-size: base.rem(20px);
|
||||
line-height: 32px !important;
|
||||
}
|
||||
|
||||
.TitleBar__minimize {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 46px;
|
||||
line-height: base.rem(32px) !important;
|
||||
}
|
||||
|
||||
.TitleBar__close {
|
||||
@@ -68,8 +72,27 @@ $color-shadow: rgba(0, 0, 0, 0.1) !default;
|
||||
top: -1px;
|
||||
right: 0;
|
||||
width: 45px;
|
||||
width: base.rem(45px);
|
||||
height: 32px;
|
||||
height: base.rem(32px);
|
||||
font-size: 20px;
|
||||
font-size: base.rem(20px);
|
||||
line-height: 31px;
|
||||
line-height: base.rem(31px);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.TitleBar__devBuildIndicator {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
top: base.rem(6px);
|
||||
right: 52px;
|
||||
right: base.rem(52px);
|
||||
min-width: 20px;
|
||||
min-width: base.rem(20px);
|
||||
padding: 2px 4px;
|
||||
padding: base.rem(2px) base.rem(4px);
|
||||
background-color: rgba(colors.$good, 0.75);
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -27,17 +27,36 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
height: base.rem(32px);
|
||||
}
|
||||
|
||||
// Everything after the title bar
|
||||
.Window__rest {
|
||||
position: fixed;
|
||||
top: 32px;
|
||||
top: base.rem(32px);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.Window__contentPadding {
|
||||
margin: 0.5rem;
|
||||
// 0.01 is needed to make the scrollbar not appear
|
||||
// due to rem rendering inaccuracies in IE11.
|
||||
height: calc(100% - 1.01rem);
|
||||
}
|
||||
|
||||
.Window__contentPadding:after {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.Layout__content--scrollable .Window__contentPadding:after {
|
||||
display: block;
|
||||
content: '';
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
.Window__dimmer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -48,25 +67,14 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.Window__toast {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
font-size: 12px;
|
||||
height: 40px;
|
||||
line-height: 39px;
|
||||
padding: 0 12px;
|
||||
background-color: color.scale(base.$color-bg, $lightness: -50%);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.Window__resizeHandle__se {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
width: base.rem(20px);
|
||||
height: 20px;
|
||||
height: base.rem(20px);
|
||||
cursor: se-resize;
|
||||
}
|
||||
|
||||
@@ -76,6 +84,7 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 6px;
|
||||
height: base.rem(6px);
|
||||
cursor: s-resize;
|
||||
}
|
||||
|
||||
@@ -85,5 +94,6 @@
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 3px;
|
||||
width: base.rem(3px);
|
||||
cursor: e-resize;
|
||||
}
|
||||
|
||||
@@ -13,10 +13,7 @@
|
||||
@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');
|
||||
|
||||
// Components
|
||||
@@ -39,6 +36,7 @@
|
||||
@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/TextArea.scss');
|
||||
@include meta.load-css('./components/Tooltip.scss');
|
||||
|
||||
// Interfaces
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use './base.scss';
|
||||
|
||||
html, body {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
font-size: base.$font-size;
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -28,22 +30,27 @@ h1, h2, h3, h4, h5, h6 {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 6px 0;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
font-size: 1.333rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
font-size: 1.167rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 12px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
td, th {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use 'sass:color';
|
||||
@use 'sass:meta';
|
||||
|
||||
@use '../colors.scss' with (
|
||||
$primary: #ad2350,
|
||||
$fg-map-keys: (),
|
||||
$bg-map-keys: (),
|
||||
);
|
||||
@use '../base.scss' with (
|
||||
$color-bg: #2a314a,
|
||||
$color-bg-grad-spread: 6%,
|
||||
$border-radius: 2px,
|
||||
);
|
||||
|
||||
.theme-abductor {
|
||||
// Atomic classes
|
||||
@include meta.load-css('../atomic/color.scss');
|
||||
|
||||
// Components
|
||||
@include meta.load-css('../components/Button.scss', $with: (
|
||||
'color-default': colors.$primary,
|
||||
'color-disabled': #363636,
|
||||
'color-selected': #465899,
|
||||
'color-caution': #be6209,
|
||||
'color-danger': #9a9d00,
|
||||
));
|
||||
@include meta.load-css('../components/Input.scss', $with: (
|
||||
'border-color': #404b6e,
|
||||
));
|
||||
@include meta.load-css('../components/NoticeBox.scss', $with: (
|
||||
'color-background': #a82d55,
|
||||
));
|
||||
@include meta.load-css('../components/NumberInput.scss', $with: (
|
||||
'border-color': #404b6e,
|
||||
));
|
||||
@include meta.load-css('../components/ProgressBar.scss', $with: (
|
||||
'color-background': rgba(0, 0, 0, 0.5),
|
||||
));
|
||||
@include meta.load-css('../components/Section.scss');
|
||||
@include meta.load-css('../components/Tooltip.scss', $with: (
|
||||
'color-background': #a82d55,
|
||||
));
|
||||
|
||||
// Layouts
|
||||
@include meta.load-css('../layouts/Layout.scss');
|
||||
@include meta.load-css('../layouts/Window.scss');
|
||||
@include meta.load-css('../layouts/TitleBar.scss', $with: (
|
||||
'color-background': #9e1b46,
|
||||
));
|
||||
|
||||
.Layout__content {
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
@use '../base.scss' with (
|
||||
$color-bg: #117039,
|
||||
$color-bg-grad-spread: 0%,
|
||||
$border-radius: 0px,
|
||||
$border-radius: 0,
|
||||
);
|
||||
|
||||
//Made for the roulette table, probably requires a bunch of manual hacks to work for anything else
|
||||
@@ -31,7 +31,7 @@
|
||||
'color-danger': #9a9d00,
|
||||
));
|
||||
@include meta.load-css('../components/NumberInput.scss', $with: (
|
||||
'border-color': #FFFFFF,
|
||||
'border-color': #fff,
|
||||
));
|
||||
@include meta.load-css('../components/ProgressBar.scss', $with: (
|
||||
'color-background': rgba(0, 0, 0, 0.5),
|
||||
@@ -46,8 +46,6 @@
|
||||
));
|
||||
|
||||
.Button {
|
||||
border-color: #FFFFFF;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border: base.em(2px) solid #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
@use '../base.scss' with (
|
||||
$color-bg: #121b12,
|
||||
$color-bg-grad-spread: 0%,
|
||||
$border-radius: 2px,
|
||||
);
|
||||
|
||||
.theme-hackerman {
|
||||
@@ -46,10 +45,10 @@
|
||||
|
||||
.Button {
|
||||
font-family: monospace;
|
||||
border-width: 2px;
|
||||
border-width: base.em(2px);
|
||||
border-style: outset;
|
||||
border-color: #00AA00;
|
||||
outline: 1px solid rgb(0, 122, 0);
|
||||
outline: base.em(1px) solid rgb(0, 122, 0);
|
||||
}
|
||||
|
||||
.candystripe:nth-child(odd) {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
@use '../base.scss' with (
|
||||
$color-bg: #1b3443,
|
||||
$color-bg-grad-spread: 6%,
|
||||
$border-radius: 2px,
|
||||
);
|
||||
|
||||
.theme-malfunction {
|
||||
|
||||
@@ -14,7 +14,6 @@ $nanotrasen: #384e68;
|
||||
);
|
||||
@use '../base.scss' with (
|
||||
$color-bg: color.scale($nanotrasen, $lightness: -45%),
|
||||
$border-radius: 2px,
|
||||
);
|
||||
|
||||
.theme-ntos {
|
||||
|
||||
@@ -1,52 +1,171 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Paul Bruner
|
||||
* @license MIT
|
||||
* Copyright (c) 2020 Paul Bruner
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use 'sass:color';
|
||||
@use 'sass:meta';
|
||||
@use 'sass:color';
|
||||
@use 'sass:meta';
|
||||
|
||||
@use '../colors.scss' with (
|
||||
$primary: #ffffff,
|
||||
$fg-map-keys: (),
|
||||
$bg-map-keys: (),
|
||||
);
|
||||
@use '../base.scss' with (
|
||||
$color-fg: rgb(0,0,0),
|
||||
$color-bg: rgb(255, 255, 255),
|
||||
$color-bg-grad-spread: 0%,
|
||||
$border-radius: 0px,
|
||||
);
|
||||
@use '../colors.scss' with (
|
||||
$primary: #ffffff,
|
||||
$bg-lightness: -25%,
|
||||
$fg-lightness: -10%,
|
||||
// Commenting out color maps will adjust all colors based on the lightness
|
||||
// settings above, but will add extra 10KB to the theme.
|
||||
$fg-map-keys: (),
|
||||
$bg-map-keys: (),
|
||||
);
|
||||
@use '../base.scss' with (
|
||||
$color-fg: #000000,
|
||||
$color-bg: #ffffff,
|
||||
$color-bg-grad-spread: 0%,
|
||||
);
|
||||
|
||||
// A fat warning to anyone who wants to use this: this only half works.
|
||||
// It was made almost purely for the nuke ui, and requires a good amount of manual hacks to get it working as intended.
|
||||
.theme-paper {
|
||||
// Atomic classes
|
||||
@include meta.load-css('../atomic/color.scss');
|
||||
$font-size: 24px;
|
||||
|
||||
// Components
|
||||
@include meta.load-css('../components/Tabs.scss');
|
||||
// A fat warning to anyone who wants to use this: this only half works.
|
||||
// It was made almost purely for the nuke ui, and requires a good amount of manual hacks to get it working as intended.
|
||||
.theme-paper {
|
||||
// Atomic classes
|
||||
@include meta.load-css('../atomic/color.scss');
|
||||
|
||||
// Components
|
||||
@include meta.load-css('../components/Tabs.scss');
|
||||
@include meta.load-css('../components/Section.scss', $with: (
|
||||
'color-background': rgba(0, 0, 0, 0.1),
|
||||
'color-shadow': rgba(0, 0, 0, 0.2),
|
||||
));
|
||||
@include meta.load-css('../components/Button.scss', $with: (
|
||||
'color-default': #E8E4C9,
|
||||
'color-disabled': #363636,
|
||||
'color-selected': #9d0808,
|
||||
'color-caution': #be6209,
|
||||
'color-danger': #9a9d00,
|
||||
'color-transparent-text': rgba(0, 0, 0, 0.5),
|
||||
));
|
||||
|
||||
@include meta.load-css('../components/Button.scss', $with: (
|
||||
'color-default': #E8E4C9,
|
||||
'color-disabled': #363636,
|
||||
'color-selected': #9d0808,
|
||||
'color-caution': #be6209,
|
||||
'color-danger': #9a9d00,
|
||||
));
|
||||
// Layouts
|
||||
@include meta.load-css('../layouts/Layout.scss');
|
||||
// Layouts
|
||||
@include meta.load-css('../layouts/Layout.scss');
|
||||
@include meta.load-css('../layouts/Window.scss');
|
||||
@include meta.load-css('../layouts/TitleBar.scss', $with: (
|
||||
'color-text': rgba(0, 0, 0, 0.75),
|
||||
'color-background': base.$color-bg,
|
||||
'color-shadow-core': rgba(0, 0, 0, 0.25),
|
||||
));
|
||||
|
||||
@include meta.load-css('../layouts/Window.scss');
|
||||
.PaperInput {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-bottom: 1px solid #000;
|
||||
outline: none;
|
||||
background-color:rgba(255, 255, 62, 0.8);
|
||||
padding: 0 4px;
|
||||
margin-right: 2px;
|
||||
line-height: 17px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
@include meta.load-css('../layouts/TitleBar.scss', $with: (
|
||||
'color-background': #ffffff,
|
||||
));
|
||||
.PaperInput__baseline {
|
||||
display: inline-block;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.Layout__content {
|
||||
background-image: none;
|
||||
}
|
||||
.PaperInput__input {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 17px;
|
||||
height: 17px;
|
||||
margin: 0;
|
||||
padding: 0 6px;
|
||||
font-family: Verdana, sans-serif;
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
color: inherit;
|
||||
|
||||
}
|
||||
&:-ms-input-placeholder {
|
||||
font-style: italic;
|
||||
color: #777;
|
||||
color: rgba(255, 255, 255, 0.45);
|
||||
}
|
||||
}
|
||||
|
||||
.Layout__content {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.Window {
|
||||
background-image: none;
|
||||
color: base.$color-fg;
|
||||
}
|
||||
|
||||
.paper-text {
|
||||
input:disabled {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-bottom: 1px solid #000;
|
||||
outline: none;
|
||||
background-color:rgba(255, 255, 62, 0.8);
|
||||
padding: 0 4px;
|
||||
margin-right: 2px;
|
||||
line-height: 17px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
input {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-bottom: 1px solid #000;
|
||||
outline: none;
|
||||
background-color:rgba(255, 255, 62, 0.8);
|
||||
padding: 0 4px;
|
||||
margin-right: 2px;
|
||||
line-height: 17px;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.paper-field {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-bottom: 1px solid #000;
|
||||
outline: none;
|
||||
background-color:rgba(255, 255, 62, 0.8);
|
||||
padding: 0 4px;
|
||||
margin-right: 2px;
|
||||
line-height: 17px;
|
||||
overflow: visible;
|
||||
|
||||
input:disabled {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-bottom: 1px solid #000;
|
||||
outline: none;
|
||||
background-color:rgba(255, 255, 62, 0.8);
|
||||
padding: 0 4px;
|
||||
margin-right: 2px;
|
||||
line-height: 17px;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
@use '../base.scss' with (
|
||||
$color-bg: #E8E4C9,
|
||||
$color-bg-grad-spread: 0%,
|
||||
$border-radius: 0px,
|
||||
$border-radius: 0,
|
||||
);
|
||||
|
||||
// A fat warning to anyone who wants to use this: this only half works.
|
||||
@@ -46,10 +46,8 @@
|
||||
.Button {
|
||||
font-family: monospace;
|
||||
color: #161613;
|
||||
border-width: 8px;
|
||||
border-style: outset;
|
||||
border-color: #E8E4C9;
|
||||
outline: 3px solid #161613;
|
||||
border: base.em(2px) outset #E8E4C9;
|
||||
outline: base.em(1px) solid #161613;
|
||||
}
|
||||
|
||||
.Layout__content {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
@use '../base.scss' with (
|
||||
$color-bg: #550202,
|
||||
$color-bg-grad-spread: 6%,
|
||||
$border-radius: 2px,
|
||||
);
|
||||
|
||||
.theme-syndicate {
|
||||
|
||||
Reference in New Issue
Block a user