frontend
@@ -4,7 +4,7 @@ const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
@@ -4,7 +4,7 @@ const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eslint",
|
||||
"version": "7.21.0-pnpify",
|
||||
"version": "7.32.0-sdk",
|
||||
"main": "./lib/api.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This file is automatically generated by PnPify.
|
||||
# Manual changes will be lost!
|
||||
# This file is automatically generated by @yarnpkg/sdks.
|
||||
# Manual changes might be lost!
|
||||
|
||||
integrations:
|
||||
- vscode
|
||||
|
||||
@@ -4,7 +4,7 @@ const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
@@ -4,7 +4,7 @@ const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
@@ -4,7 +4,7 @@ const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
@@ -4,15 +4,22 @@ const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
const moduleWrapper = tsserver => {
|
||||
if (!process.versions.pnp) {
|
||||
return tsserver;
|
||||
}
|
||||
|
||||
const {isAbsolute} = require(`path`);
|
||||
const pnpApi = require(`pnpapi`);
|
||||
|
||||
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||
|
||||
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||
return `${locator.name}@${locator.reference}`;
|
||||
}));
|
||||
@@ -23,9 +30,9 @@ const moduleWrapper = tsserver => {
|
||||
|
||||
function toEditorPath(str) {
|
||||
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||
if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || str.match(/\$\$virtual\//))) {
|
||||
if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || isVirtual(str))) {
|
||||
// We also take the opportunity to turn virtual paths into physical ones;
|
||||
// this makes is much easier to work with workspaces that list peer
|
||||
// this makes it much easier to work with workspaces that list peer
|
||||
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||
// file instances instead of the real ones.
|
||||
//
|
||||
@@ -34,26 +41,49 @@ const moduleWrapper = tsserver => {
|
||||
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||
// errors on react).
|
||||
//
|
||||
const resolved = pnpApi.resolveVirtual(str);
|
||||
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||
if (resolved) {
|
||||
const locator = pnpApi.findPackageLocator(resolved);
|
||||
if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) {
|
||||
str = resolved;
|
||||
str = resolved;
|
||||
}
|
||||
}
|
||||
|
||||
str = str.replace(/\\/g, `/`)
|
||||
str = str.replace(/^\/?/, `/`);
|
||||
str = normalize(str);
|
||||
|
||||
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||
// VSCode only adds it automatically for supported schemes,
|
||||
// so we have to do it manually for the `zip` scheme.
|
||||
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||
//
|
||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||
//
|
||||
if (str.match(/\.zip\//)) {
|
||||
str = `${isVSCode ? `^` : ``}zip:${str}`;
|
||||
switch (hostInfo) {
|
||||
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||
// VSCode only adds it automatically for supported schemes,
|
||||
// so we have to do it manually for the `zip` scheme.
|
||||
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||
//
|
||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||
//
|
||||
case `vscode`: {
|
||||
str = `^zip:${str}`;
|
||||
} break;
|
||||
|
||||
// To make "go to definition" work,
|
||||
// We have to resolve the actual file system path from virtual path
|
||||
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
||||
case `coc-nvim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = resolve(`zipfile:${str}`);
|
||||
} break;
|
||||
|
||||
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
||||
// We have to resolve the actual file system path from virtual path,
|
||||
// everything else is up to neovim
|
||||
case `neovim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = `zipfile:${str}`;
|
||||
} break;
|
||||
|
||||
default: {
|
||||
str = `zip:${str}`;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,15 +96,29 @@ const moduleWrapper = tsserver => {
|
||||
: str.replace(/^\^?zip:/, ``);
|
||||
}
|
||||
|
||||
// Force enable 'allowLocalPluginLoads'
|
||||
// TypeScript tries to resolve plugins using a path relative to itself
|
||||
// which doesn't work when using the global cache
|
||||
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
|
||||
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
|
||||
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
||||
// https://github.com/microsoft/vscode/issues/45856
|
||||
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
||||
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
||||
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
||||
this.projectService.allowLocalPluginLoads = true;
|
||||
return originalEnablePluginsWithOptions.apply(this, arguments);
|
||||
};
|
||||
|
||||
// And here is the point where we hijack the VSCode <-> TS communications
|
||||
// by adding ourselves in the middle. We locate everything that looks
|
||||
// like an absolute path of ours and normalize it.
|
||||
|
||||
const Session = tsserver.server.Session;
|
||||
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||
let isVSCode = false;
|
||||
let hostInfo = `unknown`;
|
||||
|
||||
return Object.assign(Session.prototype, {
|
||||
Object.assign(Session.prototype, {
|
||||
onMessage(/** @type {string} */ message) {
|
||||
const parsedMessage = JSON.parse(message)
|
||||
|
||||
@@ -82,9 +126,9 @@ const moduleWrapper = tsserver => {
|
||||
parsedMessage != null &&
|
||||
typeof parsedMessage === `object` &&
|
||||
parsedMessage.arguments &&
|
||||
parsedMessage.arguments.hostInfo === `vscode`
|
||||
typeof parsedMessage.arguments.hostInfo === `string`
|
||||
) {
|
||||
isVSCode = true;
|
||||
hostInfo = parsedMessage.arguments.hostInfo;
|
||||
}
|
||||
|
||||
return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => {
|
||||
@@ -98,6 +142,8 @@ const moduleWrapper = tsserver => {
|
||||
})));
|
||||
}
|
||||
});
|
||||
|
||||
return tsserver;
|
||||
};
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
const moduleWrapper = tsserver => {
|
||||
if (!process.versions.pnp) {
|
||||
return tsserver;
|
||||
}
|
||||
|
||||
const {isAbsolute} = require(`path`);
|
||||
const pnpApi = require(`pnpapi`);
|
||||
|
||||
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||
|
||||
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||
return `${locator.name}@${locator.reference}`;
|
||||
}));
|
||||
|
||||
// VSCode sends the zip paths to TS using the "zip://" prefix, that TS
|
||||
// doesn't understand. This layer makes sure to remove the protocol
|
||||
// before forwarding it to TS, and to add it back on all returned paths.
|
||||
|
||||
function toEditorPath(str) {
|
||||
// We add the `zip:` prefix to both `.zip/` paths and virtual paths
|
||||
if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || isVirtual(str))) {
|
||||
// We also take the opportunity to turn virtual paths into physical ones;
|
||||
// this makes it much easier to work with workspaces that list peer
|
||||
// dependencies, since otherwise Ctrl+Click would bring us to the virtual
|
||||
// file instances instead of the real ones.
|
||||
//
|
||||
// We only do this to modules owned by the the dependency tree roots.
|
||||
// This avoids breaking the resolution when jumping inside a vendor
|
||||
// with peer dep (otherwise jumping into react-dom would show resolution
|
||||
// errors on react).
|
||||
//
|
||||
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||
if (resolved) {
|
||||
const locator = pnpApi.findPackageLocator(resolved);
|
||||
if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) {
|
||||
str = resolved;
|
||||
}
|
||||
}
|
||||
|
||||
str = normalize(str);
|
||||
|
||||
if (str.match(/\.zip\//)) {
|
||||
switch (hostInfo) {
|
||||
// Absolute VSCode `Uri.fsPath`s need to start with a slash.
|
||||
// VSCode only adds it automatically for supported schemes,
|
||||
// so we have to do it manually for the `zip` scheme.
|
||||
// The path needs to start with a caret otherwise VSCode doesn't handle the protocol
|
||||
//
|
||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||
//
|
||||
case `vscode`: {
|
||||
str = `^zip:${str}`;
|
||||
} break;
|
||||
|
||||
// To make "go to definition" work,
|
||||
// We have to resolve the actual file system path from virtual path
|
||||
// and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip)
|
||||
case `coc-nvim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = resolve(`zipfile:${str}`);
|
||||
} break;
|
||||
|
||||
// Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server)
|
||||
// We have to resolve the actual file system path from virtual path,
|
||||
// everything else is up to neovim
|
||||
case `neovim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = `zipfile:${str}`;
|
||||
} break;
|
||||
|
||||
default: {
|
||||
str = `zip:${str}`;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function fromEditorPath(str) {
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^\^?zip:\//, ``)
|
||||
: str.replace(/^\^?zip:/, ``);
|
||||
}
|
||||
|
||||
// Force enable 'allowLocalPluginLoads'
|
||||
// TypeScript tries to resolve plugins using a path relative to itself
|
||||
// which doesn't work when using the global cache
|
||||
// https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238
|
||||
// VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but
|
||||
// TypeScript already does local loads and if this code is running the user trusts the workspace
|
||||
// https://github.com/microsoft/vscode/issues/45856
|
||||
const ConfiguredProject = tsserver.server.ConfiguredProject;
|
||||
const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype;
|
||||
ConfiguredProject.prototype.enablePluginsWithOptions = function() {
|
||||
this.projectService.allowLocalPluginLoads = true;
|
||||
return originalEnablePluginsWithOptions.apply(this, arguments);
|
||||
};
|
||||
|
||||
// And here is the point where we hijack the VSCode <-> TS communications
|
||||
// by adding ourselves in the middle. We locate everything that looks
|
||||
// like an absolute path of ours and normalize it.
|
||||
|
||||
const Session = tsserver.server.Session;
|
||||
const {onMessage: originalOnMessage, send: originalSend} = Session.prototype;
|
||||
let hostInfo = `unknown`;
|
||||
|
||||
Object.assign(Session.prototype, {
|
||||
onMessage(/** @type {string} */ message) {
|
||||
const parsedMessage = JSON.parse(message)
|
||||
|
||||
if (
|
||||
parsedMessage != null &&
|
||||
typeof parsedMessage === `object` &&
|
||||
parsedMessage.arguments &&
|
||||
typeof parsedMessage.arguments.hostInfo === `string`
|
||||
) {
|
||||
hostInfo = parsedMessage.arguments.hostInfo;
|
||||
}
|
||||
|
||||
return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => {
|
||||
return typeof value === `string` ? fromEditorPath(value) : value;
|
||||
}));
|
||||
},
|
||||
|
||||
send(/** @type {any} */ msg) {
|
||||
return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => {
|
||||
return typeof value === `string` ? toEditorPath(value) : value;
|
||||
})));
|
||||
}
|
||||
});
|
||||
|
||||
return tsserver;
|
||||
};
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require typescript/lib/tsserverlibrary.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real typescript/lib/tsserverlibrary.js your application uses
|
||||
module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`));
|
||||
@@ -4,7 +4,7 @@ const {existsSync} = require(`fs`);
|
||||
const {createRequire, createRequireFromPath} = require(`module`);
|
||||
const {resolve} = require(`path`);
|
||||
|
||||
const relPnpApiPath = "../../../../.pnp.js";
|
||||
const relPnpApiPath = "../../../../.pnp.cjs";
|
||||
|
||||
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
|
||||
const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "typescript",
|
||||
"version": "4.2.3-pnpify",
|
||||
"version": "4.3.5-sdk",
|
||||
"main": "./lib/typescript.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
|
||||
@@ -14,4 +14,4 @@ preferAggregateCacheInfo: true
|
||||
|
||||
preferInteractive: true
|
||||
|
||||
yarnPath: .yarn/releases/yarn-2.4.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-3.0.1.cjs
|
||||
|
||||
@@ -14,7 +14,7 @@ const createBabelConfig = options => {
|
||||
[require.resolve('@babel/preset-env'), {
|
||||
modules: 'commonjs',
|
||||
useBuiltIns: 'entry',
|
||||
corejs: '3.10',
|
||||
corejs: '3',
|
||||
spec: false,
|
||||
loose: true,
|
||||
targets: [],
|
||||
|
||||
@@ -59,7 +59,7 @@ task-webpack() {
|
||||
## Runs a development server
|
||||
task-dev-server() {
|
||||
cd "${base_dir}"
|
||||
yarn node packages/tgui-dev-server/index.esm.js "${@}"
|
||||
yarn node --experimental-modules packages/tgui-dev-server/index.js "${@}"
|
||||
}
|
||||
|
||||
## Run a linter through all packages
|
||||
@@ -92,7 +92,7 @@ task-clean() {
|
||||
rm -f .yarn/build-state.yml
|
||||
rm -f .yarn/install-state.gz
|
||||
rm -f .yarn/install-target
|
||||
rm -f .pnp.js
|
||||
rm -f .pnp.*
|
||||
## NPM artifacts
|
||||
rm -rf **/node_modules
|
||||
rm -f **/package-lock.json
|
||||
|
||||
@@ -47,7 +47,7 @@ function task-webpack {
|
||||
|
||||
## Runs a development server
|
||||
function task-dev-server {
|
||||
yarn node "packages/tgui-dev-server/index.esm.js" @Args
|
||||
yarn node --experimental-modules "packages/tgui-dev-server/index.js" @Args
|
||||
}
|
||||
|
||||
## Run a linter through all packages
|
||||
@@ -75,7 +75,7 @@ function task-clean {
|
||||
Remove-Quiet -Force ".yarn\build-state.yml"
|
||||
Remove-Quiet -Force ".yarn\install-state.gz"
|
||||
Remove-Quiet -Force ".yarn\install-target"
|
||||
Remove-Quiet -Force ".pnp.js"
|
||||
Remove-Quiet -Force ".pnp.*"
|
||||
## NPM artifacts
|
||||
Get-ChildItem -Path "." -Include "node_modules" -Recurse -File:$false | Remove-Item -Recurse -Force
|
||||
Remove-Quiet -Force "package-lock.json"
|
||||
|
||||
@@ -186,6 +186,7 @@ all available horizontal space.
|
||||
- `bold: boolean` - Make text bold.
|
||||
- `italic: boolean` - Make text italic.
|
||||
- `nowrap: boolean` - Stops text from wrapping.
|
||||
- `preserveWhitespace: boolean` - Preserves line-breaks and spacing in text.
|
||||
- `textAlign: string` - Align text inside the box.
|
||||
- `left` (default)
|
||||
- `center`
|
||||
@@ -220,11 +221,7 @@ Buttons allow users to take actions, and make choices, with a single click.
|
||||
- `selected: boolean` - Activates the button (gives it a green color).
|
||||
- `tooltip: string` - A fancy, boxy tooltip, which appears when hovering
|
||||
over the button.
|
||||
- `tooltipPosition: string` - Position of the tooltip.
|
||||
- `top` - Show tooltip above the button.
|
||||
- `bottom` (default) - Show tooltip below the button.
|
||||
- `left` - Show tooltip on the left of the button.
|
||||
- `right` - Show tooltip on the right of the button.
|
||||
- `tooltipPosition?: string` - Position of the tooltip. See [`Popper`](#Popper) for valid options.
|
||||
- `ellipsis: boolean` - If button width is constrained, button text will
|
||||
be truncated with an ellipsis. Be careful however, because this prop breaks
|
||||
the baseline alignment.
|
||||
@@ -743,6 +740,16 @@ the input, or successfully enter a number.
|
||||
- `onDrag: (e, value) => void` - An event, which fires about every 500ms
|
||||
when you drag the input up and down, on release and on manual editing.
|
||||
|
||||
### `Popper`
|
||||
|
||||
Popper lets you position elements so that they don't go out of the bounds of the window. See [popper.js](https://popper.js.org/) for more information.
|
||||
|
||||
**Props:**
|
||||
|
||||
- `popperContent: InfernoNode` - The content that will be put inside the popper.
|
||||
- `options?: { ... }` - An object of options to pass to `createPopper`. See [https://popper.js.org/docs/v2/constructors/#options], but the one you want most is `placement`. Valid placements are "bottom", "top", "left", and "right". You can affix "-start" and "-end" to achieve something like top left or top right respectively. You can also use "auto" (with an optional "-start" or "-end"), where a best fit will be chosen.
|
||||
- `additionalStyles: { ... }` - A map of CSS styles to add to the element that will contain the popper.
|
||||
|
||||
### `ProgressBar`
|
||||
|
||||
Progress indicators inform users about the status of ongoing processes.
|
||||
@@ -1092,17 +1099,16 @@ it is recommended to use that prop instead.
|
||||
Usage:
|
||||
|
||||
```jsx
|
||||
<Box position="relative">
|
||||
Sample text.
|
||||
<Tooltip
|
||||
position="bottom"
|
||||
content="Box tooltip" />
|
||||
</Box>
|
||||
<Tooltip position="bottom" content="Box tooltip">
|
||||
<Box position="relative">
|
||||
Sample text.
|
||||
</Box>
|
||||
</Tooltip>
|
||||
```
|
||||
|
||||
**Props:**
|
||||
|
||||
- `position: string` - Tooltip position.
|
||||
- `position?: string` - Tooltip position. See [`Popper`](#Popper) for valid options. Defaults to "auto".
|
||||
- `content: string` - Content of the tooltip. Must be a plain string.
|
||||
Fragments or other elements are **not** supported.
|
||||
|
||||
@@ -1133,7 +1139,7 @@ Example:
|
||||
- `title: string` - Window title.
|
||||
- `width: number` - Window width.
|
||||
- `height: number` - Window height.
|
||||
- `noClose: boolean` - Controls the ability to close the window.
|
||||
- `canClose: boolean` - Controls the ability to close the window.
|
||||
- `children: any` - Child elements, which are rendered directly inside the
|
||||
window. If you use a [Dimmer](#dimmer) or [Modal](#modal) in your UI,
|
||||
they should be put as direct childs of a Window, otherwise you should be
|
||||
|
||||
@@ -6,39 +6,40 @@
|
||||
"packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.13.15",
|
||||
"@babel/eslint-parser": "^7.13.14",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-transform-jscript": "^7.12.13",
|
||||
"@babel/preset-env": "^7.13.15",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/jsdom": "^16.2.10",
|
||||
"@types/node": "^14.14.41",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
"@babel/core": "^7.15.0",
|
||||
"@babel/eslint-parser": "^7.15.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-transform-jscript": "^7.14.5",
|
||||
"@babel/preset-env": "^7.15.0",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@types/jest": "^27.0.1",
|
||||
"@types/jsdom": "^16.2.13",
|
||||
"@types/node": "^14.17.9",
|
||||
"@typescript-eslint/parser": "^4.29.1",
|
||||
"babel-jest": "^27.0.6",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-inferno": "^6.2.0",
|
||||
"babel-plugin-inferno": "^6.3.0",
|
||||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"common": "workspace:*",
|
||||
"css-loader": "^5.2.2",
|
||||
"eslint": "^7.24.0",
|
||||
"css-loader": "^5.2.7",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-radar": "^0.2.1",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"inferno": "^7.4.8",
|
||||
"jest": "^26.6.3",
|
||||
"jest-circus": "^26.6.3",
|
||||
"jsdom": "^16.5.3",
|
||||
"mini-css-extract-plugin": "^1.4.1",
|
||||
"sass": "^1.32.8",
|
||||
"sass-loader": "^11.0.1",
|
||||
"jest": "^27.0.6",
|
||||
"jest-circus": "^27.0.6",
|
||||
"jsdom": "^16.7.0",
|
||||
"mini-css-extract-plugin": "^1.6.2",
|
||||
"sass": "^1.37.5",
|
||||
"sass-loader": "^11.1.1",
|
||||
"style-loader": "^2.0.0",
|
||||
"terser-webpack-plugin": "^5.1.1",
|
||||
"typescript": "^4.2.4",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"typescript": "^4.3.5",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.33.2",
|
||||
"webpack-bundle-analyzer": "^4.4.1",
|
||||
"webpack-cli": "^4.6.0"
|
||||
}
|
||||
"webpack": "^5.50.0",
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"webpack-cli": "^4.7.2"
|
||||
},
|
||||
"packageManager": "yarn@3.0.1"
|
||||
}
|
||||
|
||||
@@ -1,29 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 437.4 434.4" style="enable-background:new 0 0 437.4 434.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-width:0.57;stroke-miterlimit:10;}
|
||||
.st1{fill:none;stroke:#000000;stroke-width:0.5;stroke-miterlimit:10;}
|
||||
.st2{stroke:#000000;stroke-width:18;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g id="Layer_3">
|
||||
</g>
|
||||
<path class="st0" d="M41.18,306.18l84.43,84.43l-19.53,20.8c-11.26,11.99-26.1,17.98-40.93,17.98c-14.84,0-29.68-5.99-40.93-17.98
|
||||
c-22.52-23.97-22.52-63.2,0-87.18L41.18,306.18z"/>
|
||||
<path class="st0" d="M351.66,149.88L139.65,375.65l-84.43-84.43L269.8,62.7c22.51-23.98,59.34-23.98,81.86,0
|
||||
C374.17,86.67,374.17,125.9,351.66,149.88z"/>
|
||||
<path class="st1" d="M426.11,88.46c-24.57-26.71-49.15-53.42-73.72-80.13c-5.87,0.15-12.84-0.21-20.56-1.73
|
||||
c-3.76-0.74-7.22-1.66-10.37-2.66l0,0c-6.96,7.41-6.96,19.53,0,26.94l79.36,84.52c6.96,7.41,18.34,7.41,25.29,0
|
||||
C433.07,107.99,433.07,95.87,426.11,88.46z"/>
|
||||
<path class="st1" d="M248.07,35.83c13.66,16.01,4.9,48.19-19.57,71.87c-24.47,23.68-33.21,29.27-46.88,13.25
|
||||
c-13.66-16.01-2.33-26.15,22.14-49.83C228.23,47.45,234.41,19.82,248.07,35.83z"/>
|
||||
<line class="st0" x1="139.93" y1="375.93" x2="139.65" y2="375.65"/>
|
||||
<line class="st0" x1="125.93" y1="390.93" x2="125.61" y2="390.61"/>
|
||||
|
||||
<rect x="191.43" y="-6.09" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -86.5159 210.9892)" class="st2" width="40" height="432.04"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 437.4 434.4" style="enable-background:new 0 0 437.4 434.4" xml:space="preserve"><style>.st0{fill:none;stroke:#000;stroke-width:.57;stroke-miterlimit:10}</style><g id="Layer_1"><path class="st0" d="m41.18 306.18 84.43 84.43-19.53 20.8c-11.26 11.99-26.1 17.98-40.93 17.98-14.84 0-29.68-5.99-40.93-17.98-22.52-23.97-22.52-63.2 0-87.18l16.96-18.05zm310.48-156.3L139.65 375.65l-84.43-84.43L269.8 62.7c22.51-23.98 59.34-23.98 81.86 0 22.51 23.97 22.51 63.2 0 87.18z"/><path d="M426.11 88.46c-24.57-26.71-49.15-53.42-73.72-80.13a93.834 93.834 0 0 1-30.93-4.39c-6.96 7.41-6.96 19.53 0 26.94l79.36 84.52c6.96 7.41 18.34 7.41 25.29 0 6.96-7.41 6.96-19.53 0-26.94zM248.07 35.83c13.66 16.01 4.9 48.19-19.57 71.87-24.47 23.68-33.21 29.27-46.88 13.25-13.66-16.01-2.33-26.15 22.14-49.83 24.47-23.67 30.65-51.3 44.31-35.29z" style="stroke-width:.5;fill:none;stroke:#000;stroke-miterlimit:10"/><path class="st0" d="m139.93 375.93-.28-.28m-13.72 15.28-.32-.32"/><path transform="rotate(-45.001 211.427 209.932)" style="stroke:#000;stroke-width:18;stroke-miterlimit:10" d="M191.43-6.09h40v432.04h-40z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,26 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 437.4 434.4" style="enable-background:new 0 0 437.4 434.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-width:0.57;stroke-miterlimit:10;}
|
||||
.st1{fill:none;stroke:#000000;stroke-width:0.5;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g id="Layer_3">
|
||||
</g>
|
||||
<path class="st0" d="M41.18,306.18l84.43,84.43l-19.53,20.8c-11.26,11.99-26.1,17.98-40.93,17.98c-14.84,0-29.68-5.99-40.93-17.98
|
||||
c-22.52-23.97-22.52-63.2,0-87.18L41.18,306.18z"/>
|
||||
<path class="st0" d="M351.66,149.88L139.65,375.65l-84.43-84.43L269.8,62.7c22.51-23.98,59.34-23.98,81.86,0
|
||||
C374.17,86.67,374.17,125.9,351.66,149.88z"/>
|
||||
<path class="st1" d="M426.11,88.46c-24.57-26.71-49.15-53.42-73.72-80.13c-5.87,0.15-12.84-0.21-20.56-1.73
|
||||
c-3.76-0.74-7.22-1.66-10.37-2.66l0,0c-6.96,7.41-6.96,19.53,0,26.94l79.36,84.52c6.96,7.41,18.34,7.41,25.29,0
|
||||
C433.07,107.99,433.07,95.87,426.11,88.46z"/>
|
||||
<path class="st1" d="M248.07,35.83c13.66,16.01,4.9,48.19-19.57,71.87c-24.47,23.68-33.21,29.27-46.88,13.25
|
||||
c-13.66-16.01-2.33-26.15,22.14-49.83C228.23,47.45,234.41,19.82,248.07,35.83z"/>
|
||||
<line class="st0" x1="139.93" y1="375.93" x2="139.65" y2="375.65"/>
|
||||
<line class="st0" x1="125.93" y1="390.93" x2="125.61" y2="390.61"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 437.4 434.4" style="enable-background:new 0 0 437.4 434.4" xml:space="preserve"><style>.st0{fill:none;stroke:#000;stroke-width:.57;stroke-miterlimit:10}</style><g id="Layer_1"><path class="st0" d="m41.18 306.18 84.43 84.43-19.53 20.8c-11.26 11.99-26.1 17.98-40.93 17.98-14.84 0-29.68-5.99-40.93-17.98-22.52-23.97-22.52-63.2 0-87.18l16.96-18.05zm310.48-156.3L139.65 375.65l-84.43-84.43L269.8 62.7c22.51-23.98 59.34-23.98 81.86 0 22.51 23.97 22.51 63.2 0 87.18z"/><path d="M426.11 88.46c-24.57-26.71-49.15-53.42-73.72-80.13a93.834 93.834 0 0 1-30.93-4.39c-6.96 7.41-6.96 19.53 0 26.94l79.36 84.52c6.96 7.41 18.34 7.41 25.29 0 6.96-7.41 6.96-19.53 0-26.94zM248.07 35.83c13.66 16.01 4.9 48.19-19.57 71.87-24.47 23.68-33.21 29.27-46.88 13.25-13.66-16.01-2.33-26.15 22.14-49.83 24.47-23.67 30.65-51.3 44.31-35.29z" style="stroke-width:.5;fill:none;stroke:#000;stroke-miterlimit:10"/><path class="st0" d="m139.93 375.93-.28-.28m-13.72 15.28-.32-.32"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1007 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="670" height="512"><path d="M655.714 228H484.286c-7.857 0-14.286 6.412-14.286 14.25v28.5c0 7.838 6.429 14.25 14.286 14.25h171.428c7.857 0 14.286-6.413 14.286-14.25v-28.5c0-7.838-6.429-14.25-14.286-14.25z" style="stroke-width:.89174"/><path d="M384 121.941V128H256V0h6.059a24 24 0 0 1 16.97 7.029l97.941 97.941a24.002 24.002 0 0 1 7.03 16.971zM248 160c-13.2 0-24-10.8-24-24V0H24C10.745 0 0 10.745 0 24v464c0 13.255 10.745 24 24 24h336c13.255 0 24-10.745 24-24V160zm-135.46 16c26.51 0 48 21.49 48 48s-21.49 48-48 48-48-21.49-48-48 21.491-48 48-48zm208 240h-256l.485-48.485L104.54 328c4.686-4.686 11.799-4.201 16.485.485L160.54 368l103.52-103.52c4.686-4.686 12.284-4.686 16.971 0l39.514 39.515z"/></svg>
|
||||
|
After Width: | Height: | Size: 746 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="670" height="512"><path d="M655.714 228.429h-57.143v-57.143c0-7.857-6.428-14.286-14.285-14.286h-28.572c-7.857 0-14.285 6.429-14.285 14.286v57.143h-57.143c-7.857 0-14.286 6.428-14.286 14.285v28.572c0 7.857 6.429 14.285 14.286 14.285h57.143v57.143c0 7.857 6.428 14.286 14.285 14.286h28.572c7.857 0 14.285-6.429 14.285-14.286v-57.143h57.143c7.857 0 14.286-6.428 14.286-14.285v-28.572c0-7.857-6.429-14.285-14.286-14.285z" style="stroke-width:.892857"/><path d="M384 121.941V128H256V0h6.059a24 24 0 0 1 16.97 7.029l97.941 97.941a24.002 24.002 0 0 1 7.03 16.971zM248 160c-13.2 0-24-10.8-24-24V0H24C10.745 0 0 10.745 0 24v464c0 13.255 10.745 24 24 24h336c13.255 0 24-10.745 24-24V160zm-135.46 16c26.51 0 48 21.49 48 48s-21.49 48-48 48-48-21.49-48-48 21.491-48 48-48zm208 240h-256l.485-48.485L104.54 328c4.686-4.686 11.799-4.201 16.485.485L160.54 368l103.52-103.52c4.686-4.686 12.284-4.686 16.971 0l39.514 39.515z"/></svg>
|
||||
|
After Width: | Height: | Size: 961 B |
@@ -1,8 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 425 200" opacity=".33">
|
||||
<path d="m 178.00399,0.03869 -71.20393,0 a 6.7613422,6.0255495 0 0 0 -6.76134,6.02555 l 0,187.87147 a 6.7613422,6.0255495 0 0 0 6.76134,6.02554 l 53.1072,0 a 6.7613422,6.0255495 0 0 0 6.76135,-6.02554 l 0,-101.544018 72.21628,104.699398 a 6.7613422,6.0255495 0 0 0 5.76015,2.87016 l 73.55487,0 a 6.7613422,6.0255495 0 0 0 6.76135,-6.02554 l 0,-187.87147 a 6.7613422,6.0255495 0 0 0 -6.76135,-6.02555 l -54.71644,0 a 6.7613422,6.0255495 0 0 0 -6.76133,6.02555 l 0,102.61935 L 183.76413,2.90886 a 6.7613422,6.0255495 0 0 0 -5.76014,-2.87017 z" />
|
||||
<path d="M 4.8446333,22.10875 A 13.412039,12.501842 0 0 1 13.477588,0.03924 l 66.118315,0 a 5.3648158,5.000737 0 0 1 5.364823,5.00073 l 0,79.87931 z" />
|
||||
<path d="m 420.15535,177.89119 a 13.412038,12.501842 0 0 1 -8.63295,22.06951 l -66.11832,0 a 5.3648152,5.000737 0 0 1 -5.36482,-5.00074 l 0,-79.87931 z" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 425 200" opacity=".33"><path d="M178.004.039H106.8a6.761 6.026 0 0 0-6.761 6.025v187.872a6.761 6.026 0 0 0 6.761 6.025h53.107a6.761 6.026 0 0 0 6.762-6.025V92.392l72.216 104.7a6.761 6.026 0 0 0 5.76 2.87H318.2a6.761 6.026 0 0 0 6.761-6.026V6.064A6.761 6.026 0 0 0 318.2.04h-54.717a6.761 6.026 0 0 0-6.76 6.025v102.62L183.763 2.909a6.761 6.026 0 0 0-5.76-2.87zM4.845 22.109A13.412 12.502 0 0 1 13.478.039h66.118A5.365 5 0 0 1 84.96 5.04v79.88zm415.31 155.782a13.412 12.502 0 0 1-8.633 22.07h-66.118a5.365 5 0 0 1-5.365-5.001v-79.88z"/></svg>
|
||||
<!-- This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. -->
|
||||
<!-- http://creativecommons.org/licenses/by-sa/4.0/ -->
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 770 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="542" height="512"><path d="M215.03 71.05 126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97z"/><path d="M527.714 228H356.286c-7.857 0-14.286 6.412-14.286 14.25v28.5c0 7.838 6.429 14.25 14.286 14.25h171.428c7.857 0 14.286-6.413 14.286-14.25v-28.5c0-7.838-6.429-14.25-14.286-14.25z" style="stroke-width:.89174"/></svg>
|
||||
|
After Width: | Height: | Size: 469 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="542" height="512"><path d="M527.714 227.929h-57.143v-57.143c0-7.857-6.428-14.286-14.285-14.286h-28.572c-7.857 0-14.285 6.429-14.285 14.286v57.143h-57.143c-7.857 0-14.286 6.428-14.286 14.285v28.572c0 7.857 6.429 14.285 14.286 14.285h57.143v57.143c0 7.857 6.428 14.286 14.285 14.286h28.572c7.857 0 14.285-6.429 14.285-14.286v-57.143h57.143c7.857 0 14.286-6.428 14.286-14.285v-28.572c0-7.857-6.429-14.285-14.286-14.285z" style="stroke-width:.892857"/><path d="M215.03 71.05 126.06 160H24c-13.26 0-24 10.74-24 24v144c0 13.25 10.74 24 24 24h102.06l88.97 88.95c15.03 15.03 40.97 4.47 40.97-16.97V88.02c0-21.46-25.96-31.98-40.97-16.97z"/></svg>
|
||||
|
After Width: | Height: | Size: 684 B |
@@ -1,6 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 289.742" opacity=".33">
|
||||
<path d="m 93.537677,0 c -18.113125,0 -34.220133,3.11164 -48.323484,9.33437 -13.965092,6.22167 -24.612442,15.07114 -31.940651,26.5471 -7.1899398,11.33789 -10.3012266,24.74911 -10.3012266,40.23478 0,10.64662 2.7250026,20.46465 8.1751116,29.45258 5.615277,8.98686 14.038277,17.35204 25.268821,25.09436 11.230544,7.60531 26.507421,15.41835 45.830514,23.43782 19.983748,8.29557 34.848848,15.55471 44.592998,21.77638 9.74414,6.22273 16.7617,12.8585 21.05572,19.90951 4.29404,7.05208 6.44193,15.76408 6.44193,26.13459 0,16.17702 -5.20196,28.48222 -15.60673,36.91682 -10.2396,8.4347 -25.02203,12.6523 -44.345169,12.6523 -14.038171,0 -25.515247,-1.6594 -34.433618,-4.9777 -8.91837,-3.4566 -16.185572,-8.7113 -21.800839,-15.7633 -5.615277,-7.0521 -10.074795,-16.66088 -13.377899,-28.82812 l -24.7731626293945,0 0,56.82632 C 33.856769,286.07601 63.74904,289.74201 89.678383,289.74201 c 16.020027,0 30.719787,-1.3827 44.097337,-4.1479 13.54272,-2.9043 25.1041,-7.4676 34.68309,-13.6893 9.74413,-6.3597 17.34042,-14.5195 22.79052,-24.4748 5.4501,-10.09332 8.17511,-22.39959 8.17511,-36.91682 0,-12.99764 -3.3021,-24.33539 -9.90829,-34.0146 -6.44105,-9.81725 -15.52545,-18.52707 -27.25146,-26.13133 -11.56085,-7.60427 -27.91083,-15.83142 -49.05066,-24.68022 -17.50644,-7.19012 -30.719668,-13.68948 -39.638038,-19.49701 -8.918371,-5.80752 -18.607474,-12.43409 -24.096524,-18.87417 -5.426043,-6.36616 -9.658826,-15.07003 -9.658826,-24.88729 0,-9.26401 2.075414,-17.21345 6.223454,-23.85033 11.098298,-14.39748 41.286638,-1.79507 45.075609,24.34762 4.839392,6.77491 8.84935,16.24729 12.029515,28.4156 l 20.53234,0 0,-55.99967 c -4.47825,-5.92448 -9.95488,-10.63222 -15.90837,-14.37411 1.64055,0.47905 3.19039,1.02376 4.63865,1.64024 6.49861,2.62607 12.16793,7.32747 17.0073,14.10345 4.83939,6.77491 8.84935,16.24567 12.02952,28.41397 0,0 8.48128,-0.12894 8.48978,-0.002 0.41776,6.41494 -1.75339,9.45286 -4.12342,12.56104 -2.4174,3.16978 -5.14486,6.78973 -4.00278,13.0029 1.50786,8.20318 10.18354,10.59642 14.62194,9.31154 -3.31842,-0.49911 -5.31855,-1.74948 -5.31855,-1.74948 0,0 1.87646,0.99868 5.65117,-1.35981 -3.27695,0.95571 -10.70529,-0.79738 -11.80125,-6.76313 -0.95752,-5.20861 0.94654,-7.29514 3.40113,-10.51482 2.45462,-3.21968 5.28426,-6.95831 4.6843,-14.48824 l 0.003,0.002 8.92676,0 0,-55.99967 c -15.07125,-3.87168 -27.65314,-6.36042 -37.74671,-7.46586 -9.95531,-1.10755 -20.18823,-1.65981 -30.696613,-1.65981 z m 70.321603,17.30893 0.23805,40.3049 c 1.31808,1.22666 2.43965,2.27815 3.34081,3.10602 4.83939,6.77491 8.84934,16.24566 12.02951,28.41397 l 20.53234,0 0,-55.99967 c -6.67731,-4.59381 -19.83643,-10.47309 -36.14071,-15.82522 z m -28.12049,5.60551 8.56479,17.71655 c -11.97037,-6.46697 -13.84678,-9.71726 -8.56479,-17.71655 z m 22.79705,0 c 2.7715,7.99929 1.78741,11.24958 -4.49354,17.71655 l 4.49354,-17.71655 z m 15.22195,24.00848 8.56479,17.71655 c -11.97038,-6.46697 -13.84679,-9.71726 -8.56479,-17.71655 z m 22.79704,0 c 2.7715,7.99929 1.78741,11.24958 -4.49354,17.71655 l 4.49354,-17.71655 z m -99.11384,2.20764 8.56479,17.71655 c -11.970382,-6.46697 -13.846782,-9.71726 -8.56479,-17.71655 z m 22.79542,0 c 2.7715,7.99929 1.78741,11.24958 -4.49354,17.71655 l 4.49354,-17.71655 z" />
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 289.742" opacity=".33"><path d="M93.538 0c-18.113 0-34.22 3.112-48.324 9.334-13.965 6.222-24.612 15.072-31.94 26.547C6.084 47.22 2.972 60.631 2.972 76.116c0 10.647 2.725 20.465 8.175 29.453 5.616 8.987 14.039 17.352 25.27 25.094 11.23 7.606 26.507 15.419 45.83 23.438 19.984 8.296 34.849 15.555 44.593 21.776 9.744 6.223 16.761 12.859 21.055 19.91 4.295 7.052 6.442 15.764 6.442 26.134 0 16.178-5.202 28.483-15.606 36.917-10.24 8.435-25.022 12.653-44.345 12.653-14.039 0-25.516-1.66-34.434-4.978-8.918-3.457-16.186-8.711-21.8-15.763-5.616-7.052-10.076-16.661-13.379-28.829H0v56.827c33.857 7.328 63.749 10.994 89.678 10.994 16.02 0 30.72-1.383 44.098-4.148 13.542-2.904 25.104-7.467 34.683-13.69 9.744-6.359 17.34-14.519 22.79-24.474 5.45-10.093 8.175-22.4 8.175-36.917 0-12.997-3.302-24.335-9.908-34.014-6.44-9.818-15.525-18.527-27.251-26.132-11.561-7.604-27.911-15.831-49.051-24.68-17.506-7.19-30.72-13.69-39.638-19.497S54.969 93.756 49.479 87.316c-5.426-6.366-9.658-15.07-9.658-24.887 0-9.264 2.075-17.214 6.223-23.85C57.142 24.18 87.331 36.782 91.12 62.925c4.84 6.775 8.85 16.247 12.03 28.415h20.532v-56c-4.479-5.924-9.955-10.631-15.909-14.373 1.64.479 3.19 1.023 4.639 1.64 6.498 2.626 12.168 7.327 17.007 14.103 4.84 6.775 8.85 16.246 12.03 28.414 0 0 8.48-.129 8.49-.002.417 6.415-1.754 9.453-4.124 12.561-2.417 3.17-5.145 6.79-4.003 13.003 1.508 8.203 10.184 10.597 14.622 9.312-3.318-.5-5.318-1.75-5.318-1.75s1.876.999 5.65-1.36c-3.276.956-10.704-.797-11.8-6.763-.958-5.208.946-7.295 3.4-10.514 2.455-3.22 5.285-6.959 4.685-14.489l.003.002h8.927v-56c-15.072-3.871-27.653-6.36-37.747-7.465C114.279.552 104.046 0 93.537 0zm70.321 17.309.238 40.305c1.318 1.226 2.44 2.278 3.341 3.106 4.84 6.775 8.85 16.246 12.03 28.414H200v-56c-6.677-4.594-19.836-10.473-36.14-15.825zm-28.12 5.605 8.565 17.717c-11.97-6.467-13.847-9.717-8.565-17.717zm22.797 0c2.771 8 1.787 11.25-4.494 17.717l4.494-17.717zm15.222 24.009 8.565 17.716c-11.97-6.466-13.847-9.717-8.565-17.716zm22.797 0c2.771 8 1.787 11.25-4.494 17.716l4.494-17.716zM97.44 49.13l8.565 17.716c-11.97-6.467-13.847-9.717-8.565-17.716zm22.795 0c2.772 7.999 1.788 11.25-4.493 17.716l4.493-17.716z"/></svg>
|
||||
<!-- This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. -->
|
||||
<!-- http://creativecommons.org/licenses/by-sa/4.0/ -->
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -3,7 +3,7 @@
|
||||
"name": "tgfont",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"fantasticon": "^1.1.3"
|
||||
"fantasticon": "^1.2.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node mkdist.cjs && fantasticon --config config.cjs"
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { exec } from 'child_process';
|
||||
import { createLogger } from 'common/logging.js';
|
||||
import { promisify } from 'util';
|
||||
import { createLogger } from './logging.js';
|
||||
import { require } from './require.js';
|
||||
|
||||
const axios = require('axios');
|
||||
const logger = createLogger('dreamseeker');
|
||||
|
||||
const instanceByPid = new Map();
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
const esmRequire = require('esm')(module, {
|
||||
cache: false,
|
||||
});
|
||||
|
||||
esmRequire('./index.js');
|
||||
@@ -37,7 +37,7 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
window.onunload = () => socket && socket.close();
|
||||
}
|
||||
|
||||
export const subscribe = fn => subscribers.push(fn);
|
||||
const subscribe = fn => subscribers.push(fn);
|
||||
|
||||
/**
|
||||
* A json serializer which handles circular references and other junk.
|
||||
@@ -91,7 +91,7 @@ const serializeObject = obj => {
|
||||
return json;
|
||||
};
|
||||
|
||||
export const sendMessage = msg => {
|
||||
const sendMessage = msg => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const json = serializeObject(msg);
|
||||
// Send message using WebSocket
|
||||
@@ -119,7 +119,7 @@ export const sendMessage = msg => {
|
||||
}
|
||||
};
|
||||
|
||||
export const sendLogEntry = (level, ns, ...args) => {
|
||||
const sendLogEntry = (level, ns, ...args) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
try {
|
||||
sendMessage({
|
||||
@@ -135,7 +135,7 @@ export const sendLogEntry = (level, ns, ...args) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const setupHotReloading = () => {
|
||||
const setupHotReloading = () => {
|
||||
if (process.env.NODE_ENV !== 'production'
|
||||
&& process.env.WEBPACK_HMR_ENABLED
|
||||
&& window.WebSocket) {
|
||||
@@ -168,3 +168,10 @@ export const setupHotReloading = () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
subscribe,
|
||||
sendMessage,
|
||||
sendLogEntry,
|
||||
setupHotReloading,
|
||||
};
|
||||
@@ -4,13 +4,15 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { createLogger } from 'common/logging.js';
|
||||
import fs from 'fs';
|
||||
import { basename } from 'path';
|
||||
import SourceMap from 'source-map';
|
||||
import { parse as parseStackTrace } from 'stacktrace-parser';
|
||||
import { createLogger } from '../logging.js';
|
||||
import { require } from '../require.js';
|
||||
import { resolveGlob } from '../util.js';
|
||||
|
||||
const SourceMap = require('source-map');
|
||||
const { parse: parseStackTrace } = require('stacktrace-parser');
|
||||
|
||||
const logger = createLogger('retrace');
|
||||
|
||||
const { SourceMapConsumer } = SourceMap;
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { createLogger, directLog } from 'common/logging.js';
|
||||
import http from 'http';
|
||||
import { inspect } from 'util';
|
||||
import WebSocket from 'ws';
|
||||
import { retrace, loadSourceMaps } from './retrace.js';
|
||||
import { createLogger, directLog } from '../logging.js';
|
||||
import { require } from '../require.js';
|
||||
import { loadSourceMaps, retrace } from './retrace.js';
|
||||
|
||||
const WebSocket = require('ws');
|
||||
|
||||
const logger = createLogger('link');
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
"private": true,
|
||||
"name": "tgui-dev-server",
|
||||
"version": "4.3.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"common": "workspace:*",
|
||||
"esm": "^3.2.25",
|
||||
"glob": "^7.1.6",
|
||||
"glob": "^7.1.7",
|
||||
"source-map": "^0.7.3",
|
||||
"stacktrace-parser": "^0.1.10",
|
||||
"ws": "^7.4.6"
|
||||
"ws": "^7.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { createLogger } from 'common/logging.js';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import { basename } from 'path';
|
||||
import { DreamSeeker } from './dreamseeker.js';
|
||||
import { createLogger } from './logging.js';
|
||||
import { resolveGlob, resolvePath } from './util.js';
|
||||
import { regQuery } from './winreg.js';
|
||||
import { DreamSeeker } from './dreamseeker.js';
|
||||
|
||||
const logger = createLogger('reloader');
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { createRequire } from 'module';
|
||||
|
||||
export const require = createRequire(import.meta.url);
|
||||
@@ -4,26 +4,26 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import glob from 'glob';
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import path from 'path';
|
||||
import { require } from './require.js';
|
||||
|
||||
export { resolvePath };
|
||||
const globPkg = require('glob');
|
||||
|
||||
export const resolvePath = path.resolve;
|
||||
|
||||
/**
|
||||
* Combines path.resolve with glob patterns.
|
||||
*/
|
||||
export const resolveGlob = async (...sections) => {
|
||||
const unsafePaths = await promisify(glob)(
|
||||
resolvePath(...sections), {
|
||||
strict: false,
|
||||
silent: true,
|
||||
});
|
||||
export const resolveGlob = (...sections) => {
|
||||
const unsafePaths = globPkg.sync(path.resolve(...sections), {
|
||||
strict: false,
|
||||
silent: true,
|
||||
});
|
||||
const safePaths = [];
|
||||
for (let path of unsafePaths) {
|
||||
try {
|
||||
await promisify(fs.stat)(path);
|
||||
fs.statSync(path);
|
||||
safePaths.push(path);
|
||||
}
|
||||
catch {}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { createLogger } from 'common/logging.js';
|
||||
import fs from 'fs';
|
||||
import { createRequire } from 'module';
|
||||
import { dirname } from 'path';
|
||||
import { loadSourceMaps, setupLink } from './link/server.js';
|
||||
import { createLogger } from './logging.js';
|
||||
import { reloadByondCache } from './reloader.js';
|
||||
import { resolveGlob } from './util.js';
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
*/
|
||||
|
||||
import { exec } from 'child_process';
|
||||
import { createLogger } from 'common/logging.js';
|
||||
import { promisify } from 'util';
|
||||
import { createLogger } from './logging.js';
|
||||
|
||||
const logger = createLogger('winreg');
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export const Panel = (props, context) => {
|
||||
selected={audio.visible}
|
||||
icon="music"
|
||||
tooltip="Music player"
|
||||
tooltipPosition="bottom-left"
|
||||
tooltipPosition="bottom-start"
|
||||
onClick={() => audio.toggle()} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
@@ -60,7 +60,7 @@ export const Panel = (props, context) => {
|
||||
tooltip={settings.visible
|
||||
? 'Close settings'
|
||||
: 'Open settings'}
|
||||
tooltipPosition="bottom-left"
|
||||
tooltipPosition="bottom-start"
|
||||
onClick={() => settings.toggle()} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
|
||||
@@ -58,31 +58,31 @@ export const MESSAGE_TYPES = [
|
||||
type: MESSAGE_TYPE_RADIO,
|
||||
name: 'Radio',
|
||||
description: 'All departments of radio messages',
|
||||
selector: '.alert, .minorannounce, .syndradio, .airadio, .entradio, .centcomradio, .aiprivradio, .comradio, .secradio, .engradio, .medradio, .sciradio, .suppradio, .servradio, .radio, .deptradio, .binarysay, .newscaster',
|
||||
selector: '.alert, .minorannounce, .syndradio, .centcomradio, .aiprivradio, .comradio, .secradio, .engradio, .medradio, .sciradio, .suppradio, .servradio, .radio, .deptradio, .binarysay, .newscaster, .resonate',
|
||||
},
|
||||
{
|
||||
type: MESSAGE_TYPE_INFO,
|
||||
name: 'Info',
|
||||
description: 'Non-urgent messages from the game and items',
|
||||
selector: '.notice:not(.pm), .adminnotice, .info, .sinister, .cult, .infoplain',
|
||||
selector: '.notice:not(.pm), .adminnotice, .info, .sinister, .cult, .infoplain, .announce, .hear, .smallnotice, .holoparasite',
|
||||
},
|
||||
{
|
||||
type: MESSAGE_TYPE_WARNING,
|
||||
name: 'Warnings',
|
||||
description: 'Urgent messages from the game and items',
|
||||
selector: '.warning:not(.pm), .critical, .userdanger, .italics',
|
||||
selector: '.warning:not(.pm), .critical, .userdanger, .italics, .alertsyndie, .warningplain',
|
||||
},
|
||||
{
|
||||
type: MESSAGE_TYPE_DEADCHAT,
|
||||
name: 'Deadchat',
|
||||
description: 'All of deadchat',
|
||||
selector: '.deadsay',
|
||||
selector: '.deadsay, .ghostalert',
|
||||
},
|
||||
{
|
||||
type: MESSAGE_TYPE_OOC,
|
||||
name: 'OOC',
|
||||
description: 'The bluewall of global OOC messages',
|
||||
selector: '.ooc, .adminooc',
|
||||
selector: '.ooc, .adminooc, .oocplain',
|
||||
},
|
||||
{
|
||||
type: 'looc',
|
||||
|
||||
@@ -117,7 +117,9 @@ export const chatMiddleware = store => {
|
||||
const settings = selectSettings(store.getState());
|
||||
chatRenderer.setHighlight(
|
||||
settings.highlightText,
|
||||
settings.highlightColor);
|
||||
settings.highlightColor,
|
||||
settings.matchWord,
|
||||
settings.matchCase);
|
||||
return;
|
||||
}
|
||||
if (type === 'roundrestart') {
|
||||
|
||||
@@ -170,23 +170,19 @@ class ChatRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
setHighlight(text, color) {
|
||||
setHighlight(text, color, matchWord, matchCase) {
|
||||
if (!text || !color) {
|
||||
this.highlightRegex = null;
|
||||
this.highlightColor = null;
|
||||
return;
|
||||
}
|
||||
// citadel update - ig changed to i for flags,
|
||||
// to fix issues with highlighting only working for every other word.
|
||||
const allowedRegex = /^[a-z0-9_\-\s]+$/i;
|
||||
const lines = String(text)
|
||||
.split(',')
|
||||
.map(str => str.trim())
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
.map(str => str.trim().replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'))
|
||||
.filter(str => (
|
||||
// Must be longer than one character
|
||||
str && str.length > 1
|
||||
// Must be alphanumeric (with some punctuation)
|
||||
&& allowedRegex.test(str)
|
||||
));
|
||||
// Nothing to match, reset highlighting
|
||||
if (lines.length === 0) {
|
||||
@@ -194,7 +190,9 @@ class ChatRenderer {
|
||||
this.highlightColor = null;
|
||||
return;
|
||||
}
|
||||
this.highlightRegex = new RegExp('(' + lines.join('|') + ')', 'gi');
|
||||
const pattern = `${(matchWord ? '\\b' : '')}(${lines.join('|')})${(matchWord ? '\\b' : '')}`;
|
||||
const flags = 'g' + (matchCase ? '' : 'i');
|
||||
this.highlightRegex = new RegExp(pattern, flags);
|
||||
this.highlightColor = color;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import './styles/themes/light.scss';
|
||||
|
||||
import { perf } from 'common/perf';
|
||||
import { combineReducers } from 'common/redux';
|
||||
import { setupHotReloading } from 'tgui-dev-server/link/client';
|
||||
import { setupHotReloading } from 'tgui-dev-server/link/client.cjs';
|
||||
import { setupGlobalEvents } from 'tgui/events';
|
||||
import { captureExternalLinks } from 'tgui/links';
|
||||
import { createRenderer } from 'tgui/renderer';
|
||||
@@ -93,8 +93,13 @@ const setupApp = () => {
|
||||
'pos': '0x0',
|
||||
'size': '0x0',
|
||||
});
|
||||
// Absolutely shit workaround for chatbox visiblity.
|
||||
based_winset();
|
||||
|
||||
// Resize the panel to match the non-browser output
|
||||
Byond.winget('output').then(output => {
|
||||
Byond.winset('browseroutput', {
|
||||
'size': output.size,
|
||||
});
|
||||
});
|
||||
|
||||
// Enable hot module reloading
|
||||
if (module.hot) {
|
||||
@@ -114,12 +119,4 @@ const setupApp = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const based_winset = async (based_on_what = 'output') => {
|
||||
// shitty workaround because winget is async.
|
||||
const winget_output = await Byond.winget(based_on_what);
|
||||
Byond.winset('browseroutput', {
|
||||
'size': winget_output["size"],
|
||||
});
|
||||
};
|
||||
|
||||
setupApp();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"version": "4.3.0",
|
||||
"dependencies": {
|
||||
"common": "workspace:*",
|
||||
"dompurify": "^2.2.7",
|
||||
"dompurify": "^2.3.1",
|
||||
"inferno": "^7.4.8",
|
||||
"tgui": "workspace:*",
|
||||
"tgui-dev-server": "workspace:*",
|
||||
|
||||
@@ -56,6 +56,8 @@ export const SettingsGeneral = (props, context) => {
|
||||
lineHeight,
|
||||
highlightText,
|
||||
highlightColor,
|
||||
matchWord,
|
||||
matchCase,
|
||||
} = useSelector(context, selectSettings);
|
||||
const dispatch = useDispatch(context);
|
||||
const [freeFont, setFreeFont] = useLocalState(context, "freeFont", false);
|
||||
@@ -134,7 +136,7 @@ export const SettingsGeneral = (props, context) => {
|
||||
<Box>
|
||||
<Flex mb={1} color="label" align="baseline">
|
||||
<Flex.Item grow={1}>
|
||||
Highlight words (comma separated):
|
||||
Highlight text (comma separated):
|
||||
</Flex.Item>
|
||||
<Flex.Item shrink={0}>
|
||||
<ColorBox mr={1} color={highlightColor} />
|
||||
@@ -154,6 +156,22 @@ export const SettingsGeneral = (props, context) => {
|
||||
onChange={(e, value) => dispatch(updateSettings({
|
||||
highlightText: value,
|
||||
}))} />
|
||||
<Button.Checkbox
|
||||
checked={matchWord}
|
||||
tooltipPosition="bottom-start"
|
||||
tooltip="Not compatible with punctuation."
|
||||
onClick={() => dispatch(updateSettings({
|
||||
matchWord: !matchWord,
|
||||
}))}>
|
||||
Match word
|
||||
</Button.Checkbox>
|
||||
<Button.Checkbox
|
||||
checked={matchCase}
|
||||
onClick={() => dispatch(updateSettings({
|
||||
matchCase: !matchCase,
|
||||
}))}>
|
||||
Match case
|
||||
</Button.Checkbox>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box>
|
||||
|
||||
@@ -16,6 +16,8 @@ const initialState = {
|
||||
adminMusicVolume: 0.5,
|
||||
highlightText: '',
|
||||
highlightColor: '#ffdd44',
|
||||
matchWord: false,
|
||||
matchCase: false,
|
||||
view: {
|
||||
visible: false,
|
||||
activeTab: SETTINGS_TABS[0].id,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
@use 'sass:color';
|
||||
@use 'sass:math';
|
||||
@use '~tgui/styles/base.scss';
|
||||
@use '~tgui/styles/colors.scss';
|
||||
|
||||
@@ -62,7 +63,7 @@ $color-bg-section: base.$color-bg-section !default;
|
||||
content: '';
|
||||
display: block;
|
||||
margin-top: -0.75em;
|
||||
border-bottom: (1em / 6) solid colors.$red;
|
||||
border-bottom: math.div(1em, 6) solid colors.$red;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +83,7 @@ $color-bg-section: base.$color-bg-section !default;
|
||||
|
||||
.ChatMessage--highlighted {
|
||||
position: relative;
|
||||
border-left: (1em / 6) solid rgba(255, 221, 68);
|
||||
border-left: math.div(1em, 6) solid rgba(255, 221, 68);
|
||||
padding-left: 0.5em;
|
||||
|
||||
&:after {
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
@use 'sass:math';
|
||||
|
||||
$border-color: rgba(140, 140, 140, 0.5) !default;
|
||||
|
||||
.Ping {
|
||||
position: relative;
|
||||
padding: 0.125em 0.25em;
|
||||
border: (1em / 12) solid $border-color;
|
||||
border: math.div(1em, 12) solid $border-color;
|
||||
border-radius: 0.25em;
|
||||
width: 3.75em;
|
||||
text-align: right;
|
||||
|
||||
@@ -287,16 +287,6 @@ em {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.looc {
|
||||
color: #6699CC;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.antagooc {
|
||||
color: #b8002e;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.adminobserverooc {
|
||||
color: #0099cc;
|
||||
font-weight: bold;
|
||||
@@ -338,7 +328,7 @@ em {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.say, .emote, .infoplain {
|
||||
.say, .emote, .infoplain, .oocplain, .warningplain {
|
||||
}
|
||||
|
||||
.deadsay {
|
||||
@@ -357,6 +347,9 @@ em {
|
||||
color: #88ff88;
|
||||
}
|
||||
|
||||
/* RADIO COLORS */
|
||||
/* IF YOU CHANGE THIS KEEP IT IN SYNC WITH TGUI CONSTANTS */
|
||||
|
||||
.radio {
|
||||
color: #1ecc43;
|
||||
}
|
||||
@@ -409,6 +402,14 @@ em {
|
||||
color: #3434fd;
|
||||
}
|
||||
|
||||
.greenteamradio {
|
||||
color: #34fd34;
|
||||
}
|
||||
|
||||
.yellowteamradio {
|
||||
color: #fdfd34;
|
||||
}
|
||||
|
||||
.yell {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -993,6 +994,10 @@ em {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.bigicon {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
.memo {
|
||||
color: #638500;
|
||||
text-align: center;
|
||||
|
||||
@@ -356,7 +356,7 @@ em {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.say, .emote, .infoplain {
|
||||
.say, .emote, .infoplain, .oocplain, .warningplain {
|
||||
}
|
||||
|
||||
.deadsay {
|
||||
@@ -430,6 +430,14 @@ em {
|
||||
color: #0000ff;
|
||||
}
|
||||
|
||||
.greenteamradio {
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.yellowteamradio {
|
||||
color: #d1ba22;
|
||||
}
|
||||
|
||||
.yell {
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -1032,6 +1040,10 @@ h1.alert, h2.alert {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.bigicon {
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
.memo {
|
||||
color: #638500;
|
||||
text-align: center;
|
||||
|
||||
@@ -14,8 +14,4 @@ import './ie8';
|
||||
import './dom4';
|
||||
import './css-om';
|
||||
import './inferno';
|
||||
|
||||
// Fetch is required for Webpack HMR
|
||||
if (module.hot) {
|
||||
require('whatwg-fetch');
|
||||
}
|
||||
import 'unfetch/polyfill';
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
"name": "tgui-polyfill",
|
||||
"version": "4.3.0",
|
||||
"dependencies": {
|
||||
"core-js": "^3.10.1",
|
||||
"regenerator-runtime": "^0.13.8",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
"core-js": "^3.16.1",
|
||||
"regenerator-runtime": "^0.13.9",
|
||||
"unfetch": "^4.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export class AnimatedNumber extends Component {
|
||||
if (!isSafeNumber(targetValue)) {
|
||||
return targetValue || null;
|
||||
}
|
||||
let formattedValue = currentValue;
|
||||
let formattedValue;
|
||||
// Use custom formatter
|
||||
if (format) {
|
||||
formattedValue = format(currentValue);
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface BoxProps {
|
||||
bold?: BooleanLike;
|
||||
italic?: BooleanLike;
|
||||
nowrap?: BooleanLike;
|
||||
preserveWhitespace?: BooleanLike;
|
||||
m?: string | BooleanLike;
|
||||
mx?: string | BooleanLike;
|
||||
my?: string | BooleanLike;
|
||||
@@ -163,6 +164,7 @@ const styleMapperByPropName = {
|
||||
bold: mapBooleanPropTo('font-weight', 'bold'),
|
||||
italic: mapBooleanPropTo('font-style', 'italic'),
|
||||
nowrap: mapBooleanPropTo('white-space', 'nowrap'),
|
||||
preserveWhitespace: mapBooleanPropTo('white-space', 'pre-wrap'),
|
||||
// Margins
|
||||
m: mapDirectionalUnitPropTo('margin', halfUnit, [
|
||||
'top', 'bottom', 'left', 'right',
|
||||
|
||||
@@ -28,7 +28,6 @@ export const Button = props => {
|
||||
selected,
|
||||
tooltip,
|
||||
tooltipPosition,
|
||||
tooltipOverrideLong,
|
||||
ellipsis,
|
||||
compact,
|
||||
circular,
|
||||
@@ -49,7 +48,7 @@ export const Button = props => {
|
||||
}
|
||||
// IE8: Use a lowercase "onclick" because synthetic events are fucked.
|
||||
// IE8: Use an "unselectable" prop because "user-select" doesn't work.
|
||||
return (
|
||||
let buttonContent = (
|
||||
<Box
|
||||
className={classes([
|
||||
'Button',
|
||||
@@ -106,14 +105,18 @@ export const Button = props => {
|
||||
rotation={iconRotation}
|
||||
spin={iconSpin} />
|
||||
)}
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
content={tooltip}
|
||||
overrideLong={tooltipOverrideLong}
|
||||
position={tooltipPosition} />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
if (tooltip) {
|
||||
buttonContent = (
|
||||
<Tooltip content={tooltip} position={tooltipPosition}>
|
||||
{buttonContent}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return buttonContent;
|
||||
};
|
||||
|
||||
Button.defaultHooks = pureComponentHooks;
|
||||
@@ -234,14 +237,13 @@ export class ButtonInput extends Component {
|
||||
iconSpin,
|
||||
tooltip,
|
||||
tooltipPosition,
|
||||
tooltipOverrideLong,
|
||||
color = 'default',
|
||||
placeholder,
|
||||
maxLength,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
let buttonContent = (
|
||||
<Box
|
||||
className={classes([
|
||||
'Button',
|
||||
@@ -281,15 +283,21 @@ export class ButtonInput extends Component {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
content={tooltip}
|
||||
overrideLong={tooltipOverrideLong}
|
||||
position={tooltipPosition}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
if (tooltip) {
|
||||
buttonContent = (
|
||||
<Tooltip
|
||||
content={tooltip}
|
||||
position={tooltipPosition}
|
||||
>
|
||||
{buttonContent}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return buttonContent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import { computeBoxProps } from "./Box";
|
||||
import { Stack } from "./Stack";
|
||||
import { ProgressBar } from "./ProgressBar";
|
||||
import { Button } from "./Button";
|
||||
import { Component } from 'inferno';
|
||||
|
||||
const ZOOM_MIN_VAL = 0.5;
|
||||
const ZOOM_MAX_VAL = 1.5;
|
||||
|
||||
const ZOOM_INCREMENT = 0.1;
|
||||
|
||||
export class InfinitePlane extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
mouseDown: false,
|
||||
|
||||
left: 0,
|
||||
top: 0,
|
||||
|
||||
lastLeft: 0,
|
||||
lastTop: 0,
|
||||
|
||||
zoom: 1,
|
||||
};
|
||||
|
||||
this.handleMouseDown = this.handleMouseDown.bind(this);
|
||||
this.handleMouseMove = this.handleMouseMove.bind(this);
|
||||
this.handleZoomIncrease = this.handleZoomIncrease.bind(this);
|
||||
this.handleZoomDecrease = this.handleZoomDecrease.bind(this);
|
||||
this.onMouseUp = this.onMouseUp.bind(this);
|
||||
|
||||
this.doOffsetMouse = this.doOffsetMouse.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener("mouseup", this.onMouseUp);
|
||||
|
||||
window.addEventListener("mousedown", this.doOffsetMouse);
|
||||
window.addEventListener("mousemove", this.doOffsetMouse);
|
||||
window.addEventListener("mouseup", this.doOffsetMouse);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener("mouseup", this.onMouseUp);
|
||||
|
||||
window.removeEventListener("mousedown", this.doOffsetMouse);
|
||||
window.removeEventListener("mousemove", this.doOffsetMouse);
|
||||
window.removeEventListener("mouseup", this.doOffsetMouse);
|
||||
}
|
||||
|
||||
doOffsetMouse(event) {
|
||||
const { zoom } = this.state;
|
||||
event.screenZoomX = event.screenX * Math.pow(zoom, -1);
|
||||
event.screenZoomY = event.screenY * Math.pow(zoom, -1);
|
||||
}
|
||||
|
||||
handleMouseDown(event) {
|
||||
this.setState((state) => {
|
||||
return {
|
||||
mouseDown: true,
|
||||
lastLeft: event.clientX - state.left,
|
||||
lastTop: event.clientY - state.top,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
this.setState({
|
||||
mouseDown: false,
|
||||
});
|
||||
}
|
||||
|
||||
handleZoomIncrease(event) {
|
||||
const { onZoomChange } = this.props;
|
||||
const { zoom } = this.state;
|
||||
const newZoomValue = Math.min(zoom+ZOOM_INCREMENT, ZOOM_MAX_VAL);
|
||||
this.setState({
|
||||
zoom: newZoomValue,
|
||||
});
|
||||
if (onZoomChange) {
|
||||
onZoomChange(newZoomValue);
|
||||
}
|
||||
}
|
||||
|
||||
handleZoomDecrease(event) {
|
||||
const { onZoomChange } = this.props;
|
||||
const { zoom } = this.state;
|
||||
const newZoomValue = Math.max(zoom-ZOOM_INCREMENT, ZOOM_MIN_VAL);
|
||||
this.setState({
|
||||
zoom: newZoomValue,
|
||||
});
|
||||
|
||||
if (onZoomChange) {
|
||||
onZoomChange(newZoomValue);
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseMove(event) {
|
||||
const {
|
||||
onBackgroundMoved,
|
||||
initialLeft = 0,
|
||||
initialTop = 0,
|
||||
} = this.props;
|
||||
if (this.state.mouseDown) {
|
||||
let newX, newY;
|
||||
this.setState((state) => {
|
||||
newX = event.clientX - state.lastLeft;
|
||||
newY = event.clientY - state.lastTop;
|
||||
return {
|
||||
left: newX,
|
||||
top: newY,
|
||||
};
|
||||
});
|
||||
if (onBackgroundMoved) {
|
||||
onBackgroundMoved(newX+initialLeft, newY+initialTop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
backgroundImage,
|
||||
imageWidth,
|
||||
initialLeft = 0,
|
||||
initialTop = 0,
|
||||
...rest
|
||||
} = this.props;
|
||||
const {
|
||||
left,
|
||||
top,
|
||||
zoom,
|
||||
} = this.state;
|
||||
|
||||
const finalLeft = initialLeft + left;
|
||||
const finalTop = initialTop + top;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.ref}
|
||||
{...computeBoxProps({
|
||||
...rest,
|
||||
style: {
|
||||
...rest.style,
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
},
|
||||
})}
|
||||
>
|
||||
<div
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseMove={this.handleMouseMove}
|
||||
style={{
|
||||
"position": "fixed",
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
"background-image": `url("${backgroundImage}")`,
|
||||
"background-position": `${finalLeft}px ${finalTop}px`,
|
||||
"background-repeat": "repeat",
|
||||
"background-size": `${zoom*imageWidth}px`,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseMove={this.handleMouseMove}
|
||||
style={{
|
||||
"position": "fixed",
|
||||
"transform": `translate(${finalLeft}px, ${finalTop}px) scale(${zoom})`,
|
||||
"transform-origin": "top left",
|
||||
"height": "100%",
|
||||
"width": "100%",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<Stack
|
||||
position="absolute"
|
||||
width="100%"
|
||||
>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="minus"
|
||||
onClick={this.handleZoomDecrease}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={1}>
|
||||
<ProgressBar
|
||||
minValue={ZOOM_MIN_VAL}
|
||||
value={zoom}
|
||||
maxValue={ZOOM_MAX_VAL}
|
||||
>
|
||||
{zoom}x
|
||||
</ProgressBar>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="plus"
|
||||
onClick={this.handleZoomIncrease}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { createPopper, OptionsGeneric } from "@popperjs/core";
|
||||
import { Component, findDOMfromVNode, InfernoNode, render } from "inferno";
|
||||
|
||||
type PopperProps = {
|
||||
popperContent: InfernoNode;
|
||||
options?: Partial<OptionsGeneric<unknown>>;
|
||||
additionalStyles?: CSSProperties,
|
||||
};
|
||||
|
||||
export class Popper extends Component<PopperProps> {
|
||||
static id: number = 0;
|
||||
|
||||
renderedContent: HTMLDivElement;
|
||||
popperInstance: ReturnType<typeof createPopper>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
Popper.id += 1;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
additionalStyles,
|
||||
options,
|
||||
} = this.props;
|
||||
|
||||
this.renderedContent = document.createElement("div");
|
||||
if (additionalStyles) {
|
||||
for (const [attribute, value] of Object.entries(additionalStyles)) {
|
||||
this.renderedContent.style[attribute] = value;
|
||||
}
|
||||
}
|
||||
|
||||
this.renderPopperContent(() => {
|
||||
document.body.appendChild(this.renderedContent);
|
||||
|
||||
this.popperInstance = createPopper(
|
||||
// HACK: We don't want to create a wrapper, as it could break the layout
|
||||
// of consumers, so we do the inferno equivalent of `findDOMNode(this)`.
|
||||
// This is usually bad as refs are usually better, but refs did
|
||||
// not work in this case, as they weren't propagating correctly.
|
||||
// A previous attempt was made as a render prop that passed an ID,
|
||||
// but this made consuming use too unwieldly.
|
||||
// This code is copied from `findDOMNode` in inferno-extras.
|
||||
// Because this component is written in TypeScript, we will know
|
||||
// immediately if this internal variable is removed.
|
||||
findDOMfromVNode(this.$LI, true),
|
||||
this.renderedContent,
|
||||
options,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.renderPopperContent(() => this.popperInstance?.update());
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.popperInstance?.destroy();
|
||||
this.renderedContent.remove();
|
||||
this.renderedContent = null;
|
||||
}
|
||||
|
||||
renderPopperContent(callback: () => void) {
|
||||
render(this.props.popperContent, this.renderedContent, callback);
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
@@ -35,15 +35,16 @@ export const RoundGauge = props => {
|
||||
minValue,
|
||||
maxValue);
|
||||
const clampedValue = clamp01(scaledValue);
|
||||
let scaledRanges = ranges ? {} : { "primary": [0, 1] };
|
||||
if (ranges)
|
||||
{ Object.keys(ranges).forEach(x => {
|
||||
const range = ranges[x];
|
||||
scaledRanges[x] = [
|
||||
scale(range[0], minValue, maxValue),
|
||||
scale(range[1], minValue, maxValue),
|
||||
];
|
||||
}); }
|
||||
const scaledRanges = ranges ? {} : { "primary": [0, 1] };
|
||||
if (ranges) {
|
||||
Object.keys(ranges).forEach(x => {
|
||||
const range = ranges[x];
|
||||
scaledRanges[x] = [
|
||||
scale(range[0], minValue, maxValue),
|
||||
scale(range[1], minValue, maxValue),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
let alertColor = null;
|
||||
if (alertAfter < value) {
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { classes } from 'common/react';
|
||||
|
||||
export const Tooltip = props => {
|
||||
const {
|
||||
content,
|
||||
overrideLong = false,
|
||||
position = 'bottom',
|
||||
} = props;
|
||||
// Empirically calculated length of the string,
|
||||
// at which tooltip text starts to overflow.
|
||||
const long = typeof content === 'string'
|
||||
&& (content.length > 35 && !overrideLong);
|
||||
return (
|
||||
<div
|
||||
className={classes([
|
||||
'Tooltip',
|
||||
long && 'Tooltip--long',
|
||||
position && 'Tooltip--' + position,
|
||||
])}
|
||||
data-tooltip={content} />
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
import { Placement } from '@popperjs/core';
|
||||
import { Component, findDOMfromVNode, InfernoNode } from 'inferno';
|
||||
import { Popper } from "./Popper";
|
||||
|
||||
const DEFAULT_PLACEMENT = "top";
|
||||
|
||||
type TooltipProps = {
|
||||
children?: InfernoNode;
|
||||
content: string;
|
||||
position?: Placement,
|
||||
};
|
||||
|
||||
type TooltipState = {
|
||||
hovered: boolean;
|
||||
};
|
||||
|
||||
export class Tooltip extends Component<TooltipProps, TooltipState> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
hovered: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// HACK: We don't want to create a wrapper, as it could break the layout
|
||||
// of consumers, so we do the inferno equivalent of `findDOMNode(this)`.
|
||||
// My attempt to avoid this was a render prop that passed in
|
||||
// callbacks to onmouseenter and onmouseleave, but this was unwiedly
|
||||
// to consumers, specifically buttons.
|
||||
// This code is copied from `findDOMNode` in inferno-extras.
|
||||
// Because this component is written in TypeScript, we will know
|
||||
// immediately if this internal variable is removed.
|
||||
const domNode = findDOMfromVNode(this.$LI, true);
|
||||
|
||||
domNode.addEventListener("mouseenter", () => {
|
||||
this.setState({
|
||||
hovered: true,
|
||||
});
|
||||
});
|
||||
|
||||
domNode.addEventListener("mouseleave", () => {
|
||||
this.setState({
|
||||
hovered: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Popper
|
||||
options={{
|
||||
placement: this.props.position || "auto",
|
||||
}}
|
||||
popperContent={
|
||||
<div
|
||||
className="Tooltip"
|
||||
style={{
|
||||
opacity: this.state.hovered ? 1 : 0,
|
||||
}}>
|
||||
{this.props.content}
|
||||
</div>
|
||||
}
|
||||
additionalStyles={{
|
||||
"pointer-events": "none",
|
||||
}}>
|
||||
{this.props.children}
|
||||
</Popper>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export { Dropdown } from './Dropdown';
|
||||
export { Flex } from './Flex';
|
||||
export { Grid } from './Grid';
|
||||
export { Icon } from './Icon';
|
||||
export { InfinitePlane } from './InfinitePlane';
|
||||
export { Input } from './Input';
|
||||
export { Knob } from './Knob';
|
||||
export { LabeledControls } from './LabeledControls';
|
||||
@@ -28,6 +29,7 @@ export { Modal } from './Modal';
|
||||
export { NoticeBox } from './NoticeBox';
|
||||
export { NumberInput } from './NumberInput';
|
||||
export { ProgressBar } from './ProgressBar';
|
||||
export { Popper } from './Popper';
|
||||
export { RoundGauge } from './RoundGauge';
|
||||
export { Section } from './Section';
|
||||
export { Slider } from './Slider';
|
||||
|
||||
@@ -59,11 +59,12 @@ export const CSS_COLORS = [
|
||||
'label',
|
||||
];
|
||||
|
||||
/* IF YOU CHANGE THIS KEEP IT IN SYNC WITH CHAT CSS */
|
||||
export const RADIO_CHANNELS = [
|
||||
{
|
||||
name: 'Syndicate',
|
||||
freq: 1213,
|
||||
color: '#a52a2a',
|
||||
color: '#8f4a4b',
|
||||
},
|
||||
{
|
||||
name: 'Red Team',
|
||||
@@ -75,6 +76,16 @@ export const RADIO_CHANNELS = [
|
||||
freq: 1217,
|
||||
color: '#3434fd',
|
||||
},
|
||||
{
|
||||
name: 'Green Team',
|
||||
freq: 1219,
|
||||
color: '#34fd34',
|
||||
},
|
||||
{
|
||||
name: 'Yellow Team',
|
||||
freq: 1221,
|
||||
color: '#fdfd34',
|
||||
},
|
||||
{
|
||||
name: 'CentCom',
|
||||
freq: 1337,
|
||||
@@ -98,7 +109,7 @@ export const RADIO_CHANNELS = [
|
||||
{
|
||||
name: 'Command',
|
||||
freq: 1353,
|
||||
color: '#5177ff',
|
||||
color: '#fcdf03',
|
||||
},
|
||||
{
|
||||
name: 'Medical',
|
||||
|
||||
@@ -39,7 +39,7 @@ export const debugMiddleware = store => {
|
||||
};
|
||||
|
||||
export const relayMiddleware = store => {
|
||||
const devServer = require('tgui-dev-server/link/client');
|
||||
const devServer = require('tgui-dev-server/link/client.cjs');
|
||||
const externalBrowser = location.search === '?external';
|
||||
if (externalBrowser) {
|
||||
devServer.subscribe(msg => {
|
||||
|
||||
@@ -105,7 +105,7 @@ export const formatMoney = (value, precision = 0) => {
|
||||
*/
|
||||
export const formatDb = value => {
|
||||
const db = 20 * Math.log(value) / Math.log(10);
|
||||
const sign = db >= 0 ? '+' : db < 0 ? '–' : '';
|
||||
const sign = db >= 0 ? '+' : '–';
|
||||
let formatted = Math.abs(db);
|
||||
if (formatted === Infinity) {
|
||||
formatted = 'Inf';
|
||||
|
||||
@@ -19,7 +19,7 @@ import './styles/themes/wizard.scss';
|
||||
import './styles/themes/clockcult.scss';
|
||||
|
||||
import { perf } from 'common/perf';
|
||||
import { setupHotReloading } from 'tgui-dev-server/link/client';
|
||||
import { setupHotReloading } from 'tgui-dev-server/link/client.cjs';
|
||||
import { setupHotKeys } from './hotkeys';
|
||||
import { captureExternalLinks } from './links';
|
||||
import { createRenderer } from './renderer';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useSharedState } from '../backend';
|
||||
import { Button, LabeledList, NoticeBox, Section, Tabs } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
@@ -10,8 +9,7 @@ export const AbductorConsole = (props, context) => {
|
||||
<Window
|
||||
theme="abductor"
|
||||
width={600}
|
||||
height={532}
|
||||
resizable>
|
||||
height={532}>
|
||||
<Window.Content scrollable>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
@@ -33,10 +31,10 @@ export const AbductorConsole = (props, context) => {
|
||||
<Abductsoft />
|
||||
)}
|
||||
{tab === 2 && (
|
||||
<Fragment>
|
||||
<>
|
||||
<EmergencyTeleporter />
|
||||
<VestSettings />
|
||||
</Fragment>
|
||||
</>
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
@@ -60,7 +58,7 @@ const Abductsoft = (props, context) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Collected Samples">
|
||||
@@ -71,7 +69,7 @@ const Abductsoft = (props, context) => {
|
||||
<GenericUplink
|
||||
currencyAmount={credits}
|
||||
currencySymbol="Credits" />
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Button, LabeledList, Section, Box, NoticeBox, Table } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { AdventureDataProvider, AdventureScreen } from './ExodroneConsole';
|
||||
import { formatTime } from '../format';
|
||||
|
||||
type Adventure = {
|
||||
ref: string;
|
||||
name: string;
|
||||
id: string;
|
||||
approved: boolean;
|
||||
uploader: string;
|
||||
version: number;
|
||||
timestamp: string;
|
||||
json_status: string;
|
||||
};
|
||||
|
||||
type AdventureBrowserData = AdventureDataProvider & {
|
||||
adventures: Array<Adventure>;
|
||||
feedback_message: string;
|
||||
play_mode: boolean;
|
||||
adventure_data: any;
|
||||
delay_time: number;
|
||||
delay_message: string;
|
||||
};
|
||||
|
||||
const AdventureEntry = (props, context) => {
|
||||
const { data, act } = useBackend<AdventureBrowserData>(context);
|
||||
const { entry_ref, close }: { entry_ref: string, close: () => void } = props;
|
||||
const entry = data.adventures.find(x => x.ref === entry_ref);
|
||||
return (
|
||||
<Section>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="ID">{entry.id}</LabeledList.Item>
|
||||
<LabeledList.Item label="Name">{entry.name}</LabeledList.Item>
|
||||
<LabeledList.Item label="JSON Version">{entry.version}</LabeledList.Item>
|
||||
<LabeledList.Item label="Uploader">{entry.uploader}</LabeledList.Item>
|
||||
<LabeledList.Item label="Last Update">{entry.timestamp}</LabeledList.Item>
|
||||
<LabeledList.Item label="Approved">
|
||||
<Button.Checkbox
|
||||
checked={entry.approved}
|
||||
onClick={() => act("approve", { ref: entry.ref })}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="JSON">
|
||||
{entry.json_status}
|
||||
<Button onClick={() => act("upload", { ref: entry.ref })} content="Upload" />
|
||||
<Button onClick={() => act("download", { ref: entry.ref })} content="Download" />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
<Button.Confirm onClick={() => { close(); act("delete", { ref: entry.ref }); }} content="Delete" />
|
||||
<Button onClick={() => act("play", { ref: entry.ref })} content="Play" />
|
||||
<Button onClick={() => act("refresh", { ref: entry.ref })} content="Refresh" />
|
||||
<Button.Confirm onClick={() => act("save", { ref: entry.ref })} content="Save" />
|
||||
<Button onClick={close} content="Close" />
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const AdventureList = (props, context) => {
|
||||
const { data, act } = useBackend<AdventureBrowserData>(context);
|
||||
const [
|
||||
openAdventure,
|
||||
setOpenAdventure,
|
||||
] = useLocalState(context, 'openAdventure', null);
|
||||
|
||||
return (
|
||||
<>
|
||||
{openAdventure && (
|
||||
<AdventureEntry
|
||||
entry_ref={openAdventure}
|
||||
close={() => setOpenAdventure(null)} />
|
||||
)}
|
||||
{!openAdventure && (
|
||||
<Table>
|
||||
<Table.Row>
|
||||
<Table.Cell color="label">ID</Table.Cell>
|
||||
<Table.Cell color="label">Title</Table.Cell>
|
||||
<Table.Cell color="label">Edit</Table.Cell>
|
||||
</Table.Row>
|
||||
{data.adventures.map(p => (
|
||||
<Table.Row
|
||||
key={p.ref}
|
||||
className="candystripe">
|
||||
<Table.Cell>{p.id}</Table.Cell>
|
||||
<Table.Cell>{p.name}</Table.Cell>
|
||||
<Table.Cell><Button icon="edit" onClick={() => setOpenAdventure(p.ref)} /></Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
<Table.Row>
|
||||
<Button onClick={() => act("create")}>Create New</Button>
|
||||
</Table.Row>
|
||||
</Table>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const DebugPlayer = (props, context) => {
|
||||
const { data, act } = useBackend<AdventureBrowserData>(context);
|
||||
return (
|
||||
<Section
|
||||
title="Playtest"
|
||||
buttons={<Button onClick={() => act("end_play")}>End Playtest</Button>}>
|
||||
{data.delay_time > 0
|
||||
? <Box>DELAY {formatTime(data.delay_time)} / {data.delay_message}</Box>
|
||||
: <AdventureScreen hide_status />}
|
||||
</Section>);
|
||||
};
|
||||
|
||||
export const AdventureBrowser = (props, context) => {
|
||||
const { data } = useBackend<AdventureBrowserData>(context);
|
||||
|
||||
return (
|
||||
<Window width={650} height={500} title="Adventure Manager">
|
||||
<Window.Content>
|
||||
{!!data.feedback_message && (
|
||||
<NoticeBox>
|
||||
{data.feedback_message}
|
||||
</NoticeBox>
|
||||
)}
|
||||
{data.play_mode ? (<DebugPlayer />) : (<AdventureList />)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
@@ -68,7 +67,7 @@ export const AiAirlock = (props, context) => {
|
||||
label="Electrify"
|
||||
color={statusElectrify.color}
|
||||
buttons={(
|
||||
<Fragment>
|
||||
<>
|
||||
<Button
|
||||
icon="wrench"
|
||||
disabled={!(data.wires.shock && data.shock === 0)}
|
||||
@@ -84,7 +83,7 @@ export const AiAirlock = (props, context) => {
|
||||
disabled={!data.wires.shock}
|
||||
content="Permanent"
|
||||
onClick={() => act('shock-perm')} />
|
||||
</Fragment>
|
||||
</>
|
||||
)}>
|
||||
{data.shock === 2 ? 'Safe' : 'Electrified'}
|
||||
{' '}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, LabeledList, NoticeBox, ProgressBar, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
@@ -7,8 +6,7 @@ export const AiRestorer = () => {
|
||||
return (
|
||||
<Window
|
||||
width={370}
|
||||
height={360}
|
||||
resizable>
|
||||
height={360}>
|
||||
<Window.Content scrollable>
|
||||
<AiRestorerContent />
|
||||
</Window.Content>
|
||||
@@ -29,7 +27,7 @@ export const AiRestorerContent = (props, context) => {
|
||||
ejectable,
|
||||
} = data;
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
{error && (
|
||||
<NoticeBox textAlign="center">
|
||||
{error}
|
||||
@@ -93,6 +91,6 @@ export const AiRestorerContent = (props, context) => {
|
||||
</Section>
|
||||
</Section>
|
||||
)}
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -12,8 +12,7 @@ export const AirAlarm = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={440}
|
||||
height={650}
|
||||
resizable>
|
||||
height={650}>
|
||||
<Window.Content scrollable>
|
||||
<InterfaceLockNoticeBox />
|
||||
<AirAlarmStatus />
|
||||
@@ -48,7 +47,7 @@ const AirAlarmStatus = (props, context) => {
|
||||
<Section title="Air Status">
|
||||
<LabeledList>
|
||||
{entries.length > 0 && (
|
||||
<Fragment>
|
||||
<>
|
||||
{entries.map(entry => {
|
||||
const status = dangerMap[entry.danger_level] || dangerMap[0];
|
||||
return (
|
||||
@@ -72,7 +71,7 @@ const AirAlarmStatus = (props, context) => {
|
||||
|| data.fire_alarm && 'Fire Alarm'
|
||||
|| 'Nominal'}
|
||||
</LabeledList.Item>
|
||||
</Fragment>
|
||||
</>
|
||||
) || (
|
||||
<LabeledList.Item
|
||||
label="Warning"
|
||||
@@ -145,7 +144,7 @@ const AirAlarmControlHome = (props, context) => {
|
||||
atmos_alarm,
|
||||
} = data;
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<Button
|
||||
icon={atmos_alarm
|
||||
? 'exclamation-triangle'
|
||||
@@ -183,7 +182,7 @@ const AirAlarmControlHome = (props, context) => {
|
||||
icon="chart-bar"
|
||||
content="Alarm Thresholds"
|
||||
onClick={() => setScreen('thresholds')} />
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -6,68 +6,143 @@
|
||||
|
||||
import { clamp01 } from 'common/math';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Flex } from '../components';
|
||||
import { Component, createRef } from 'inferno';
|
||||
import { Box, Flex, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import {
|
||||
KEY_ENTER,
|
||||
KEY_LEFT,
|
||||
KEY_RIGHT,
|
||||
KEY_SPACE,
|
||||
KEY_TAB,
|
||||
} from 'common/keycodes';
|
||||
|
||||
export const AlertModal = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
title,
|
||||
message,
|
||||
buttons,
|
||||
timeout,
|
||||
} = data;
|
||||
export class AlertModal extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
return (
|
||||
<Window
|
||||
title={title}
|
||||
width={350}
|
||||
height={150}
|
||||
resizable>
|
||||
{timeout !== undefined && <Loader value={timeout} />}
|
||||
<Window.Content>
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex.Item grow={1}>
|
||||
<Flex
|
||||
direction="column"
|
||||
className="AlertModal__Message"
|
||||
height="100%">
|
||||
<Flex.Item>
|
||||
<Box m={1}>
|
||||
{message}
|
||||
</Box>
|
||||
this.buttonRefs = [createRef()];
|
||||
this.state = { current: 0 };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { data } = useBackend(this.context);
|
||||
const { buttons, autofocus } = data;
|
||||
const { current } = this.state;
|
||||
const button = this.buttonRefs[current].current;
|
||||
|
||||
// Fill ref array with refs for other buttons
|
||||
for (let i = 1; i < buttons.length; i++) {
|
||||
this.buttonRefs.push(createRef());
|
||||
}
|
||||
|
||||
if (autofocus) {
|
||||
setTimeout(() => button.focus(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
setCurrent(current, isArrowKey) {
|
||||
const { data } = useBackend(this.context);
|
||||
const { buttons } = data;
|
||||
|
||||
// Mimic alert() behavior for tabs and arrow keys
|
||||
if (current >= buttons.length) {
|
||||
current = isArrowKey ? current - 1 : 0;
|
||||
} else if (current < 0) {
|
||||
current = isArrowKey ? 0 : buttons.length - 1;
|
||||
}
|
||||
|
||||
const button = this.buttonRefs[current].current;
|
||||
|
||||
// Prevents an error from occurring on close
|
||||
if (button) {
|
||||
setTimeout(() => button.focus(), 1);
|
||||
}
|
||||
this.setState({ current });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { act, data } = useBackend(this.context);
|
||||
const { title, message, buttons, timeout } = data;
|
||||
const { current } = this.state;
|
||||
const focusCurrentButton = () => this.setCurrent(current, false);
|
||||
|
||||
return (
|
||||
<Window
|
||||
title={title}
|
||||
width={350}
|
||||
height={150}
|
||||
canClose={timeout > 0}>
|
||||
{timeout && <Loader value={timeout} />}
|
||||
<Window.Content
|
||||
onFocus={focusCurrentButton}
|
||||
onClick={focusCurrentButton}>
|
||||
<Section fill>
|
||||
<Flex direction="column" height="100%">
|
||||
<Flex.Item grow={1}>
|
||||
<Flex
|
||||
direction="column"
|
||||
className="AlertModal__Message"
|
||||
height="100%">
|
||||
<Flex.Item>
|
||||
<Box m={1}>
|
||||
{message}
|
||||
</Box>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
<Flex.Item my={2}>
|
||||
<Flex className="AlertModal__Buttons">
|
||||
{buttons.map((button, buttonIndex) => (
|
||||
<Flex.Item key={buttonIndex} mx={1}>
|
||||
<div
|
||||
ref={this.buttonRefs[buttonIndex]}
|
||||
className="Button Button--color--default"
|
||||
px={3}
|
||||
onClick={() => act("choose", { choice: button })}
|
||||
onKeyDown={e => {
|
||||
const keyCode = window.event ? e.which : e.keyCode;
|
||||
|
||||
/**
|
||||
* Simulate a click when pressing space or enter,
|
||||
* allow keyboard navigation, override tab behavior
|
||||
*/
|
||||
if (keyCode === KEY_SPACE || keyCode === KEY_ENTER) {
|
||||
act("choose", { choice: button });
|
||||
} else if (
|
||||
keyCode === KEY_LEFT
|
||||
|| (e.shiftKey && keyCode === KEY_TAB)
|
||||
) {
|
||||
this.setCurrent(current - 1, keyCode === KEY_LEFT);
|
||||
} else if (
|
||||
keyCode === KEY_RIGHT || keyCode === KEY_TAB
|
||||
) {
|
||||
this.setCurrent(current + 1, keyCode === KEY_RIGHT);
|
||||
}
|
||||
}}>
|
||||
{button}
|
||||
</div>
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
<Flex.Item my={2}>
|
||||
<Flex className="AlertModal__Buttons">
|
||||
{buttons.map(button => (
|
||||
<Flex.Item key={button} mx={1}>
|
||||
<Button
|
||||
px={3}
|
||||
onClick={() => act("choose", { choice: button })}>
|
||||
{button}
|
||||
</Button>
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const Loader = props => {
|
||||
const { value } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="AlertModal__Loader">
|
||||
<div className="AlertModal__Loader">
|
||||
<Box
|
||||
className="AlertModal__LoaderProgress"
|
||||
style={{
|
||||
width: clamp01(value) * 100 + '%',
|
||||
}} />
|
||||
style={{ width: clamp01(value) * 100 + '%' }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Blink, BlockQuote, Box, Dimmer, Icon, Section, Stack } from '../components';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
type Objective = {
|
||||
count: number;
|
||||
name: string;
|
||||
explanation: string;
|
||||
complete: BooleanLike;
|
||||
was_uncompleted: BooleanLike;
|
||||
reward: number;
|
||||
}
|
||||
|
||||
type Info = {
|
||||
objectives: Objective[];
|
||||
};
|
||||
|
||||
export const AntagInfoBrainwashed = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={400}
|
||||
height={400}
|
||||
theme="abductor">
|
||||
<Window.Content
|
||||
backgroundColor="#722e7d">
|
||||
<Icon
|
||||
size={16}
|
||||
name="flushed"
|
||||
color="#3f1945"
|
||||
position="absolute"
|
||||
top="42%"
|
||||
left="26%" />
|
||||
<Section fill>
|
||||
<Stack align="baseline" vertical fill>
|
||||
<Stack.Item fontFamily="Wingdings">
|
||||
Hey, no! Stop translating this!
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={-0.25} fontSize="20px">
|
||||
Your mind reels...
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={-0.25} fontSize="20px">
|
||||
It is focusing on a single purpose...
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={1.5} grow >
|
||||
<ObjectivePrintout />
|
||||
</Stack.Item>
|
||||
<Stack.Item fontSize="20px">
|
||||
Follow the Directives, at any cost!
|
||||
</Stack.Item>
|
||||
<Stack.Item fontFamily="Wingdings">
|
||||
You ruined my cool font effect.
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const ObjectivePrintout = (props, context) => {
|
||||
const { data } = useBackend<Info>(context);
|
||||
const {
|
||||
objectives,
|
||||
} = data;
|
||||
return (
|
||||
<Stack vertical>
|
||||
<Stack.Item bold>
|
||||
Your current objectives:
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
{!objectives && "None!"
|
||||
|| objectives.map(objective => (
|
||||
<>
|
||||
<Stack.Item key={objective.count}>
|
||||
#{objective.count}: {objective.explanation}
|
||||
</Stack.Item>
|
||||
<Stack.Item textColor="red">
|
||||
This Directive must be followed.
|
||||
</Stack.Item>
|
||||
</>
|
||||
)) }
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
import { BlockQuote, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const goodstyle = {
|
||||
color: 'lightgreen',
|
||||
};
|
||||
|
||||
const badstyle = {
|
||||
color: 'red',
|
||||
};
|
||||
|
||||
const noticestyle = {
|
||||
color: 'lightblue',
|
||||
};
|
||||
|
||||
export const AntagInfoMorph = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={620}
|
||||
height={170}
|
||||
theme="abductor">
|
||||
<Window.Content>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item fontSize="25px">
|
||||
You are a morph...
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<BlockQuote>
|
||||
...a shapeshifting abomination that can eat almost anything.
|
||||
You may take the form of anything you can
|
||||
see by <span style={noticestyle}>shift-clicking</span> it.
|
||||
<span style={badstyle}>
|
||||
 This process will alert any nearby observers.
|
||||
</span> While morphed,
|
||||
you move faster, but are unable to
|
||||
attack creatures or eat anything.
|
||||
In addition,
|
||||
<span style={badstyle}>
|
||||
 anyone within three tiles will note an uncanny
|
||||
wrongness if examining you.
|
||||
</span> You can attack any item or dead
|
||||
creature to consume it -
|
||||
<span style={goodstyle}>
|
||||
 creatures will restore your health.
|
||||
</span> Finally, you can restore yourself
|
||||
to your original form while morphed
|
||||
by <span style={noticestyle}>shift-clicking</span> yourself.
|
||||
</BlockQuote>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import { BlockQuote, LabeledList, Section, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const tipstyle = {
|
||||
color: 'white',
|
||||
};
|
||||
|
||||
const noticestyle = {
|
||||
color: 'lightblue',
|
||||
};
|
||||
|
||||
export const AntagInfoNightmare = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={620}
|
||||
height={340}>
|
||||
<Window.Content backgroundColor="#0d0d0d">
|
||||
<Stack fill>
|
||||
<Stack.Item width="46.2%">
|
||||
<Section fill>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item fontSize="25px">
|
||||
You are a Nightmare.
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<BlockQuote>
|
||||
You are a creature from beyond the stars that
|
||||
has incredibly strong powers in the darkness,
|
||||
becoming nigh unbeatable. Unfortunately, you
|
||||
wither and burn away in the light. You must use your
|
||||
<span style={noticestyle}> light eater</span> to
|
||||
dim the station, making hunting easier.
|
||||
</BlockQuote>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item textColor="label">
|
||||
<span style={tipstyle}>
|
||||
Tip #1: 
|
||||
</span>
|
||||
Move often. The station will be hunting you
|
||||
after you are discovered, so don't
|
||||
stay in one area for long.<br />
|
||||
<span style={tipstyle}>
|
||||
Tip #2: 
|
||||
</span>
|
||||
Pick unfair fights. You are incredibly strong
|
||||
in one versus one situations, use it. The more
|
||||
you fight, the harder it will be to keep it dark.<br />
|
||||
<span style={tipstyle}>
|
||||
Tip #3: 
|
||||
</span>
|
||||
Fully destroy APCs when possible. Instead of
|
||||
hunting lights that can be fixed, hunt the APCs
|
||||
which are harder to repair.
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item width="53%">
|
||||
<Section fill title="Powers">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Shadow Dance">
|
||||
Whilst in the shadows, you are immune to all melee
|
||||
and ranged attacks, whilst also rapidly regenerating
|
||||
health.
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Shadow Walk">
|
||||
You are allowed unlimited, unrestricted
|
||||
movement in the dark. Light will pull you out of this.
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Heart of Darkness">
|
||||
Your heart invites the shadows. If you
|
||||
die in the darkness, you will eventually
|
||||
revive if left alone.
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Light Eater">
|
||||
Your twisted appendage. It will consume
|
||||
the light of what it touches,
|
||||
be it victim or object.
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,246 @@
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { multiline } from 'common/string';
|
||||
import { BlockQuote, Button, Dimmer, Section, Stack } from '../components';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const allystyle = {
|
||||
fontWeight: 'bold',
|
||||
color: 'yellow',
|
||||
};
|
||||
|
||||
const badstyle = {
|
||||
color: 'red',
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
|
||||
const goalstyle = {
|
||||
color: 'lightblue',
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
|
||||
type Objective = {
|
||||
count: number;
|
||||
name: string;
|
||||
explanation: string;
|
||||
}
|
||||
|
||||
type Info = {
|
||||
phrases: string;
|
||||
responses: string;
|
||||
theme: string;
|
||||
allies: string;
|
||||
goal: string;
|
||||
intro: string;
|
||||
code: string;
|
||||
has_uplink: BooleanLike;
|
||||
uplink_intro: string;
|
||||
uplink_unlock_info: string;
|
||||
objectives: Objective[];
|
||||
};
|
||||
|
||||
const ObjectivePrintout = (props, context) => {
|
||||
const { data } = useBackend<Info>(context);
|
||||
const {
|
||||
objectives,
|
||||
} = data;
|
||||
return (
|
||||
<Stack vertical>
|
||||
<Stack.Item bold>
|
||||
Your current objectives:
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
{!objectives && "None!"
|
||||
|| objectives.map(objective => (
|
||||
<Stack.Item key={objective.count}>
|
||||
#{objective.count}: {objective.explanation}
|
||||
</Stack.Item>
|
||||
)) }
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const IntroductionSection = (props, context) => {
|
||||
const { act, data } = useBackend<Info>(context);
|
||||
const {
|
||||
intro,
|
||||
} = data;
|
||||
return (
|
||||
<Section fill title="Intro" scrollable>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item fontSize="25px">
|
||||
{intro}
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<ObjectivePrintout />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const EmployerSection = (props, context) => {
|
||||
const { data } = useBackend<Info>(context);
|
||||
const {
|
||||
allies,
|
||||
goal,
|
||||
} = data;
|
||||
return (
|
||||
<Section
|
||||
fill
|
||||
title="Employer"
|
||||
scrollable
|
||||
buttons={
|
||||
<Button
|
||||
icon="hammer"
|
||||
tooltip={multiline`
|
||||
This is a gameplay suggestion for bored traitors.
|
||||
You don't have to follow it... kinda like spacelaw!`}
|
||||
tooltipPosition="bottom-start">
|
||||
Policy
|
||||
</Button>
|
||||
}>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item grow>
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<span style={allystyle}>
|
||||
Your allegiances:<br />
|
||||
</span>
|
||||
<BlockQuote>
|
||||
{allies}
|
||||
</BlockQuote>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item>
|
||||
<span style={goalstyle}>
|
||||
Employer thoughts:<br />
|
||||
</span>
|
||||
<BlockQuote>
|
||||
{goal}
|
||||
</BlockQuote>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const UplinkSection = (props, context) => {
|
||||
const { data } = useBackend<Info>(context);
|
||||
const {
|
||||
has_uplink,
|
||||
uplink_intro,
|
||||
uplink_unlock_info,
|
||||
code,
|
||||
} = data;
|
||||
return (
|
||||
<Section
|
||||
title="Uplink"
|
||||
mb={!has_uplink && -1}>
|
||||
<Stack fill>
|
||||
{!has_uplink && (
|
||||
<Dimmer>
|
||||
<Stack.Item fontSize="18px">
|
||||
You were not supplied with an uplink.
|
||||
</Stack.Item>
|
||||
</Dimmer>
|
||||
) || (
|
||||
<>
|
||||
<Stack.Item bold>
|
||||
{uplink_intro}
|
||||
<br />
|
||||
<span style={goalstyle}>Code: {code}</span>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item mt="1%">
|
||||
<BlockQuote>
|
||||
{uplink_unlock_info}
|
||||
</BlockQuote>
|
||||
</Stack.Item>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const CodewordsSection = (props, context) => {
|
||||
const { data } = useBackend<Info>(context);
|
||||
const {
|
||||
phrases,
|
||||
responses,
|
||||
} = data;
|
||||
return (
|
||||
<Section title="Codewords">
|
||||
<Stack fill>
|
||||
<Stack.Item grow basis={0}>
|
||||
<BlockQuote>
|
||||
The Syndicate have provided you with the following
|
||||
codewords to identify fellow agents. Use the codewords
|
||||
during regular conversation to identify other agents.
|
||||
Proceed with caution, however, as everyone is a
|
||||
potential foe.
|
||||
<span style={badstyle}>
|
||||
 You have memorized the codewords, allowing you
|
||||
to recognise them when heard.
|
||||
</span>
|
||||
</BlockQuote>
|
||||
</Stack.Item>
|
||||
<Stack.Divider mr={1} />
|
||||
<Stack.Item grow basis={0}>
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
Code Phrases:
|
||||
</Stack.Item>
|
||||
<Stack.Item bold textColor="blue">
|
||||
{phrases}
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
Code Responses:
|
||||
</Stack.Item>
|
||||
<Stack.Item bold textColor="red">
|
||||
{responses}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
export const AntagInfoTraitor = (props, context) => {
|
||||
const { data } = useBackend<Info>(context);
|
||||
const {
|
||||
theme,
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={620}
|
||||
height={580}
|
||||
theme={theme}>
|
||||
<Window.Content>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item grow>
|
||||
<Stack fill>
|
||||
<Stack.Item width="70%">
|
||||
<IntroductionSection />
|
||||
</Stack.Item>
|
||||
<Stack.Item width="30%">
|
||||
<EmployerSection />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<UplinkSection />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<CodewordsSection />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,152 @@
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Blink, BlockQuote, Box, Dimmer, Icon, Section, Stack } from '../components';
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
const teleportstyle = {
|
||||
color: 'yellow',
|
||||
};
|
||||
|
||||
const robestyle = {
|
||||
color: 'lightblue',
|
||||
};
|
||||
|
||||
const destructionstyle = {
|
||||
color: 'red',
|
||||
};
|
||||
|
||||
const defensestyle = {
|
||||
color: 'orange',
|
||||
};
|
||||
|
||||
const transportstyle = {
|
||||
color: 'yellow',
|
||||
};
|
||||
|
||||
const summonstyle = {
|
||||
color: 'cyan',
|
||||
};
|
||||
|
||||
const ritualstyle = {
|
||||
color: 'violet',
|
||||
};
|
||||
|
||||
type Objective = {
|
||||
count: number;
|
||||
name: string;
|
||||
explanation: string;
|
||||
complete: BooleanLike;
|
||||
was_uncompleted: BooleanLike;
|
||||
reward: number;
|
||||
}
|
||||
|
||||
type Info = {
|
||||
objectives: Objective[];
|
||||
};
|
||||
|
||||
export const AntagInfoWizard = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={620}
|
||||
height={580}
|
||||
theme="wizard">
|
||||
<Window.Content>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item grow>
|
||||
<Section scrollable fill>
|
||||
<Stack vertical>
|
||||
<Stack.Item textColor="red" fontSize="20px">
|
||||
You are the Space Wizard!
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<ObjectivePrintout />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Section fill title="Spellbook">
|
||||
<Stack vertical fill>
|
||||
<Stack.Item grow>
|
||||
You have a spellbook which is bound to you. You can use it
|
||||
to choose a magical arsenal.<br />
|
||||
<span style={destructionstyle}>
|
||||
The deadly page has the offensive spells, to destroy your
|
||||
enemies.
|
||||
</span><br />
|
||||
<span style={defensestyle}>
|
||||
The defensive page has defensive spells, to keep yourself
|
||||
alive. Remember, you may be powerful, but you are still
|
||||
only human.
|
||||
</span><br />
|
||||
<span style={transportstyle}>
|
||||
The transport page has mobility spells, very important
|
||||
aspect of staying alive and getting things done.
|
||||
</span><br />
|
||||
<span style={summonstyle}>
|
||||
The summoning page has summoning and other helpful spells
|
||||
for not fighting alone. Careful, not every summon is on your
|
||||
side.
|
||||
</span><br />
|
||||
<span style={ritualstyle}>
|
||||
The rituals page has powerful global effects, that will pit
|
||||
the station against itself. Do mind that these are either
|
||||
expensive, or just for panache.
|
||||
</span>
|
||||
</Stack.Item>
|
||||
<Stack.Item textColor="lightgreen">
|
||||
(If you are unsure what to get or are new to the Federation,
|
||||
go to the "Wizard Approved Loadouts" section.
|
||||
There you will find some kits that work fairly well for new
|
||||
wizards.)
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Section title="Misc Gear">
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<span style={teleportstyle}>Teleport scroll:</span> 4
|
||||
uses to teleport wherever you want.
|
||||
You will not be able to come back to the den, so
|
||||
be sure you have everything ready before departing.<br />
|
||||
<span style={robestyle}>Wizard robes:</span> Used
|
||||
to cast most spells. Your spellbook will let
|
||||
you know which spells cannot be cast without a garb.
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Section textAlign="center" textColor="red" fontSize="20px">
|
||||
Remember: Do not forget to prepare your spells.
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const ObjectivePrintout = (props, context) => {
|
||||
const { data } = useBackend<Info>(context);
|
||||
const {
|
||||
objectives,
|
||||
} = data;
|
||||
return (
|
||||
<Stack vertical>
|
||||
<Stack.Item bold>
|
||||
The Space Wizards Federation has given you the following tasks:
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
{!objectives && "None!"
|
||||
|| objectives.map(objective => (
|
||||
<Stack.Item key={objective.count}>
|
||||
#{objective.count}: {objective.explanation}
|
||||
</Stack.Item>
|
||||
)) }
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, LabeledList, NoticeBox, ProgressBar, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
@@ -8,8 +7,7 @@ export const Apc = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={450}
|
||||
height={445}
|
||||
resizable>
|
||||
height={445}>
|
||||
<Window.Content scrollable>
|
||||
<ApcContent />
|
||||
</Window.Content>
|
||||
@@ -86,7 +84,7 @@ const ApcContent = (props, context) => {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<InterfaceLockNoticeBox />
|
||||
<Section title="Power Status">
|
||||
<LabeledList>
|
||||
@@ -113,7 +111,7 @@ const ApcContent = (props, context) => {
|
||||
color={chargingStatus.color}
|
||||
buttons={(
|
||||
<Button
|
||||
icon={data.chargeMode ? 'sync' : 'close'}
|
||||
icon={data.chargeMode ? 'sync' : 'times'}
|
||||
content={data.chargeMode ? 'Auto' : 'Off'}
|
||||
disabled={locked}
|
||||
onClick={() => act('charge')} />
|
||||
@@ -131,7 +129,7 @@ const ApcContent = (props, context) => {
|
||||
key={channel.title}
|
||||
label={channel.title}
|
||||
buttons={(
|
||||
<Fragment>
|
||||
<>
|
||||
<Box inline mx={2}
|
||||
color={channel.status >= 2 ? 'good' : 'bad'}>
|
||||
{channel.status >= 2 ? 'On' : 'Off'}
|
||||
@@ -156,7 +154,7 @@ const ApcContent = (props, context) => {
|
||||
selected={!locked && channel.status === 0}
|
||||
disabled={locked}
|
||||
onClick={() => act('channel', topicParams.off)} />
|
||||
</Fragment>
|
||||
</>
|
||||
)}>
|
||||
{channel.powerLoad}
|
||||
</LabeledList.Item>
|
||||
@@ -170,7 +168,7 @@ const ApcContent = (props, context) => {
|
||||
<Section
|
||||
title="Misc"
|
||||
buttons={!!data.siliconUser && (
|
||||
<Fragment>
|
||||
<>
|
||||
{!!data.malfStatus && (
|
||||
<Button
|
||||
icon={malfStatus.icon}
|
||||
@@ -182,7 +180,7 @@ const ApcContent = (props, context) => {
|
||||
icon="lightbulb-o"
|
||||
content="Overload"
|
||||
onClick={() => act('overload')} />
|
||||
</Fragment>
|
||||
</>
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
@@ -236,6 +234,6 @@ const ApcContent = (props, context) => {
|
||||
)}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { map, sortBy } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { pureComponentHooks } from 'common/react';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, Dimmer, Flex, Icon, Table, Tabs } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
@@ -13,8 +12,7 @@ export const ApcControl = (props, context) => {
|
||||
<Window
|
||||
title="APC Controller"
|
||||
width={550}
|
||||
height={500}
|
||||
resizable>
|
||||
height={500}>
|
||||
{data.authenticated === 1 && (
|
||||
<ApcLoggedIn />
|
||||
)}
|
||||
@@ -48,7 +46,7 @@ const ApcLoggedIn = (props, context) => {
|
||||
setTabIndex,
|
||||
] = useLocalState(context, 'tab-index', 1);
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
selected={tabIndex === 1}
|
||||
@@ -74,14 +72,14 @@ const ApcLoggedIn = (props, context) => {
|
||||
</Dimmer>
|
||||
)}
|
||||
{tabIndex === 1 && (
|
||||
<Fragment>
|
||||
<>
|
||||
<ControlPanel />
|
||||
<Box fillPositionedParent top="53px">
|
||||
<Window.Content scrollable>
|
||||
<ApcControlScene />
|
||||
</Window.Content>
|
||||
</Box>
|
||||
</Fragment>
|
||||
</>
|
||||
)}
|
||||
{tabIndex === 2 && (
|
||||
<Box fillPositionedParent top="20px">
|
||||
@@ -90,7 +88,7 @@ const ApcLoggedIn = (props, context) => {
|
||||
</Window.Content>
|
||||
</Box>
|
||||
)}
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -128,7 +126,7 @@ const ControlPanel = (props, context) => {
|
||||
<Flex.Item grow={1} />
|
||||
<Flex.Item>
|
||||
{emagged === 1 && (
|
||||
<Fragment>
|
||||
<>
|
||||
<Button
|
||||
color={logging === 1 ? 'bad' : 'good'}
|
||||
content={logging === 1 ? 'Stop Logging' : 'Restore Logging'}
|
||||
@@ -138,7 +136,7 @@ const ControlPanel = (props, context) => {
|
||||
content="Reset Console"
|
||||
onClick={() => act('restore-console')}
|
||||
/>
|
||||
</Fragment>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
color="bad"
|
||||
|
||||
@@ -9,8 +9,7 @@ export const AtmosAlertConsole = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={350}
|
||||
height={300}
|
||||
resizable>
|
||||
height={300}>
|
||||
<Window.Content scrollable>
|
||||
<Section title="Alarms">
|
||||
<ul>
|
||||
|
||||
@@ -10,8 +10,7 @@ export const AtmosControlConsole = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={500}
|
||||
height={315}
|
||||
resizable>
|
||||
height={315}>
|
||||
<Window.Content scrollable>
|
||||
<Section
|
||||
title={!!data.tank && sensors[0]?.long_name}>
|
||||
|
||||
@@ -18,8 +18,7 @@ export const AtmosControlPanel = (props, context) => {
|
||||
<Window
|
||||
title="SSAir Control Panel"
|
||||
width={900}
|
||||
height={500}
|
||||
resizable>
|
||||
height={500}>
|
||||
<Section m={1}>
|
||||
<Flex
|
||||
justify="space-between"
|
||||
|
||||
@@ -22,7 +22,7 @@ export const AtmosTempPump = (props, context) => {
|
||||
<NumberInput
|
||||
animated
|
||||
value={parseFloat(data.rate)}
|
||||
unit="K/s"
|
||||
unit="%"
|
||||
width="75px"
|
||||
minValue={0}
|
||||
maxValue={data.max_heat_transfer_rate}
|
||||
|
||||
@@ -44,7 +44,7 @@ export const Bepis = (props, context) => {
|
||||
<Grid.Column size={1.5}>
|
||||
<Section title="Stored Data and Statistics">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Deposited Credits">
|
||||
<LabeledList.Item label="Available Credits">
|
||||
{data.stored_cash}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Investment Variability">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { classes } from 'common/react';
|
||||
import { createSearch } from 'common/string';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, Dimmer, Flex, Icon, Input, NoticeBox, NumberInput, Section, Table, Tabs } from '../components';
|
||||
import { formatMoney } from '../format';
|
||||
@@ -17,8 +16,7 @@ export const Biogenerator = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={550}
|
||||
height={420}
|
||||
resizable>
|
||||
height={420}>
|
||||
{!!processing && (
|
||||
<Dimmer fontSize="32px">
|
||||
<Icon name="cog" spin={1} />
|
||||
@@ -77,7 +75,7 @@ export const BiogeneratorContent = (props, context) => {
|
||||
</Box>
|
||||
)}
|
||||
buttons={(
|
||||
<Fragment>
|
||||
<>
|
||||
Search
|
||||
<Input
|
||||
autoFocus
|
||||
@@ -93,7 +91,7 @@ export const BiogeneratorContent = (props, context) => {
|
||||
content="Activate"
|
||||
disabled={!can_process}
|
||||
onClick={() => act('activate')} />
|
||||
</Fragment>
|
||||
</>
|
||||
)}>
|
||||
<Flex>
|
||||
{searchText.length === 0 && (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, Flex, Modal, Section, Tabs } from '../components';
|
||||
import { AnimatedNumber, Box, Button, Flex, Modal, Section, Stack, Tabs } from '../components';
|
||||
import { formatMoney } from '../format';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
@@ -15,10 +15,9 @@ export const BlackMarketUplink = (props, context) => {
|
||||
} = data;
|
||||
return (
|
||||
<Window
|
||||
width={800}
|
||||
width={600}
|
||||
height={480}
|
||||
theme="hackerman"
|
||||
resizable>
|
||||
theme="hackerman">
|
||||
<ShipmentSelector />
|
||||
<Window.Content scrollable>
|
||||
<Section
|
||||
@@ -65,27 +64,27 @@ export const BlackMarketUplink = (props, context) => {
|
||||
className="candystripe"
|
||||
p={1}
|
||||
pb={2}>
|
||||
<Flex spacing={1} align="baseline">
|
||||
<Flex.Item bold grow={1}>
|
||||
<Stack align="baseline">
|
||||
<Stack.Item grow bold>
|
||||
{item.name}
|
||||
</Flex.Item>
|
||||
<Flex.Item color="label">
|
||||
</Stack.Item>
|
||||
<Stack.Item color="label">
|
||||
{item.amount
|
||||
? item.amount + " in stock"
|
||||
: "Out of stock"}
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
{formatMoney(item.cost) + ' cr'}
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
content="Buy"
|
||||
disabled={!item.amount || item.cost > money}
|
||||
onClick={() => act('select', {
|
||||
item: item.id,
|
||||
})} />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
{item.desc}
|
||||
</Box>
|
||||
))}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, LabeledList, NoticeBox, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
@@ -22,7 +21,7 @@ export const BluespaceArtillery = (props, context) => {
|
||||
</NoticeBox>
|
||||
)}
|
||||
{connected ? (
|
||||
<Fragment>
|
||||
<>
|
||||
<Section
|
||||
title="Target"
|
||||
buttons={(
|
||||
@@ -51,7 +50,7 @@ export const BluespaceArtillery = (props, context) => {
|
||||
onClick={() => act('fire')} />
|
||||
</Box>
|
||||
) : (
|
||||
<Fragment>
|
||||
<>
|
||||
<Box
|
||||
color="bad"
|
||||
fontSize="18px">
|
||||
@@ -61,10 +60,10 @@ export const BluespaceArtillery = (props, context) => {
|
||||
Awaiting authorization via keycard reader from at minimum
|
||||
two station heads.
|
||||
</Box>
|
||||
</Fragment>
|
||||
</>
|
||||
)}
|
||||
</Section>
|
||||
</Fragment>
|
||||
</>
|
||||
) : (
|
||||
<Section>
|
||||
<LabeledList>
|
||||
|
||||
@@ -18,8 +18,7 @@ export const BluespaceLocator = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={300}
|
||||
height={300}
|
||||
resizable>
|
||||
height={300}>
|
||||
<Window.Content scrollable>
|
||||
<Tabs>
|
||||
<Tabs.Tab
|
||||
|
||||
@@ -36,7 +36,7 @@ export const BluespaceSender = (props, context) => {
|
||||
mr={0.5}
|
||||
color="transparent"
|
||||
icon="info"
|
||||
tooltipPosition="bottom-left"
|
||||
tooltipPosition="bottom-start"
|
||||
tooltip={multiline`
|
||||
Any gas you pipe into here will be added to the Bluespace
|
||||
Network! That means any connected Bluespace Vendor (multitool)
|
||||
@@ -59,13 +59,13 @@ export const BluespaceSender = (props, context) => {
|
||||
icon={data.on ? 'power-off' : 'times'}
|
||||
content={data.on ? 'On' : 'Off'}
|
||||
selected={data.on}
|
||||
tooltipPosition="bottom-left"
|
||||
tooltipPosition="bottom-start"
|
||||
tooltip="Will only take in gases while on."
|
||||
onClick={() => act('power')} />
|
||||
<Button
|
||||
ml={0.5}
|
||||
content="Retrieve gases"
|
||||
tooltipPosition="bottom-left"
|
||||
tooltipPosition="bottom-start"
|
||||
tooltip="Will transfer any gases inside to the pipe."
|
||||
onClick={() => act('retrieve')} />
|
||||
</>
|
||||
|
||||
@@ -87,7 +87,7 @@ export const BluespaceVendor = (props, context) => {
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="info"
|
||||
tooltipPosition="bottom-left"
|
||||
tooltipPosition="bottom-start"
|
||||
tooltip={multiline`
|
||||
Quick guide for machine use: prepare a tank to create a
|
||||
new one in the machine, pick how much you want it filled,
|
||||
|
||||
@@ -16,8 +16,7 @@ export const BorgPanel = (props, context) => {
|
||||
<Window
|
||||
title="Borg Panel"
|
||||
width={700}
|
||||
height={700}
|
||||
resizable>
|
||||
height={700}>
|
||||
<Window.Content scrollable>
|
||||
<Section
|
||||
title={borg.name}
|
||||
@@ -83,7 +82,7 @@ export const BorgPanel = (props, context) => {
|
||||
})} />
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Module">
|
||||
<LabeledList.Item label="Model">
|
||||
{modules.map(module => (
|
||||
<Button
|
||||
key={module.type}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
@@ -8,13 +7,12 @@ export const BrigTimer = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={300}
|
||||
height={138}
|
||||
resizable>
|
||||
height={138}>
|
||||
<Window.Content scrollable>
|
||||
<Section
|
||||
title="Cell Timer"
|
||||
buttons={(
|
||||
<Fragment>
|
||||
<>
|
||||
<Button
|
||||
icon="clock-o"
|
||||
content={data.timing ? 'Stop' : 'Start'}
|
||||
@@ -25,7 +23,7 @@ export const BrigTimer = (props, context) => {
|
||||
content={data.flash_charging ? 'Recharging' : 'Flash'}
|
||||
disabled={data.flash_charging}
|
||||
onClick={() => act('flash')} />
|
||||
</Fragment>
|
||||
</>
|
||||
)}>
|
||||
<Button
|
||||
icon="fast-backward"
|
||||
|
||||
@@ -51,8 +51,7 @@ export const CameraConsole = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={870}
|
||||
height={708}
|
||||
resizable>
|
||||
height={708}>
|
||||
<div className="CameraConsole__left">
|
||||
<Window.Content scrollable>
|
||||
<CameraConsoleContent />
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { toFixed } from 'common/math';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, Flex, Icon, Knob, LabeledControls, LabeledList, RoundGauge, Section, Tooltip } from '../components';
|
||||
import { formatSiUnit } from '../format';
|
||||
@@ -40,7 +39,7 @@ export const Canister = (props, context) => {
|
||||
<Section
|
||||
title="Canister"
|
||||
buttons={(
|
||||
<Fragment>
|
||||
<>
|
||||
{!!isPrototype && (
|
||||
<Button
|
||||
mr={1}
|
||||
@@ -55,7 +54,7 @@ export const Canister = (props, context) => {
|
||||
icon="pencil-alt"
|
||||
content="Relabel"
|
||||
onClick={() => act('relabel')} />
|
||||
</Fragment>
|
||||
</>
|
||||
)}>
|
||||
<LabeledControls>
|
||||
<LabeledControls.Item
|
||||
@@ -127,17 +126,19 @@ export const Canister = (props, context) => {
|
||||
<LabeledControls.Item
|
||||
mr={1}
|
||||
label="Port">
|
||||
<Box position="relative">
|
||||
<Icon
|
||||
size={1.25}
|
||||
name={portConnected ? 'plug' : 'times'}
|
||||
color={portConnected ? 'good' : 'bad'} />
|
||||
<Tooltip
|
||||
content={portConnected
|
||||
? 'Connected'
|
||||
: 'Disconnected'}
|
||||
position="top" />
|
||||
</Box>
|
||||
<Tooltip
|
||||
content={portConnected
|
||||
? 'Connected'
|
||||
: 'Disconnected'}
|
||||
position="top"
|
||||
>
|
||||
<Box position="relative">
|
||||
<Icon
|
||||
size={1.25}
|
||||
name={portConnected ? 'plug' : 'times'}
|
||||
color={portConnected ? 'good' : 'bad'} />
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</LabeledControls.Item>
|
||||
</LabeledControls>
|
||||
</Section>
|
||||
|
||||
@@ -90,8 +90,7 @@ export const Canvas = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={Math.min(700, width * dotsize + 72)}
|
||||
height={Math.min(700, height * dotsize + 72)}
|
||||
resizable>
|
||||
height={Math.min(700, height * dotsize + 72)}>
|
||||
<Window.Content>
|
||||
<Box textAlign="center">
|
||||
<PaintCanvas
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
@@ -10,8 +9,7 @@ export const CargoExpress = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={600}
|
||||
height={700}
|
||||
resizable>
|
||||
height={700}>
|
||||
<Window.Content scrollable>
|
||||
<InterfaceLockNoticeBox
|
||||
accessText="a QM-level ID card" />
|
||||
@@ -26,7 +24,7 @@ export const CargoExpress = (props, context) => {
|
||||
const CargoExpressContent = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<Section
|
||||
title="Cargo Express"
|
||||
buttons={(
|
||||
@@ -59,6 +57,6 @@ const CargoExpressContent = (props, context) => {
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<CargoCatalog express />
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { AnimatedNumber, Box, Button, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
@@ -14,8 +13,7 @@ export const CargoHoldTerminal = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={600}
|
||||
height={230}
|
||||
resizable>
|
||||
height={230}>
|
||||
<Window.Content scrollable>
|
||||
<Section>
|
||||
<LabeledList>
|
||||
@@ -29,7 +27,7 @@ export const CargoHoldTerminal = (props, context) => {
|
||||
<Section
|
||||
title="Cargo Pad"
|
||||
buttons={(
|
||||
<Fragment>
|
||||
<>
|
||||
<Button
|
||||
icon={"sync"}
|
||||
content={"Recalculate Value"}
|
||||
@@ -41,7 +39,7 @@ export const CargoHoldTerminal = (props, context) => {
|
||||
selected={sending}
|
||||
disabled={!pad}
|
||||
onClick={() => act(sending ? 'stop' : 'send')} />
|
||||
</Fragment>
|
||||
</>
|
||||
)}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend } from '../backend';
|
||||
import { Box, Button, LabeledList, Section } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
@@ -9,8 +8,7 @@ export const CellularEmporium = (props, context) => {
|
||||
return (
|
||||
<Window
|
||||
width={900}
|
||||
height={480}
|
||||
resizable>
|
||||
height={480}>
|
||||
<Window.Content scrollable>
|
||||
<Section>
|
||||
<LabeledList>
|
||||
@@ -35,7 +33,7 @@ export const CellularEmporium = (props, context) => {
|
||||
className="candystripe"
|
||||
label={ability.name}
|
||||
buttons={(
|
||||
<Fragment>
|
||||
<>
|
||||
{ability.dna_cost}
|
||||
{' '}
|
||||
<Button
|
||||
@@ -44,7 +42,7 @@ export const CellularEmporium = (props, context) => {
|
||||
onClick={() => act('evolve', {
|
||||
name: ability.name,
|
||||
})} />
|
||||
</Fragment>
|
||||
</>
|
||||
)}>
|
||||
{ability.desc}
|
||||
<Box color="good">
|
||||
|
||||