mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-20 12:35:33 +01:00
[MIRROR] Adds Prettierx - or how I broke TGUI for the nth time [MDB IGNORE] (#14475)
* Adds Prettierx - or how I broke TGUI for the nth time * fuck Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
This commit is contained in:
+4
-1
@@ -1,10 +1,13 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
|
||||
Vendored
+2
-1
@@ -6,6 +6,7 @@
|
||||
"dbaeumer.vscode-eslint",
|
||||
"stylemistake.auto-comment-blocks",
|
||||
"Donkie.vscode-tgstation-test-adapter",
|
||||
"anturk.dmi-editor"
|
||||
"anturk.dmi-editor",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+21
-6
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"eslint.nodePath": "./tgui/.yarn/sdks",
|
||||
"eslint.workingDirectories": [
|
||||
"./tgui"
|
||||
],
|
||||
"eslint.workingDirectories": ["./tgui"],
|
||||
"prettier.prettierPath": "./tgui/.yarn/sdks/prettier/index.js",
|
||||
"typescript.tsdk": "./tgui/.yarn/sdks/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"search.exclude": {
|
||||
@@ -17,12 +16,28 @@
|
||||
"gitlens.advanced.blame.customArguments": ["-w"],
|
||||
"tgstationTestExplorer.project.resultsType": "json",
|
||||
"[javascript]": {
|
||||
"editor.rulers": [80]
|
||||
"editor.rulers": [80],
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.rulers": [80],
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.rulers": [80]
|
||||
"editor.rulers": [80],
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.rulers": [80],
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.rulers": [80]
|
||||
"editor.rulers": [80],
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
}
|
||||
|
||||
+27
-27
@@ -1,4 +1,5 @@
|
||||
root: true
|
||||
extends: prettier
|
||||
parser: '@typescript-eslint/parser'
|
||||
parserOptions:
|
||||
ecmaVersion: 2020
|
||||
@@ -17,7 +18,6 @@ settings:
|
||||
react:
|
||||
version: '16.10'
|
||||
rules:
|
||||
|
||||
## Possible Errors
|
||||
## ----------------------------------------
|
||||
## Enforce “for” loop update clause moving the counter in the right
|
||||
@@ -349,15 +349,15 @@ rules:
|
||||
## Enforce the location of arrow function bodies
|
||||
# implicit-arrow-linebreak: error
|
||||
## Enforce consistent indentation
|
||||
indent: [error, 2, { SwitchCase: 1 }]
|
||||
# indent: [error, 2, { SwitchCase: 1 }]
|
||||
## Enforce the consistent use of either double or single quotes in JSX
|
||||
## attributes
|
||||
jsx-quotes: [error, prefer-double]
|
||||
# jsx-quotes: [error, prefer-double]
|
||||
## Enforce consistent spacing between keys and values in object literal
|
||||
## properties
|
||||
key-spacing: [error, { beforeColon: false, afterColon: true }]
|
||||
# key-spacing: [error, { beforeColon: false, afterColon: true }]
|
||||
## Enforce consistent spacing before and after keywords
|
||||
keyword-spacing: [error, { before: true, after: true }]
|
||||
# keyword-spacing: [error, { before: true, after: true }]
|
||||
## Enforce position of line comments
|
||||
# line-comment-position: error
|
||||
## Enforce consistent linebreak style
|
||||
@@ -369,15 +369,15 @@ rules:
|
||||
## Enforce a maximum depth that blocks can be nested
|
||||
# max-depth: error
|
||||
## Enforce a maximum line length
|
||||
max-len: [error, {
|
||||
code: 80,
|
||||
## Ignore imports
|
||||
ignorePattern: '^(import\s.+\sfrom\s|.*require\()',
|
||||
ignoreUrls: true,
|
||||
ignoreRegExpLiterals: true,
|
||||
ignoreStrings: true,
|
||||
ignoreTemplateLiterals: true,
|
||||
}]
|
||||
# max-len: [error, {
|
||||
# code: 80,
|
||||
# ## Ignore imports
|
||||
# ignorePattern: '^(import\s.+\sfrom\s|.*require\()',
|
||||
# ignoreUrls: true,
|
||||
# ignoreRegExpLiterals: true,
|
||||
# ignoreStrings: true,
|
||||
# ignoreTemplateLiterals: true,
|
||||
# }]
|
||||
## Enforce a maximum number of lines per file
|
||||
# max-lines: error
|
||||
## Enforce a maximum number of line of code in a function
|
||||
@@ -414,7 +414,7 @@ rules:
|
||||
## Disallow mixed binary operators
|
||||
# no-mixed-operators: error
|
||||
## Disallow mixed spaces and tabs for indentation
|
||||
no-mixed-spaces-and-tabs: error
|
||||
# no-mixed-spaces-and-tabs: error
|
||||
## Disallow use of chained assignment expressions
|
||||
# no-multi-assign: error
|
||||
## Disallow multiple empty lines
|
||||
@@ -440,7 +440,7 @@ rules:
|
||||
## Disallow ternary operators when simpler alternatives exist
|
||||
# no-unneeded-ternary: error
|
||||
## Disallow whitespace before properties
|
||||
no-whitespace-before-property: error
|
||||
# no-whitespace-before-property: error
|
||||
## Enforce the location of single-line statements
|
||||
# nonblock-statement-body-position: error
|
||||
## Enforce consistent line breaks inside braces
|
||||
@@ -457,7 +457,7 @@ rules:
|
||||
## Require or disallow assignment operator shorthand where possible
|
||||
# operator-assignment: error
|
||||
## Enforce consistent linebreak style for operators
|
||||
operator-linebreak: [error, before]
|
||||
# operator-linebreak: [error, before]
|
||||
## Require or disallow padding within blocks
|
||||
# padded-blocks: error
|
||||
## Require or disallow padding lines between statements
|
||||
@@ -482,11 +482,11 @@ rules:
|
||||
## Enforce consistent spacing before blocks
|
||||
space-before-blocks: [error, always]
|
||||
## Enforce consistent spacing before function definition opening parenthesis
|
||||
space-before-function-paren: [error, {
|
||||
anonymous: always,
|
||||
named: never,
|
||||
asyncArrow: always,
|
||||
}]
|
||||
# space-before-function-paren: [error, {
|
||||
# anonymous: always,
|
||||
# named: never,
|
||||
# asyncArrow: always,
|
||||
# }]
|
||||
## Enforce consistent spacing inside parentheses
|
||||
space-in-parens: [error, never]
|
||||
## Require spacing around infix operators
|
||||
@@ -695,7 +695,7 @@ rules:
|
||||
react/jsx-closing-tag-location: error
|
||||
## Enforce or disallow newlines inside of curly braces in JSX attributes and
|
||||
## expressions (fixable)
|
||||
react/jsx-curly-newline: error
|
||||
# react/jsx-curly-newline: error
|
||||
## Enforce or disallow spaces inside of curly braces in JSX attributes and
|
||||
## expressions (fixable)
|
||||
react/jsx-curly-spacing: error
|
||||
@@ -708,11 +708,11 @@ rules:
|
||||
## Enforce event handler naming conventions in JSX
|
||||
react/jsx-handler-names: error
|
||||
## Validate JSX indentation (fixable)
|
||||
react/jsx-indent: [error, 2, {
|
||||
checkAttributes: true,
|
||||
}]
|
||||
# react/jsx-indent: [error, 2, {
|
||||
# checkAttributes: true,
|
||||
# }]
|
||||
## Validate props indentation in JSX (fixable)
|
||||
react/jsx-indent-props: [error, 2]
|
||||
# react/jsx-indent-props: [error, 2]
|
||||
## Validate JSX has key prop when in array or iterator
|
||||
react/jsx-key: error
|
||||
## Validate JSX maximum depth
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## NPM
|
||||
/**/node_modules
|
||||
|
||||
## Yarn
|
||||
/.yarn
|
||||
/yarn.lock
|
||||
/.pnp.*
|
||||
|
||||
/docs
|
||||
/public
|
||||
/packages/tgui-polyfill
|
||||
/packages/tgfont/static
|
||||
**/*.json
|
||||
**/*.yml
|
||||
**/*.md
|
||||
@@ -1,8 +1,10 @@
|
||||
arrowParens: always
|
||||
bracketSpacing: true
|
||||
breakLongMethodChains: true
|
||||
endOfLine: lf
|
||||
importFormatting: oneline
|
||||
jsxBracketSameLine: true
|
||||
jsxSingleQuote: false
|
||||
offsetTernaryExpressions: true
|
||||
printWidth: 80
|
||||
proseWrap: preserve
|
||||
quoteProps: preserve
|
||||
@@ -10,3 +12,4 @@ semi: true
|
||||
singleQuote: true
|
||||
tabWidth: 2
|
||||
trailingComma: es5
|
||||
useTabs: false
|
||||
|
||||
Vendored
+3
-3
@@ -11,10 +11,10 @@ const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require eslint/lib/api.js
|
||||
// Setup the environment to be able to require eslint
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real eslint/lib/api.js your application uses
|
||||
module.exports = absRequire(`eslint/lib/api.js`);
|
||||
// Defer to the real eslint your application uses
|
||||
module.exports = absRequire(`eslint`);
|
||||
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
#!/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);
|
||||
|
||||
if (existsSync(absPnpApiPath)) {
|
||||
if (!process.versions.pnp) {
|
||||
// Setup the environment to be able to require prettier/index.js
|
||||
require(absPnpApiPath).setup();
|
||||
}
|
||||
}
|
||||
|
||||
// Defer to the real prettier/index.js your application uses
|
||||
module.exports = absRequire(`prettier/index.js`);
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "prettier",
|
||||
"version": "0.19.0-sdk",
|
||||
"main": "./index.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
+78
-12
@@ -18,6 +18,7 @@ const moduleWrapper = tsserver => {
|
||||
const pnpApi = require(`pnpapi`);
|
||||
|
||||
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||
const isPortal = str => str.startsWith("portal:/");
|
||||
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||
|
||||
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||
@@ -30,7 +31,7 @@ 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\//) || isVirtual(str))) {
|
||||
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/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
|
||||
@@ -44,7 +45,7 @@ const moduleWrapper = tsserver => {
|
||||
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||
if (resolved) {
|
||||
const locator = pnpApi.findPackageLocator(resolved);
|
||||
if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) {
|
||||
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
||||
str = resolved;
|
||||
}
|
||||
}
|
||||
@@ -60,10 +61,34 @@ const moduleWrapper = tsserver => {
|
||||
//
|
||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||
//
|
||||
case `vscode`: {
|
||||
// 2021-10-08: VSCode changed the format in 1.61.
|
||||
// Before | ^zip:/c:/foo/bar.zip/package.json
|
||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||
//
|
||||
// 2022-04-06: VSCode changed the format in 1.66.
|
||||
// Before | ^/zip//c:/foo/bar.zip/package.json
|
||||
// After | ^/zip/c:/foo/bar.zip/package.json
|
||||
//
|
||||
// 2022-05-06: VSCode changed the format in 1.68
|
||||
// Before | ^/zip/c:/foo/bar.zip/package.json
|
||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||
//
|
||||
case `vscode <1.61`: {
|
||||
str = `^zip:${str}`;
|
||||
} break;
|
||||
|
||||
case `vscode <1.66`: {
|
||||
str = `^/zip/${str}`;
|
||||
} break;
|
||||
|
||||
case `vscode <1.68`: {
|
||||
str = `^/zip${str}`;
|
||||
} break;
|
||||
|
||||
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)
|
||||
@@ -77,7 +102,7 @@ const moduleWrapper = tsserver => {
|
||||
// everything else is up to neovim
|
||||
case `neovim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = `zipfile:${str}`;
|
||||
str = `zipfile://${str}`;
|
||||
} break;
|
||||
|
||||
default: {
|
||||
@@ -91,9 +116,28 @@ const moduleWrapper = tsserver => {
|
||||
}
|
||||
|
||||
function fromEditorPath(str) {
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^\^?zip:\//, ``)
|
||||
: str.replace(/^\^?zip:/, ``);
|
||||
switch (hostInfo) {
|
||||
case `coc-nvim`: {
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
||||
// So in order to convert it back, we use .* to match all the thing
|
||||
// before `zipfile:`
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^.*zipfile:\//, ``)
|
||||
: str.replace(/^.*zipfile:/, ``);
|
||||
} break;
|
||||
|
||||
case `neovim`: {
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
||||
return str.replace(/^zipfile:\/\//, ``);
|
||||
} break;
|
||||
|
||||
case `vscode`:
|
||||
default: {
|
||||
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Force enable 'allowLocalPluginLoads'
|
||||
@@ -119,8 +163,9 @@ const moduleWrapper = tsserver => {
|
||||
let hostInfo = `unknown`;
|
||||
|
||||
Object.assign(Session.prototype, {
|
||||
onMessage(/** @type {string} */ message) {
|
||||
const parsedMessage = JSON.parse(message)
|
||||
onMessage(/** @type {string | object} */ message) {
|
||||
const isStringMessage = typeof message === 'string';
|
||||
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
||||
|
||||
if (
|
||||
parsedMessage != null &&
|
||||
@@ -129,11 +174,32 @@ const moduleWrapper = tsserver => {
|
||||
typeof parsedMessage.arguments.hostInfo === `string`
|
||||
) {
|
||||
hostInfo = parsedMessage.arguments.hostInfo;
|
||||
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
||||
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
|
||||
// The RegExp from https://semver.org/ but without the caret at the start
|
||||
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
||||
) ?? []).map(Number)
|
||||
|
||||
if (major === 1) {
|
||||
if (minor < 61) {
|
||||
hostInfo += ` <1.61`;
|
||||
} else if (minor < 66) {
|
||||
hostInfo += ` <1.66`;
|
||||
} else if (minor < 68) {
|
||||
hostInfo += ` <1.68`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => {
|
||||
return typeof value === `string` ? fromEditorPath(value) : value;
|
||||
}));
|
||||
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
||||
return typeof value === 'string' ? fromEditorPath(value) : value;
|
||||
});
|
||||
|
||||
return originalOnMessage.call(
|
||||
this,
|
||||
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
||||
);
|
||||
},
|
||||
|
||||
send(/** @type {any} */ msg) {
|
||||
|
||||
+78
-12
@@ -18,6 +18,7 @@ const moduleWrapper = tsserver => {
|
||||
const pnpApi = require(`pnpapi`);
|
||||
|
||||
const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//);
|
||||
const isPortal = str => str.startsWith("portal:/");
|
||||
const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`);
|
||||
|
||||
const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => {
|
||||
@@ -30,7 +31,7 @@ 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\//) || isVirtual(str))) {
|
||||
if (isAbsolute(str) && !str.match(/^\^?(zip:|\/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
|
||||
@@ -44,7 +45,7 @@ const moduleWrapper = tsserver => {
|
||||
const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str;
|
||||
if (resolved) {
|
||||
const locator = pnpApi.findPackageLocator(resolved);
|
||||
if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) {
|
||||
if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) {
|
||||
str = resolved;
|
||||
}
|
||||
}
|
||||
@@ -60,10 +61,34 @@ const moduleWrapper = tsserver => {
|
||||
//
|
||||
// Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910
|
||||
//
|
||||
case `vscode`: {
|
||||
// 2021-10-08: VSCode changed the format in 1.61.
|
||||
// Before | ^zip:/c:/foo/bar.zip/package.json
|
||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||
//
|
||||
// 2022-04-06: VSCode changed the format in 1.66.
|
||||
// Before | ^/zip//c:/foo/bar.zip/package.json
|
||||
// After | ^/zip/c:/foo/bar.zip/package.json
|
||||
//
|
||||
// 2022-05-06: VSCode changed the format in 1.68
|
||||
// Before | ^/zip/c:/foo/bar.zip/package.json
|
||||
// After | ^/zip//c:/foo/bar.zip/package.json
|
||||
//
|
||||
case `vscode <1.61`: {
|
||||
str = `^zip:${str}`;
|
||||
} break;
|
||||
|
||||
case `vscode <1.66`: {
|
||||
str = `^/zip/${str}`;
|
||||
} break;
|
||||
|
||||
case `vscode <1.68`: {
|
||||
str = `^/zip${str}`;
|
||||
} break;
|
||||
|
||||
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)
|
||||
@@ -77,7 +102,7 @@ const moduleWrapper = tsserver => {
|
||||
// everything else is up to neovim
|
||||
case `neovim`: {
|
||||
str = normalize(resolved).replace(/\.zip\//, `.zip::`);
|
||||
str = `zipfile:${str}`;
|
||||
str = `zipfile://${str}`;
|
||||
} break;
|
||||
|
||||
default: {
|
||||
@@ -91,9 +116,28 @@ const moduleWrapper = tsserver => {
|
||||
}
|
||||
|
||||
function fromEditorPath(str) {
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^\^?zip:\//, ``)
|
||||
: str.replace(/^\^?zip:/, ``);
|
||||
switch (hostInfo) {
|
||||
case `coc-nvim`: {
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for coc-nvim is in format of /<pwd>/zipfile:/<pwd>/.yarn/...
|
||||
// So in order to convert it back, we use .* to match all the thing
|
||||
// before `zipfile:`
|
||||
return process.platform === `win32`
|
||||
? str.replace(/^.*zipfile:\//, ``)
|
||||
: str.replace(/^.*zipfile:/, ``);
|
||||
} break;
|
||||
|
||||
case `neovim`: {
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for neovim is in format of zipfile:///<pwd>/.yarn/...
|
||||
return str.replace(/^zipfile:\/\//, ``);
|
||||
} break;
|
||||
|
||||
case `vscode`:
|
||||
default: {
|
||||
return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`)
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Force enable 'allowLocalPluginLoads'
|
||||
@@ -119,8 +163,9 @@ const moduleWrapper = tsserver => {
|
||||
let hostInfo = `unknown`;
|
||||
|
||||
Object.assign(Session.prototype, {
|
||||
onMessage(/** @type {string} */ message) {
|
||||
const parsedMessage = JSON.parse(message)
|
||||
onMessage(/** @type {string | object} */ message) {
|
||||
const isStringMessage = typeof message === 'string';
|
||||
const parsedMessage = isStringMessage ? JSON.parse(message) : message;
|
||||
|
||||
if (
|
||||
parsedMessage != null &&
|
||||
@@ -129,11 +174,32 @@ const moduleWrapper = tsserver => {
|
||||
typeof parsedMessage.arguments.hostInfo === `string`
|
||||
) {
|
||||
hostInfo = parsedMessage.arguments.hostInfo;
|
||||
if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) {
|
||||
const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match(
|
||||
// The RegExp from https://semver.org/ but without the caret at the start
|
||||
/(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
|
||||
) ?? []).map(Number)
|
||||
|
||||
if (major === 1) {
|
||||
if (minor < 61) {
|
||||
hostInfo += ` <1.61`;
|
||||
} else if (minor < 66) {
|
||||
hostInfo += ` <1.66`;
|
||||
} else if (minor < 68) {
|
||||
hostInfo += ` <1.68`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => {
|
||||
return typeof value === `string` ? fromEditorPath(value) : value;
|
||||
}));
|
||||
const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => {
|
||||
return typeof value === 'string' ? fromEditorPath(value) : value;
|
||||
});
|
||||
|
||||
return originalOnMessage.call(
|
||||
this,
|
||||
isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON)
|
||||
);
|
||||
},
|
||||
|
||||
send(/** @type {any} */ msg) {
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const createBabelConfig = options => {
|
||||
const createBabelConfig = (options) => {
|
||||
const { presets = [], plugins = [], removeConsole } = options;
|
||||
// prettier-ignore
|
||||
return {
|
||||
presets: [
|
||||
[require.resolve('@babel/preset-typescript'), {
|
||||
@@ -34,7 +35,7 @@ const createBabelConfig = options => {
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = api => {
|
||||
module.exports = (api) => {
|
||||
api.cache(true);
|
||||
const mode = process.env.NODE_ENV;
|
||||
return createBabelConfig({ mode });
|
||||
|
||||
+1
-3
@@ -4,9 +4,7 @@ module.exports = {
|
||||
'<rootDir>/packages/**/__tests__/*.{js,ts,tsx}',
|
||||
'<rootDir>/packages/**/*.{spec,test}.{js,ts,tsx}',
|
||||
],
|
||||
testPathIgnorePatterns: [
|
||||
'<rootDir>/packages/tgui-bench',
|
||||
],
|
||||
testPathIgnorePatterns: ['<rootDir>/packages/tgui-bench'],
|
||||
testEnvironment: 'jsdom',
|
||||
testRunner: require.resolve('jest-circus/runner'),
|
||||
transform: {
|
||||
|
||||
+6
-3
@@ -7,16 +7,17 @@
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"tgui:build": "webpack",
|
||||
"tgui:analyze": "webpack --analyze",
|
||||
"tgui:bench": "webpack --env TGUI_BENCH=1 && node packages/tgui-bench/index.js",
|
||||
"tgui:build": "webpack",
|
||||
"tgui:dev": "node --experimental-modules packages/tgui-dev-server/index.js",
|
||||
"tgui:lint": "eslint packages --ext .js,.cjs,.ts,.tsx",
|
||||
"tgui:prettier": "prettierx --check .",
|
||||
"tgui:sonar": "eslint packages --ext .js,.cjs,.ts,.tsx -c .eslintrc-sonar.yml",
|
||||
"tgui:tsc": "tsc",
|
||||
"tgui:test": "jest --watch",
|
||||
"tgui:test-simple": "CI=true jest --color",
|
||||
"tgui:test-ci": "CI=true jest --color --collect-coverage",
|
||||
"tgui:bench": "webpack --env TGUI_BENCH=1 && node packages/tgui-bench/index.js"
|
||||
"tgui:tsc": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
@@ -38,6 +39,7 @@
|
||||
"common": "workspace:*",
|
||||
"css-loader": "^5.2.7",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-radar": "^0.2.1",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"eslint-plugin-unused-imports": "^1.1.4",
|
||||
@@ -47,6 +49,7 @@
|
||||
"jest-circus": "^27.0.6",
|
||||
"jsdom": "^16.7.0",
|
||||
"mini-css-extract-plugin": "^1.6.2",
|
||||
"prettier": "npm:prettierx@0.19.0",
|
||||
"sass": "^1.37.5",
|
||||
"sass-loader": "^11.1.1",
|
||||
"style-loader": "^2.0.0",
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { range, zip } from "./collections";
|
||||
import { range, zip } from './collections';
|
||||
|
||||
// Type assertions, these will lint if the types are wrong.
|
||||
const _zip1: [string, number] = zip(["a"], [1])[0];
|
||||
const _zip1: [string, number] = zip(['a'], [1])[0];
|
||||
|
||||
describe("range", () => {
|
||||
test("range(0, 5)", () => {
|
||||
describe('range', () => {
|
||||
test('range(0, 5)', () => {
|
||||
expect(range(0, 5)).toEqual([0, 1, 2, 3, 4]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("zip", () => {
|
||||
describe('zip', () => {
|
||||
test("zip(['a', 'b', 'c'], [1, 2, 3, 4])", () => {
|
||||
expect(zip(["a", "b", "c"], [1, 2, 3, 4])).toEqual([
|
||||
["a", 1],
|
||||
["b", 2],
|
||||
["c", 3],
|
||||
expect(zip(['a', 'b', 'c'], [1, 2, 3, 4])).toEqual([
|
||||
['a', 1],
|
||||
['b', 2],
|
||||
['c', 3],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,43 +11,34 @@
|
||||
*
|
||||
* If collection is 'null' or 'undefined', it will be returned "as is"
|
||||
* without emitting any errors (which can be useful in some cases).
|
||||
*
|
||||
* @returns {any[]}
|
||||
*/
|
||||
export const filter = <T>(iterateeFn: (
|
||||
input: T,
|
||||
index: number,
|
||||
collection: T[],
|
||||
) => boolean) =>
|
||||
(collection: T[]): T[] => {
|
||||
if (collection === null || collection === undefined) {
|
||||
return collection;
|
||||
}
|
||||
if (Array.isArray(collection)) {
|
||||
const result: T[] = [];
|
||||
for (let i = 0; i < collection.length; i++) {
|
||||
const item = collection[i];
|
||||
if (iterateeFn(item, i, collection)) {
|
||||
result.push(item);
|
||||
}
|
||||
export const filter =
|
||||
<T>(iterateeFn: (input: T, index: number, collection: T[]) => boolean) =>
|
||||
(collection: T[]): T[] => {
|
||||
if (collection === null || collection === undefined) {
|
||||
return collection;
|
||||
}
|
||||
if (Array.isArray(collection)) {
|
||||
const result: T[] = [];
|
||||
for (let i = 0; i < collection.length; i++) {
|
||||
const item = collection[i];
|
||||
if (iterateeFn(item, i, collection)) {
|
||||
result.push(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new Error(`filter() can't iterate on type ${typeof collection}`);
|
||||
};
|
||||
return result;
|
||||
}
|
||||
throw new Error(`filter() can't iterate on type ${typeof collection}`);
|
||||
};
|
||||
|
||||
type MapFunction = {
|
||||
<T, U>(iterateeFn: (
|
||||
value: T,
|
||||
index: number,
|
||||
collection: T[],
|
||||
) => U): (collection: T[]) => U[];
|
||||
<T, U>(iterateeFn: (value: T, index: number, collection: T[]) => U): (
|
||||
collection: T[]
|
||||
) => U[];
|
||||
|
||||
<T, U, K extends string | number>(iterateeFn: (
|
||||
value: T,
|
||||
index: K,
|
||||
collection: Record<K, T>,
|
||||
) => U): (collection: Record<K, T>) => U[];
|
||||
<T, U, K extends string | number>(
|
||||
iterateeFn: (value: T, index: K, collection: Record<K, T>) => U
|
||||
): (collection: Record<K, T>) => U[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -58,7 +49,8 @@ type MapFunction = {
|
||||
* If collection is 'null' or 'undefined', it will be returned "as is"
|
||||
* without emitting any errors (which can be useful in some cases).
|
||||
*/
|
||||
export const map: MapFunction = <T, U>(iterateeFn) =>
|
||||
export const map: MapFunction =
|
||||
<T, U>(iterateeFn) =>
|
||||
(collection: T[]): U[] => {
|
||||
if (collection === null || collection === undefined) {
|
||||
return collection;
|
||||
@@ -81,9 +73,10 @@ export const map: MapFunction = <T, U>(iterateeFn) =>
|
||||
* Given a collection, will run each element through an iteratee function.
|
||||
* Will then filter out undefined values.
|
||||
*/
|
||||
export const filterMap = <T, U>(collection: T[], iterateeFn: (
|
||||
value: T
|
||||
) => U | undefined): U[] => {
|
||||
export const filterMap = <T, U>(
|
||||
collection: T[],
|
||||
iterateeFn: (value: T) => U | undefined
|
||||
): U[] => {
|
||||
const finalCollection: U[] = [];
|
||||
|
||||
for (const value of collection) {
|
||||
@@ -119,22 +112,22 @@ const COMPARATOR = (objA, objB) => {
|
||||
*
|
||||
* Iteratees are called with one argument (value).
|
||||
*/
|
||||
export const sortBy = <T>(
|
||||
...iterateeFns: ((input: T) => unknown)[]
|
||||
) => (array: T[]): T[] => {
|
||||
export const sortBy =
|
||||
<T>(...iterateeFns: ((input: T) => unknown)[]) =>
|
||||
(array: T[]): T[] => {
|
||||
if (!Array.isArray(array)) {
|
||||
return array;
|
||||
}
|
||||
let length = array.length;
|
||||
// Iterate over the array to collect criteria to sort it by
|
||||
let mappedArray: {
|
||||
criteria: unknown[],
|
||||
value: T,
|
||||
criteria: unknown[];
|
||||
value: T;
|
||||
}[] = [];
|
||||
for (let i = 0; i < length; i++) {
|
||||
const value = array[i];
|
||||
mappedArray.push({
|
||||
criteria: iterateeFns.map(fn => fn(value)),
|
||||
criteria: iterateeFns.map((fn) => fn(value)),
|
||||
value,
|
||||
});
|
||||
}
|
||||
@@ -163,15 +156,14 @@ export const range = (start: number, end: number): number[] =>
|
||||
/**
|
||||
* A fast implementation of reduce.
|
||||
*/
|
||||
export const reduce = (reducerFn, initialValue) => array => {
|
||||
export const reduce = (reducerFn, initialValue) => (array) => {
|
||||
const length = array.length;
|
||||
let i;
|
||||
let result;
|
||||
if (initialValue === undefined) {
|
||||
i = 1;
|
||||
result = array[0];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
i = 0;
|
||||
result = initialValue;
|
||||
}
|
||||
@@ -192,13 +184,14 @@ export const reduce = (reducerFn, initialValue) => array => {
|
||||
* is determined by the order they occur in the array. The iteratee is
|
||||
* invoked with one argument: value.
|
||||
*/
|
||||
export const uniqBy = <T extends unknown>(
|
||||
iterateeFn?: (value: T) => unknown
|
||||
) => (array: T[]): T[] => {
|
||||
export const uniqBy =
|
||||
<T extends unknown>(iterateeFn?: (value: T) => unknown) =>
|
||||
(array: T[]): T[] => {
|
||||
const { length } = array;
|
||||
const result: T[] = [];
|
||||
const seen: unknown[] = iterateeFn ? [] : result;
|
||||
let index = -1;
|
||||
// prettier-ignore
|
||||
outer:
|
||||
while (++index < length) {
|
||||
let value: T | 0 = array[index];
|
||||
@@ -214,8 +207,7 @@ export const uniqBy = <T extends unknown>(
|
||||
seen.push(computed);
|
||||
}
|
||||
result.push(value);
|
||||
}
|
||||
else if (!seen.includes(computed)) {
|
||||
} else if (!seen.includes(computed)) {
|
||||
if (seen !== result) {
|
||||
seen.push(computed);
|
||||
}
|
||||
@@ -224,7 +216,6 @@ export const uniqBy = <T extends unknown>(
|
||||
}
|
||||
return result;
|
||||
};
|
||||
/* eslint-enable indent */
|
||||
|
||||
export const uniq = uniqBy();
|
||||
|
||||
@@ -261,7 +252,8 @@ export const zip = <T extends unknown[][]>(...arrays: T): Zip<T> => {
|
||||
* specify how grouped values should be combined. The iteratee is
|
||||
* invoked with the elements of each group.
|
||||
*/
|
||||
export const zipWith = <T, U>(iterateeFn: (...values: T[]) => U) =>
|
||||
export const zipWith =
|
||||
<T, U>(iterateeFn: (...values: T[]) => U) =>
|
||||
(...arrays: T[][]): U[] => {
|
||||
return map((values: T[]) => iterateeFn(...values))(zip(...arrays));
|
||||
};
|
||||
@@ -269,7 +261,7 @@ export const zipWith = <T, U>(iterateeFn: (...values: T[]) => U) =>
|
||||
const binarySearch = <T, U = unknown>(
|
||||
getKey: (value: T) => U,
|
||||
collection: readonly T[],
|
||||
inserting: T,
|
||||
inserting: T
|
||||
): number => {
|
||||
if (collection.length === 0) {
|
||||
return 0;
|
||||
@@ -301,12 +293,10 @@ const binarySearch = <T, U = unknown>(
|
||||
return compare > insertingKey ? middle : middle + 1;
|
||||
};
|
||||
|
||||
export const binaryInsertWith = <T, U = unknown>(getKey: (value: T) => U):
|
||||
((collection: readonly T[], value: T) => T[]) =>
|
||||
{
|
||||
return (collection, value) => {
|
||||
export const binaryInsertWith =
|
||||
<T, U = unknown>(getKey: (value: T) => U) =>
|
||||
(collection: readonly T[], value: T) => {
|
||||
const copy = [...collection];
|
||||
copy.splice(binarySearch(getKey, collection, value), 0, value);
|
||||
return copy;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -27,23 +27,23 @@ export class Color {
|
||||
/**
|
||||
* Creates a color from the CSS hex color notation.
|
||||
*/
|
||||
Color.fromHex = hex => (
|
||||
Color.fromHex = (hex) =>
|
||||
new Color(
|
||||
parseInt(hex.substr(1, 2), 16),
|
||||
parseInt(hex.substr(3, 2), 16),
|
||||
parseInt(hex.substr(5, 2), 16))
|
||||
);
|
||||
parseInt(hex.substr(5, 2), 16)
|
||||
);
|
||||
|
||||
/**
|
||||
* Linear interpolation of two colors.
|
||||
*/
|
||||
Color.lerp = (c1, c2, n) => (
|
||||
Color.lerp = (c1, c2, n) =>
|
||||
new Color(
|
||||
(c2.r - c1.r) * n + c1.r,
|
||||
(c2.g - c1.g) * n + c1.g,
|
||||
(c2.b - c1.b) * n + c1.b,
|
||||
(c2.a - c1.a) * n + c1.a)
|
||||
);
|
||||
(c2.a - c1.a) * n + c1.a
|
||||
);
|
||||
|
||||
/**
|
||||
* Loops up the color in the provided list of colors
|
||||
|
||||
@@ -19,10 +19,9 @@ export class EventEmitter {
|
||||
if (!listeners) {
|
||||
throw new Error(`There is no listeners for "${name}"`);
|
||||
}
|
||||
this.listeners[name] = listeners
|
||||
.filter(existingListener => {
|
||||
return existingListener !== listener;
|
||||
});
|
||||
this.listeners[name] = listeners.filter((existingListener) => {
|
||||
return existingListener !== listener;
|
||||
});
|
||||
}
|
||||
|
||||
emit(name, ...params) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* functions, where each successive invocation is supplied the return
|
||||
* value of the previous.
|
||||
*/
|
||||
// prettier-ignore
|
||||
export const flow = (...funcs) => (input, ...rest) => {
|
||||
let output = input;
|
||||
for (let func of funcs) {
|
||||
@@ -37,11 +38,12 @@ export const flow = (...funcs) => (input, ...rest) => {
|
||||
*/
|
||||
export const compose = (...funcs) => {
|
||||
if (funcs.length === 0) {
|
||||
return arg => arg;
|
||||
return (arg) => arg;
|
||||
}
|
||||
if (funcs.length === 1) {
|
||||
return funcs[0];
|
||||
}
|
||||
// prettier-ignore
|
||||
return funcs.reduce((a, b) => (value, ...rest) =>
|
||||
a(b(value, ...rest), ...rest));
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ export const clamp = (value, min, max) => {
|
||||
/**
|
||||
* Limits a number between 0 and 1.
|
||||
*/
|
||||
export const clamp01 = value => {
|
||||
export const clamp01 = (value) => {
|
||||
return value < 0 ? 0 : value > 1 ? 1 : value;
|
||||
};
|
||||
|
||||
@@ -69,9 +69,7 @@ export const toFixed = (value, fractionDigits = 0) => {
|
||||
* Range is an array of two numbers, for example: [0, 15].
|
||||
*/
|
||||
export const inRange = (value, range) => {
|
||||
return range
|
||||
&& value >= range[0]
|
||||
&& value <= range[1];
|
||||
return range && value >= range[0] && value <= range[1];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -92,7 +90,7 @@ export const keyOfMatchingRange = (value, ranges) => {
|
||||
/**
|
||||
* Get number of digits following the decimal point in a number
|
||||
*/
|
||||
export const numberOfDecimalDigits = value => {
|
||||
export const numberOfDecimalDigits = (value) => {
|
||||
if (Math.floor(value) !== value) {
|
||||
return value.toString().split('.')[1].length || 0;
|
||||
}
|
||||
|
||||
@@ -48,8 +48,9 @@ const measure = (markerNameA, markerNameB) => {
|
||||
}
|
||||
};
|
||||
|
||||
const formatDuration = duration => {
|
||||
const formatDuration = (duration) => {
|
||||
const durationInFrames = duration / FRAME_DURATION;
|
||||
// prettier-ignore
|
||||
return duration.toFixed(duration < 10 ? 1 : 0) + 'ms '
|
||||
+ '(' + durationInFrames.toFixed(2) + ' frames)';
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { clamp } from "./math";
|
||||
import { clamp } from './math';
|
||||
|
||||
/**
|
||||
* Returns random number between lowerBound exclusive and upperBound inclusive
|
||||
@@ -27,7 +27,6 @@ export const randomPick = <T>(array: T[]) => {
|
||||
* Return 1 with probability P percent; otherwise 0
|
||||
*/
|
||||
export const randomProb = (probability: number) => {
|
||||
const normalized = clamp(probability, 0, 100)/100;
|
||||
const normalized = clamp(probability, 0, 100) / 100;
|
||||
return Math.random() <= normalized;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export const classes = (classNames: (string | BooleanLike)[]) => {
|
||||
*/
|
||||
export const normalizeChildren = <T>(children: T | T[]) => {
|
||||
if (Array.isArray(children)) {
|
||||
return children.flat().filter(value => value) as T[];
|
||||
return children.flat().filter((value) => value) as T[];
|
||||
}
|
||||
if (typeof children === 'object') {
|
||||
return [children];
|
||||
@@ -64,6 +64,7 @@ export const pureComponentHooks = {
|
||||
* A helper to determine whether the object is renderable by React.
|
||||
*/
|
||||
export const canRender = (value: unknown) => {
|
||||
// prettier-ignore
|
||||
return value !== undefined
|
||||
&& value !== null
|
||||
&& typeof value !== 'boolean';
|
||||
|
||||
@@ -20,11 +20,11 @@ export const createStore = (reducer, enhancer) => {
|
||||
|
||||
const getState = () => currentState;
|
||||
|
||||
const subscribe = listener => {
|
||||
const subscribe = (listener) => {
|
||||
listeners.push(listener);
|
||||
};
|
||||
|
||||
const dispatch = action => {
|
||||
const dispatch = (action) => {
|
||||
currentState = reducer(currentState, action);
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
listeners[i]();
|
||||
@@ -49,6 +49,7 @@ export const createStore = (reducer, enhancer) => {
|
||||
* actions.
|
||||
*/
|
||||
export const applyMiddleware = (...middlewares) => {
|
||||
// prettier-ignore
|
||||
return createStore => (reducer, ...args) => {
|
||||
const store = createStore(reducer, ...args);
|
||||
|
||||
@@ -80,7 +81,7 @@ export const applyMiddleware = (...middlewares) => {
|
||||
* in the state that are not present in the reducers object. This function
|
||||
* is also more flexible than the redux counterpart.
|
||||
*/
|
||||
export const combineReducers = reducersObj => {
|
||||
export const combineReducers = (reducersObj) => {
|
||||
const keys = Object.keys(reducersObj);
|
||||
let hasChanged = false;
|
||||
return (prevState = {}, action) => {
|
||||
@@ -94,9 +95,7 @@ export const combineReducers = reducersObj => {
|
||||
nextState[key] = nextDomainState;
|
||||
}
|
||||
}
|
||||
return hasChanged
|
||||
? nextState
|
||||
: prevState;
|
||||
return hasChanged ? nextState : prevState;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -136,15 +135,14 @@ export const createAction = (type, prepare = null) => {
|
||||
};
|
||||
actionCreator.toString = () => '' + type;
|
||||
actionCreator.type = type;
|
||||
actionCreator.match = action => action.type === type;
|
||||
actionCreator.match = (action) => action.type === type;
|
||||
return actionCreator;
|
||||
};
|
||||
|
||||
|
||||
// Implementation specific
|
||||
// --------------------------------------------------------
|
||||
|
||||
export const useDispatch = context => {
|
||||
export const useDispatch = (context) => {
|
||||
return context.store.dispatch;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,11 +17,10 @@ const INDEXED_DB_STORE_NAME = 'storage-v1';
|
||||
const READ_ONLY = 'readonly';
|
||||
const READ_WRITE = 'readwrite';
|
||||
|
||||
const testGeneric = testFn => () => {
|
||||
const testGeneric = (testFn) => () => {
|
||||
try {
|
||||
return Boolean(testFn());
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -29,10 +28,12 @@ const testGeneric = testFn => () => {
|
||||
// Localstorage can sometimes throw an error, even if DOM storage is not
|
||||
// disabled in IE11 settings.
|
||||
// See: https://superuser.com/questions/1080011
|
||||
// prettier-ignore
|
||||
const testLocalStorage = testGeneric(() => (
|
||||
window.localStorage && window.localStorage.getItem
|
||||
));
|
||||
|
||||
// prettier-ignore
|
||||
const testIndexedDb = testGeneric(() => (
|
||||
(window.indexedDB || window.msIndexedDB)
|
||||
&& (window.IDBTransaction || window.msIDBTransaction)
|
||||
@@ -96,8 +97,7 @@ class IndexedDbBackend {
|
||||
req.onupgradeneeded = () => {
|
||||
try {
|
||||
req.result.createObjectStore(INDEXED_DB_STORE_NAME);
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
reject(new Error('Failed to upgrade IDB: ' + req.error));
|
||||
}
|
||||
};
|
||||
@@ -109,7 +109,8 @@ class IndexedDbBackend {
|
||||
}
|
||||
|
||||
getStore(mode) {
|
||||
return this.dbPromise.then(db => db
|
||||
// prettier-ignore
|
||||
return this.dbPromise.then((db) => db
|
||||
.transaction(INDEXED_DB_STORE_NAME, mode)
|
||||
.objectStore(INDEXED_DB_STORE_NAME));
|
||||
}
|
||||
@@ -161,8 +162,7 @@ class StorageProxy {
|
||||
const backend = new IndexedDbBackend();
|
||||
await backend.dbPromise;
|
||||
return backend;
|
||||
}
|
||||
catch {}
|
||||
} catch {}
|
||||
}
|
||||
if (testLocalStorage()) {
|
||||
return new LocalStorageBackend();
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
/**
|
||||
* Removes excess whitespace and indentation from the string.
|
||||
*/
|
||||
const multiline = str => {
|
||||
const multiline = (str) => {
|
||||
const lines = str.split('\n');
|
||||
// Determine base indentation
|
||||
let minIndent;
|
||||
@@ -40,15 +40,15 @@ const multiline = str => {
|
||||
// Remove this base indentation and trim the resulting string
|
||||
// from both ends.
|
||||
return lines
|
||||
.map(line => line.substr(minIndent).trimRight())
|
||||
.map((line) => line.substr(minIndent).trimRight())
|
||||
.join('\n')
|
||||
.trim();
|
||||
};
|
||||
|
||||
const StringPlugin = ref => {
|
||||
const StringPlugin = (ref) => {
|
||||
return {
|
||||
visitor: {
|
||||
TaggedTemplateExpression: path => {
|
||||
TaggedTemplateExpression: (path) => {
|
||||
if (path.node.tag.name === 'multiline') {
|
||||
const { quasi } = path.node;
|
||||
if (quasi.expressions.length > 0) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
/**
|
||||
* Removes excess whitespace and indentation from the string.
|
||||
*/
|
||||
export const multiline = str => {
|
||||
export const multiline = (str) => {
|
||||
if (Array.isArray(str)) {
|
||||
// Small stub to allow usage as a template tag
|
||||
return multiline(str.join(''));
|
||||
@@ -32,7 +32,7 @@ export const multiline = str => {
|
||||
// Remove this base indentation and trim the resulting string
|
||||
// from both ends.
|
||||
return lines
|
||||
.map(line => line.substr(minIndent).trimRight())
|
||||
.map((line) => line.substr(minIndent).trimRight())
|
||||
.join('\n')
|
||||
.trim();
|
||||
};
|
||||
@@ -44,12 +44,13 @@ export const multiline = str => {
|
||||
*
|
||||
* Example: createGlobPattern('*@domain')('user@domain') === true
|
||||
*/
|
||||
export const createGlobPattern = pattern => {
|
||||
const escapeString = str => str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
|
||||
export const createGlobPattern = (pattern) => {
|
||||
const escapeString = (str) => str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
|
||||
// prettier-ignore
|
||||
const regex = new RegExp('^'
|
||||
+ pattern.split(/\*+/).map(escapeString).join('.*')
|
||||
+ '$');
|
||||
return str => regex.test(str);
|
||||
return (str) => regex.test(str);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -64,7 +65,7 @@ export const createGlobPattern = pattern => {
|
||||
*/
|
||||
export const createSearch = (searchText, stringifier) => {
|
||||
const preparedSearchText = searchText.toLowerCase().trim();
|
||||
return obj => {
|
||||
return (obj) => {
|
||||
if (!preparedSearchText) {
|
||||
return true;
|
||||
}
|
||||
@@ -72,13 +73,11 @@ export const createSearch = (searchText, stringifier) => {
|
||||
if (!str) {
|
||||
return false;
|
||||
}
|
||||
return str
|
||||
.toLowerCase()
|
||||
.includes(preparedSearchText);
|
||||
return str.toLowerCase().includes(preparedSearchText);
|
||||
};
|
||||
};
|
||||
|
||||
export const capitalize = str => {
|
||||
export const capitalize = (str) => {
|
||||
// Handle array
|
||||
if (Array.isArray(str)) {
|
||||
return str.map(capitalize);
|
||||
@@ -87,7 +86,7 @@ export const capitalize = str => {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
||||
};
|
||||
|
||||
export const toTitleCase = str => {
|
||||
export const toTitleCase = (str) => {
|
||||
// Handle array
|
||||
if (Array.isArray(str)) {
|
||||
return str.map(toTitleCase);
|
||||
@@ -98,20 +97,21 @@ export const toTitleCase = str => {
|
||||
}
|
||||
// Handle string
|
||||
const WORDS_UPPER = ['Id', 'Tv'];
|
||||
// prettier-ignore
|
||||
const WORDS_LOWER = [
|
||||
'A', 'An', 'And', 'As', 'At', 'But', 'By', 'For', 'For', 'From', 'In',
|
||||
'Into', 'Near', 'Nor', 'Of', 'On', 'Onto', 'Or', 'The', 'To', 'With',
|
||||
];
|
||||
let currentStr = str.replace(/([^\W_]+[^\s-]*) */g, str => {
|
||||
let currentStr = str.replace(/([^\W_]+[^\s-]*) */g, (str) => {
|
||||
return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase();
|
||||
});
|
||||
for (let word of WORDS_LOWER) {
|
||||
const regex = new RegExp('\\s' + word + '\\s', 'g');
|
||||
currentStr = currentStr.replace(regex, str => str.toLowerCase());
|
||||
currentStr = currentStr.replace(regex, (str) => str.toLowerCase());
|
||||
}
|
||||
for (let word of WORDS_UPPER) {
|
||||
const regex = new RegExp('\\b' + word + '\\b', 'g');
|
||||
currentStr = currentStr.replace(regex, str => str.toLowerCase());
|
||||
currentStr = currentStr.replace(regex, (str) => str.toLowerCase());
|
||||
}
|
||||
return currentStr;
|
||||
};
|
||||
@@ -122,7 +122,7 @@ export const toTitleCase = str => {
|
||||
* @param {String} str Encoded HTML string
|
||||
* @return {String} Decoded HTML string
|
||||
*/
|
||||
export const decodeHtmlEntities = str => {
|
||||
export const decodeHtmlEntities = (str) => {
|
||||
if (!str) {
|
||||
return str;
|
||||
}
|
||||
@@ -133,8 +133,9 @@ export const decodeHtmlEntities = str => {
|
||||
quot: '"',
|
||||
lt: '<',
|
||||
gt: '>',
|
||||
apos: '\'',
|
||||
apos: "'",
|
||||
};
|
||||
// prettier-ignore
|
||||
return str
|
||||
// Newline tags
|
||||
.replace(/<br>/gi, '\n')
|
||||
@@ -156,6 +157,7 @@ export const decodeHtmlEntities = str => {
|
||||
/**
|
||||
* Converts an object into a query string,
|
||||
*/
|
||||
// prettier-ignore
|
||||
export const buildQueryString = obj => Object.keys(obj)
|
||||
.map(key => encodeURIComponent(key)
|
||||
+ '=' + encodeURIComponent(obj[key]))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Returns the arguments of a function F as an array.
|
||||
*/
|
||||
// prettier-ignore
|
||||
export type ArgumentsOf<F extends Function>
|
||||
= F extends (...args: infer A) => unknown ? A : never;
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
*/
|
||||
export const createUuid = () => {
|
||||
let d = new Date().getTime();
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||
const r = (d + Math.random() * 16) % 16 | 0;
|
||||
d = Math.floor(d / 16);
|
||||
// prettier-ignore
|
||||
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -32,17 +32,17 @@ export const vecDivide = (...vecs) => {
|
||||
};
|
||||
|
||||
export const vecScale = (vec, n) => {
|
||||
return map(x => x * n)(vec);
|
||||
return map((x) => x * n)(vec);
|
||||
};
|
||||
|
||||
export const vecInverse = vec => {
|
||||
return map(x => -x)(vec);
|
||||
export const vecInverse = (vec) => {
|
||||
return map((x) => -x)(vec);
|
||||
};
|
||||
|
||||
export const vecLength = vec => {
|
||||
export const vecLength = (vec) => {
|
||||
return Math.sqrt(reduce(ADD)(zipWith(MUL)(vec, vec)));
|
||||
};
|
||||
|
||||
export const vecNormalize = vec => {
|
||||
export const vecNormalize = (vec) => {
|
||||
return vecDivide(vec, vecLength(vec));
|
||||
};
|
||||
|
||||
@@ -10,5 +10,4 @@ process.chdir(__dirname);
|
||||
// Silently make a dist folder
|
||||
try {
|
||||
require('fs').mkdirSync('dist');
|
||||
}
|
||||
catch (err) {}
|
||||
} catch (err) {}
|
||||
|
||||
@@ -62,8 +62,7 @@ const setupApp = async () => {
|
||||
}
|
||||
suite.run();
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
} catch (error) {
|
||||
sendMessage({ type: 'error', error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ const setup = async () => {
|
||||
assets += `</script>\n`;
|
||||
|
||||
const publicDir = path.resolve(__dirname, '../../public');
|
||||
const page = fs.readFileSync(path.join(publicDir, 'tgui.html'), 'utf-8')
|
||||
const page = fs
|
||||
.readFileSync(path.join(publicDir, 'tgui.html'), 'utf-8')
|
||||
.replace('<!-- tgui:assets -->\n', assets);
|
||||
|
||||
server.register(require('fastify-static'), {
|
||||
@@ -67,8 +68,7 @@ const setup = async () => {
|
||||
|
||||
try {
|
||||
await server.listen(3002, '0.0.0.0');
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* Manually stripped from useless junk by /tg/station13 maintainers.
|
||||
* Available under MIT license <https://mths.be/mit>
|
||||
*/
|
||||
// prettier-ignore
|
||||
module.exports = (function() {
|
||||
'use strict';
|
||||
|
||||
|
||||
@@ -7,28 +7,18 @@ const render = createRenderer();
|
||||
const handleClick = () => undefined;
|
||||
|
||||
export const SingleButton = () => {
|
||||
const node = (
|
||||
<Button>
|
||||
Hello world!
|
||||
</Button>
|
||||
);
|
||||
const node = <Button>Hello world!</Button>;
|
||||
render(node);
|
||||
};
|
||||
|
||||
export const SingleButtonWithCallback = () => {
|
||||
const node = (
|
||||
<Button onClick={() => undefined}>
|
||||
Hello world!
|
||||
</Button>
|
||||
);
|
||||
const node = <Button onClick={() => undefined}>Hello world!</Button>;
|
||||
render(node);
|
||||
};
|
||||
|
||||
export const SingleButtonWithLinkEvent = () => {
|
||||
const node = (
|
||||
<Button onClick={linkEvent(null, handleClick)}>
|
||||
Hello world!
|
||||
</Button>
|
||||
<Button onClick={linkEvent(null, handleClick)}>Hello world!</Button>
|
||||
);
|
||||
render(node);
|
||||
};
|
||||
@@ -36,11 +26,7 @@ export const SingleButtonWithLinkEvent = () => {
|
||||
export const ListOfButtons = () => {
|
||||
const nodes: JSX.Element[] = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const node = (
|
||||
<Button key={i}>
|
||||
Hello world! {i}
|
||||
</Button>
|
||||
);
|
||||
const node = <Button key={i}>Hello world! {i}</Button>;
|
||||
nodes.push(node);
|
||||
}
|
||||
render(<div>{nodes}</div>);
|
||||
|
||||
@@ -6,9 +6,11 @@ import { configureStore, StoreProvider } from 'tgui/store';
|
||||
const store = configureStore({ sideEffets: false });
|
||||
|
||||
const renderUi = createRenderer((dataJson: string) => {
|
||||
store.dispatch(backendUpdate({
|
||||
data: Byond.parseJson(dataJson),
|
||||
}));
|
||||
store.dispatch(
|
||||
backendUpdate({
|
||||
data: Byond.parseJson(dataJson),
|
||||
})
|
||||
);
|
||||
return (
|
||||
<StoreProvider store={store}>
|
||||
<DisposalUnit />
|
||||
|
||||
@@ -6,9 +6,7 @@ const render = createRenderer();
|
||||
export const Default = () => {
|
||||
const node = (
|
||||
<Flex align="baseline">
|
||||
<Flex.Item mr={1}>
|
||||
Text {Math.random()}
|
||||
</Flex.Item>
|
||||
<Flex.Item mr={1}>Text {Math.random()}</Flex.Item>
|
||||
<Flex.Item grow={1} basis={0}>
|
||||
Text {Math.random()}
|
||||
</Flex.Item>
|
||||
|
||||
@@ -6,9 +6,7 @@ const render = createRenderer();
|
||||
export const Default = () => {
|
||||
const node = (
|
||||
<Stack align="baseline">
|
||||
<Stack.Item>
|
||||
Text {Math.random()}
|
||||
</Stack.Item>
|
||||
<Stack.Item>Text {Math.random()}</Stack.Item>
|
||||
<Stack.Item grow={1} basis={0}>
|
||||
Text {Math.random()}
|
||||
</Stack.Item>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Box, Tooltip } from "tgui/components";
|
||||
import { createRenderer } from "tgui/renderer";
|
||||
import { Box, Tooltip } from 'tgui/components';
|
||||
import { createRenderer } from 'tgui/renderer';
|
||||
|
||||
const render = createRenderer();
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export class DreamSeeker {
|
||||
}
|
||||
|
||||
topic(params = {}) {
|
||||
// prettier-ignore
|
||||
const query = Object.keys(params)
|
||||
.map(key => encodeURIComponent(key)
|
||||
+ '=' + encodeURIComponent(params[key]))
|
||||
@@ -36,7 +37,7 @@ export class DreamSeeker {
|
||||
* @param {number[]} pids
|
||||
* @returns {DreamSeeker[]}
|
||||
*/
|
||||
DreamSeeker.getInstancesByPids = async pids => {
|
||||
DreamSeeker.getInstancesByPids = async (pids) => {
|
||||
if (process.platform !== 'win32') {
|
||||
return [];
|
||||
}
|
||||
@@ -46,8 +47,7 @@ DreamSeeker.getInstancesByPids = async pids => {
|
||||
const instance = instanceByPid.get(pid);
|
||||
if (instance) {
|
||||
instances.push(instance);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
pidsToResolve.push(pid);
|
||||
}
|
||||
}
|
||||
@@ -83,12 +83,10 @@ DreamSeeker.getInstancesByPids = async pids => {
|
||||
instances.push(instance);
|
||||
instanceByPid.set(pid, instance);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
if (err.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
|
||||
logger.error(err.message, err.code);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
logger.error(err);
|
||||
}
|
||||
return [];
|
||||
@@ -97,4 +95,4 @@ DreamSeeker.getInstancesByPids = async pids => {
|
||||
return instances;
|
||||
};
|
||||
|
||||
const plural = (word, n) => n !== 1 ? word + 's' : word;
|
||||
const plural = (word, n) => (n !== 1 ? word + 's' : word);
|
||||
|
||||
@@ -23,7 +23,7 @@ const ensureConnection = () => {
|
||||
socket.send(msg);
|
||||
}
|
||||
};
|
||||
socket.onmessage = event => {
|
||||
socket.onmessage = (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
for (let subscriber of subscribers) {
|
||||
subscriber(msg);
|
||||
@@ -37,14 +37,14 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
window.onunload = () => socket && socket.close();
|
||||
}
|
||||
|
||||
const subscribe = fn => subscribers.push(fn);
|
||||
const subscribe = (fn) => subscribers.push(fn);
|
||||
|
||||
/**
|
||||
* A json serializer which handles circular references and other junk.
|
||||
*/
|
||||
const serializeObject = obj => {
|
||||
const serializeObject = (obj) => {
|
||||
let refs = [];
|
||||
const primitiveReviver = value => {
|
||||
const primitiveReviver = (value) => {
|
||||
if (typeof value === 'number' && !Number.isFinite(value)) {
|
||||
return {
|
||||
__number__: String(value),
|
||||
@@ -68,6 +68,7 @@ const serializeObject = obj => {
|
||||
}
|
||||
refs.push(value);
|
||||
// Error object
|
||||
// prettier-ignore
|
||||
const isError = value instanceof Error || (
|
||||
value.code && value.message && value.message.includes('Error')
|
||||
);
|
||||
@@ -91,7 +92,7 @@ const serializeObject = obj => {
|
||||
return json;
|
||||
};
|
||||
|
||||
const sendMessage = msg => {
|
||||
const sendMessage = (msg) => {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const json = serializeObject(msg);
|
||||
// Send message using WebSocket
|
||||
@@ -99,8 +100,7 @@ const sendMessage = msg => {
|
||||
ensureConnection();
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(json);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Keep only 100 latest messages in the queue
|
||||
if (queue.length > 100) {
|
||||
queue.shift();
|
||||
@@ -130,19 +130,21 @@ const sendLogEntry = (level, ns, ...args) => {
|
||||
args,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (err) {}
|
||||
} catch (err) {}
|
||||
}
|
||||
};
|
||||
|
||||
const setupHotReloading = () => {
|
||||
if (process.env.NODE_ENV !== 'production'
|
||||
if (
|
||||
// prettier-ignore
|
||||
process.env.NODE_ENV !== 'production'
|
||||
&& process.env.WEBPACK_HMR_ENABLED
|
||||
&& window.WebSocket) {
|
||||
&& window.WebSocket
|
||||
) {
|
||||
if (module.hot) {
|
||||
ensureConnection();
|
||||
sendLogEntry(0, null, 'setting up hot reloading');
|
||||
subscribe(msg => {
|
||||
subscribe((msg) => {
|
||||
const { type } = msg;
|
||||
sendLogEntry(0, null, 'received', type);
|
||||
if (type === 'hotUpdate') {
|
||||
@@ -157,10 +159,10 @@ const setupHotReloading = () => {
|
||||
ignoreDeclined: true,
|
||||
ignoreErrored: true,
|
||||
})
|
||||
.then(modules => {
|
||||
.then((modules) => {
|
||||
sendLogEntry(0, null, 'outdated modules', modules);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
sendLogEntry(0, null, 'reload error', err);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const logger = createLogger('retrace');
|
||||
const { SourceMapConsumer } = SourceMap;
|
||||
const sourceMaps = [];
|
||||
|
||||
export const loadSourceMaps = async bundleDir => {
|
||||
export const loadSourceMaps = async (bundleDir) => {
|
||||
// Destroy and garbage collect consumers
|
||||
while (sourceMaps.length !== 0) {
|
||||
const { consumer } = sourceMaps.shift();
|
||||
@@ -30,29 +30,29 @@ export const loadSourceMaps = async bundleDir => {
|
||||
try {
|
||||
const file = basename(path).replace('.map', '');
|
||||
const consumer = await new SourceMapConsumer(
|
||||
JSON.parse(fs.readFileSync(path, 'utf8')));
|
||||
JSON.parse(fs.readFileSync(path, 'utf8'))
|
||||
);
|
||||
sourceMaps.push({ file, consumer });
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
logger.log(`loaded ${sourceMaps.length} source maps`);
|
||||
};
|
||||
|
||||
export const retrace = stack => {
|
||||
export const retrace = (stack) => {
|
||||
if (typeof stack !== 'string') {
|
||||
logger.log('ERROR: Stack is not a string!', stack);
|
||||
return stack;
|
||||
}
|
||||
const header = stack.split(/\n\s.*at/)[0];
|
||||
const mappedStack = parseStackTrace(stack)
|
||||
.map(frame => {
|
||||
.map((frame) => {
|
||||
if (!frame.file) {
|
||||
return frame;
|
||||
}
|
||||
// Find the correct source map
|
||||
const sourceMap = sourceMaps.find(sourceMap => {
|
||||
const sourceMap = sourceMaps.find((sourceMap) => {
|
||||
return frame.file.includes(sourceMap.file);
|
||||
});
|
||||
if (!sourceMap) {
|
||||
@@ -72,7 +72,7 @@ export const retrace = stack => {
|
||||
column: mappedFrame.column,
|
||||
};
|
||||
})
|
||||
.map(frame => {
|
||||
.map((frame) => {
|
||||
// Stringify the frame
|
||||
const { file, methodName, lineNumber } = frame;
|
||||
if (!file) {
|
||||
|
||||
@@ -32,9 +32,9 @@ class LinkServer {
|
||||
setupWebSocketLink() {
|
||||
const port = 3000;
|
||||
this.wss = new WebSocket.Server({ port });
|
||||
this.wss.on('connection', ws => {
|
||||
this.wss.on('connection', (ws) => {
|
||||
logger.log('client connected');
|
||||
ws.on('message', json => {
|
||||
ws.on('message', (json) => {
|
||||
const msg = deserializeObject(json);
|
||||
this.handleLinkMessage(ws, msg);
|
||||
});
|
||||
@@ -51,7 +51,7 @@ class LinkServer {
|
||||
this.httpServer = http.createServer((req, res) => {
|
||||
if (req.method === 'POST') {
|
||||
let body = '';
|
||||
req.on('data', chunk => {
|
||||
req.on('data', (chunk) => {
|
||||
body += chunk.toString();
|
||||
});
|
||||
req.on('end', () => {
|
||||
@@ -76,6 +76,7 @@ class LinkServer {
|
||||
if (level <= 0 && !DEBUG) {
|
||||
return;
|
||||
}
|
||||
// prettier-ignore
|
||||
directLog(ns, ...args.map(arg => {
|
||||
if (typeof arg === 'object') {
|
||||
return inspect(arg, {
|
||||
@@ -117,7 +118,7 @@ class LinkServer {
|
||||
}
|
||||
}
|
||||
|
||||
const deserializeObject = str => {
|
||||
const deserializeObject = (str) => {
|
||||
return JSON.parse(str, (key, value) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (value.__undefined__) {
|
||||
|
||||
@@ -11,8 +11,7 @@ const isNode = process && process.release && process.release.name === 'node';
|
||||
let isChrome = false;
|
||||
try {
|
||||
isChrome = window.navigator.userAgent.toLowerCase().includes('chrome');
|
||||
}
|
||||
catch {}
|
||||
} catch {}
|
||||
|
||||
// Timestamping function
|
||||
const getTimestamp = () => {
|
||||
@@ -32,7 +31,7 @@ const getPrefix = (() => {
|
||||
bright: '\x1b[37;1m',
|
||||
reset: '\x1b[0m',
|
||||
};
|
||||
return ns => [
|
||||
return (ns) => [
|
||||
`${ESC.dimmed}${getTimestamp()} ${ESC.bright}${ns}${ESC.reset}`,
|
||||
];
|
||||
}
|
||||
@@ -42,12 +41,13 @@ const getPrefix = (() => {
|
||||
dimmed: 'color: #888',
|
||||
bright: 'font-weight: bold',
|
||||
};
|
||||
return ns => [
|
||||
return (ns) => [
|
||||
`%c${getTimestamp()}%c ${ns}`,
|
||||
styles.dimmed,
|
||||
styles.bright,
|
||||
];
|
||||
}
|
||||
// prettier-ignore
|
||||
return ns => [
|
||||
`${getTimestamp()} ${ns}`,
|
||||
];
|
||||
@@ -56,7 +56,7 @@ const getPrefix = (() => {
|
||||
/**
|
||||
* Creates a logger object.
|
||||
*/
|
||||
export const createLogger = ns => ({
|
||||
export const createLogger = (ns) => ({
|
||||
log: (...args) => console.log(...getPrefix(ns), ...args),
|
||||
trace: (...args) => console.trace(...getPrefix(ns), ...args),
|
||||
debug: (...args) => console.debug(...getPrefix(ns), ...args),
|
||||
|
||||
@@ -50,10 +50,9 @@ export const findCacheRoot = async () => {
|
||||
// Query the Windows Registry
|
||||
if (process.platform === 'win32') {
|
||||
logger.log('querying windows registry');
|
||||
let userpath = await regQuery(
|
||||
'HKCU\\Software\\Dantom\\BYOND',
|
||||
'userpath');
|
||||
let userpath = await regQuery('HKCU\\Software\\Dantom\\BYOND', 'userpath');
|
||||
if (userpath) {
|
||||
// prettier-ignore
|
||||
cacheRoot = userpath
|
||||
.replace(/\\$/, '')
|
||||
.replace(/\\/g, '/')
|
||||
@@ -65,13 +64,13 @@ export const findCacheRoot = async () => {
|
||||
logger.log('found no cache directories');
|
||||
};
|
||||
|
||||
const onCacheRootFound = cacheRoot => {
|
||||
const onCacheRootFound = (cacheRoot) => {
|
||||
logger.log(`found cache at '${cacheRoot}'`);
|
||||
// Plant a dummy
|
||||
fs.closeSync(fs.openSync(cacheRoot + '/dummy', 'w'));
|
||||
};
|
||||
|
||||
export const reloadByondCache = async bundleDir => {
|
||||
export const reloadByondCache = async (bundleDir) => {
|
||||
const cacheRoot = await findCacheRoot();
|
||||
if (!cacheRoot) {
|
||||
return;
|
||||
@@ -83,15 +82,21 @@ export const reloadByondCache = async bundleDir => {
|
||||
return;
|
||||
}
|
||||
// Get dreamseeker instances
|
||||
const pids = cacheDirs.map(cacheDir => (
|
||||
const pids = cacheDirs.map((cacheDir) =>
|
||||
parseInt(cacheDir.split('/cache/tmp').pop(), 10)
|
||||
));
|
||||
);
|
||||
const dssPromise = DreamSeeker.getInstancesByPids(pids);
|
||||
// Copy assets
|
||||
const assets = await resolveGlob(bundleDir, './*.+(bundle|chunk|hot-update).*');
|
||||
const assets = await resolveGlob(
|
||||
bundleDir,
|
||||
'./*.+(bundle|chunk|hot-update).*'
|
||||
);
|
||||
for (let cacheDir of cacheDirs) {
|
||||
// Clear garbage
|
||||
const garbage = await resolveGlob(cacheDir, './*.+(bundle|chunk|hot-update).*');
|
||||
const garbage = await resolveGlob(
|
||||
cacheDir,
|
||||
'./*.+(bundle|chunk|hot-update).*'
|
||||
);
|
||||
try {
|
||||
for (let file of garbage) {
|
||||
fs.unlinkSync(file);
|
||||
@@ -102,8 +107,7 @@ export const reloadByondCache = async bundleDir => {
|
||||
fs.writeFileSync(destination, fs.readFileSync(asset));
|
||||
}
|
||||
logger.log(`copied ${assets.length} files to '${cacheDir}'`);
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
logger.error(`failed copying to '${cacheDir}'`);
|
||||
logger.error(err);
|
||||
}
|
||||
|
||||
@@ -25,8 +25,7 @@ export const resolveGlob = (...sections) => {
|
||||
try {
|
||||
fs.statSync(path);
|
||||
safePaths.push(path);
|
||||
}
|
||||
catch {}
|
||||
} catch {}
|
||||
}
|
||||
return safePaths;
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ const logger = createLogger('webpack');
|
||||
* @param {any} config
|
||||
* @return {WebpackCompiler}
|
||||
*/
|
||||
export const createCompiler = async options => {
|
||||
export const createCompiler = async (options) => {
|
||||
const compiler = new WebpackCompiler();
|
||||
await compiler.setup(options);
|
||||
return compiler;
|
||||
@@ -57,7 +57,7 @@ class WebpackCompiler {
|
||||
logger.log('compiling');
|
||||
});
|
||||
// Start reloading when it's finished
|
||||
compiler.hooks.done.tap('tgui-dev-server', async stats => {
|
||||
compiler.hooks.done.tap('tgui-dev-server', async (stats) => {
|
||||
// Load source maps
|
||||
await loadSourceMaps(this.bundleDir);
|
||||
// Reload cache
|
||||
@@ -77,7 +77,7 @@ class WebpackCompiler {
|
||||
stats
|
||||
.toString(this.config.devServer.stats)
|
||||
.split('\n')
|
||||
.forEach(line => logger.log(line));
|
||||
.forEach((line) => logger.log(line));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,17 +30,14 @@ export const regQuery = async (path, key) => {
|
||||
logger.error('could not find the end of the line');
|
||||
return null;
|
||||
}
|
||||
const indexOfValue = stdout.indexOf(
|
||||
' ',
|
||||
indexOfKey + keyPattern.length);
|
||||
const indexOfValue = stdout.indexOf(' ', indexOfKey + keyPattern.length);
|
||||
if (indexOfValue === -1) {
|
||||
logger.error('could not find the start of the key value');
|
||||
return null;
|
||||
}
|
||||
const value = stdout.substring(indexOfValue + 4, indexOfEol);
|
||||
return value;
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
logger.error(err);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,33 +6,20 @@
|
||||
|
||||
import { Flex } from 'tgui/components';
|
||||
|
||||
export const Notifications = props => {
|
||||
export const Notifications = (props) => {
|
||||
const { children } = props;
|
||||
return (
|
||||
<div className="Notifications">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
return <div className="Notifications">{children}</div>;
|
||||
};
|
||||
|
||||
const NotificationsItem = props => {
|
||||
const {
|
||||
rightSlot,
|
||||
children,
|
||||
} = props;
|
||||
const NotificationsItem = (props) => {
|
||||
const { rightSlot, children } = props;
|
||||
return (
|
||||
<Flex
|
||||
align="center"
|
||||
className="Notification">
|
||||
<Flex.Item
|
||||
className="Notification__content"
|
||||
grow={1}>
|
||||
<Flex align="center" className="Notification">
|
||||
<Flex.Item className="Notification__content" grow={1}>
|
||||
{children}
|
||||
</Flex.Item>
|
||||
{rightSlot && (
|
||||
<Flex.Item className="Notification__rightSlot">
|
||||
{rightSlot}
|
||||
</Flex.Item>
|
||||
<Flex.Item className="Notification__rightSlot">{rightSlot}</Flex.Item>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -17,9 +17,7 @@ import { SettingsPanel, useSettings } from './settings';
|
||||
export const Panel = (props, context) => {
|
||||
// IE8-10: Needs special treatment due to missing Flex support
|
||||
if (Byond.IS_LTE_IE10) {
|
||||
return (
|
||||
<HoboPanel />
|
||||
);
|
||||
return <HoboPanel />;
|
||||
}
|
||||
const audio = useAudio(context);
|
||||
const settings = useSettings(context);
|
||||
@@ -28,9 +26,7 @@ export const Panel = (props, context) => {
|
||||
const { useDebug, KitchenSink } = require('tgui/debug');
|
||||
const debug = useDebug(context);
|
||||
if (debug.kitchenSink) {
|
||||
return (
|
||||
<KitchenSink panel />
|
||||
);
|
||||
return <KitchenSink panel />;
|
||||
}
|
||||
}
|
||||
return (
|
||||
@@ -52,17 +48,19 @@ export const Panel = (props, context) => {
|
||||
icon="music"
|
||||
tooltip="Music player"
|
||||
tooltipPosition="bottom-start"
|
||||
onClick={() => audio.toggle()} />
|
||||
onClick={() => audio.toggle()}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon={settings.visible ? 'times' : 'cog'}
|
||||
selected={settings.visible}
|
||||
tooltip={settings.visible
|
||||
? 'Close settings'
|
||||
: 'Open settings'}
|
||||
tooltip={
|
||||
settings.visible ? 'Close settings' : 'Open settings'
|
||||
}
|
||||
tooltipPosition="bottom-start"
|
||||
onClick={() => settings.toggle()} />
|
||||
onClick={() => settings.toggle()}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
@@ -86,10 +84,9 @@ export const Panel = (props, context) => {
|
||||
</Pane.Content>
|
||||
<Notifications>
|
||||
{game.connectionLostAt && (
|
||||
<Notifications.Item
|
||||
rightSlot={<ReconnectButton />}>
|
||||
You are either AFK, experiencing lag or the connection
|
||||
has closed.
|
||||
<Notifications.Item rightSlot={<ReconnectButton />}>
|
||||
You are either AFK, experiencing lag or the connection has
|
||||
closed.
|
||||
</Notifications.Item>
|
||||
)}
|
||||
{game.roundRestartedAt && (
|
||||
@@ -122,9 +119,7 @@ const HoboPanel = (props, context) => {
|
||||
onClick={() => settings.toggle()}>
|
||||
Settings
|
||||
</Button>
|
||||
{settings.visible && (
|
||||
<SettingsPanel />
|
||||
) || (
|
||||
{(settings.visible && <SettingsPanel />) || (
|
||||
<ChatPanel lineHeight={settings.lineHeight} />
|
||||
)}
|
||||
</Pane.Content>
|
||||
|
||||
@@ -17,12 +17,9 @@ export const NowPlayingWidget = (props, context) => {
|
||||
const title = audio.meta?.title;
|
||||
return (
|
||||
<Flex align="center">
|
||||
{audio.playing && (
|
||||
{(audio.playing && (
|
||||
<>
|
||||
<Flex.Item
|
||||
shrink={0}
|
||||
mx={0.5}
|
||||
color="label">
|
||||
<Flex.Item shrink={0} mx={0.5} color="label">
|
||||
Now playing:
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
@@ -36,7 +33,7 @@ export const NowPlayingWidget = (props, context) => {
|
||||
{'An admin-played soundtrack.'}
|
||||
</Flex.Item>
|
||||
</>
|
||||
) || (
|
||||
)) || (
|
||||
<Flex.Item grow={1} color="label">
|
||||
Nothing to play.
|
||||
</Flex.Item>
|
||||
@@ -46,9 +43,12 @@ export const NowPlayingWidget = (props, context) => {
|
||||
<Button
|
||||
tooltip="Stop"
|
||||
icon="stop"
|
||||
onClick={() => dispatch({
|
||||
type: 'audio/stopMusic',
|
||||
})} />
|
||||
onClick={() =>
|
||||
dispatch({
|
||||
type: 'audio/stopMusic',
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Flex.Item>
|
||||
)}
|
||||
<Flex.Item mx={0.5} fontSize="0.9em">
|
||||
@@ -58,10 +58,13 @@ export const NowPlayingWidget = (props, context) => {
|
||||
value={settings.adminMusicVolume}
|
||||
step={0.0025}
|
||||
stepPixelSize={1}
|
||||
format={value => toFixed(value * 100) + '%'}
|
||||
onDrag={(e, value) => settings.update({
|
||||
adminMusicVolume: value,
|
||||
})} />
|
||||
format={(value) => toFixed(value * 100) + '%'}
|
||||
onDrag={(e, value) =>
|
||||
settings.update({
|
||||
adminMusicVolume: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { useSelector, useDispatch } from 'common/redux';
|
||||
import { selectAudio } from './selectors';
|
||||
|
||||
export const useAudio = context => {
|
||||
export const useAudio = (context) => {
|
||||
const state = useSelector(context, selectAudio);
|
||||
const dispatch = useDispatch(context);
|
||||
return {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { AudioPlayer } from './player';
|
||||
|
||||
export const audioMiddleware = store => {
|
||||
export const audioMiddleware = (store) => {
|
||||
const player = new AudioPlayer();
|
||||
player.onPlay(() => {
|
||||
store.dispatch({ type: 'audio/playing' });
|
||||
@@ -14,7 +14,7 @@ export const audioMiddleware = store => {
|
||||
player.onStop(() => {
|
||||
store.dispatch({ type: 'audio/stopped' });
|
||||
});
|
||||
return next => action => {
|
||||
return (next) => (action) => {
|
||||
const { type, payload } = action;
|
||||
if (type === 'audio/playMusic') {
|
||||
const { url, ...options } = payload;
|
||||
|
||||
@@ -42,7 +42,7 @@ export class AudioPlayer {
|
||||
this.stop();
|
||||
});
|
||||
// Listen for playback errors
|
||||
this.node.addEventListener('error', e => {
|
||||
this.node.addEventListener('error', (e) => {
|
||||
if (this.playing) {
|
||||
logger.log('playback error', e.error);
|
||||
this.stop();
|
||||
@@ -53,8 +53,8 @@ export class AudioPlayer {
|
||||
if (!this.playing) {
|
||||
return;
|
||||
}
|
||||
const shouldStop = this.options.end > 0
|
||||
&& this.node.currentTime >= this.options.end;
|
||||
const shouldStop =
|
||||
this.options.end > 0 && this.node.currentTime >= this.options.end;
|
||||
if (shouldStop) {
|
||||
this.stop();
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export const selectAudio = state => state.audio;
|
||||
export const selectAudio = (state) => state.audio;
|
||||
|
||||
@@ -20,54 +20,68 @@ export const ChatPageSettings = (props, context) => {
|
||||
<Input
|
||||
fluid
|
||||
value={page.name}
|
||||
onChange={(e, value) => dispatch(updateChatPage({
|
||||
pageId: page.id,
|
||||
name: value,
|
||||
}))} />
|
||||
onChange={(e, value) =>
|
||||
dispatch(
|
||||
updateChatPage({
|
||||
pageId: page.id,
|
||||
name: value,
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="times"
|
||||
color="red"
|
||||
onClick={() => dispatch(removeChatPage({
|
||||
pageId: page.id,
|
||||
}))}>
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
removeChatPage({
|
||||
pageId: page.id,
|
||||
})
|
||||
)
|
||||
}>
|
||||
Remove
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Divider />
|
||||
<Section title="Messages to display" level={2}>
|
||||
{MESSAGE_TYPES
|
||||
.filter(typeDef => !typeDef.important && !typeDef.admin)
|
||||
.map(typeDef => (
|
||||
{MESSAGE_TYPES.filter(
|
||||
(typeDef) => !typeDef.important && !typeDef.admin
|
||||
).map((typeDef) => (
|
||||
<Button.Checkbox
|
||||
key={typeDef.type}
|
||||
checked={page.acceptedTypes[typeDef.type]}
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
toggleAcceptedType({
|
||||
pageId: page.id,
|
||||
type: typeDef.type,
|
||||
})
|
||||
)
|
||||
}>
|
||||
{typeDef.name}
|
||||
</Button.Checkbox>
|
||||
))}
|
||||
<Collapsible mt={1} color="transparent" title="Admin stuff">
|
||||
{MESSAGE_TYPES.filter(
|
||||
(typeDef) => !typeDef.important && typeDef.admin
|
||||
).map((typeDef) => (
|
||||
<Button.Checkbox
|
||||
key={typeDef.type}
|
||||
checked={page.acceptedTypes[typeDef.type]}
|
||||
onClick={() => dispatch(toggleAcceptedType({
|
||||
pageId: page.id,
|
||||
type: typeDef.type,
|
||||
}))}>
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
toggleAcceptedType({
|
||||
pageId: page.id,
|
||||
type: typeDef.type,
|
||||
})
|
||||
)
|
||||
}>
|
||||
{typeDef.name}
|
||||
</Button.Checkbox>
|
||||
))}
|
||||
<Collapsible
|
||||
mt={1}
|
||||
color="transparent"
|
||||
title="Admin stuff">
|
||||
{MESSAGE_TYPES
|
||||
.filter(typeDef => !typeDef.important && typeDef.admin)
|
||||
.map(typeDef => (
|
||||
<Button.Checkbox
|
||||
key={typeDef.type}
|
||||
checked={page.acceptedTypes[typeDef.type]}
|
||||
onClick={() => dispatch(toggleAcceptedType({
|
||||
pageId: page.id,
|
||||
type: typeDef.type,
|
||||
}))}>
|
||||
{typeDef.name}
|
||||
</Button.Checkbox>
|
||||
))}
|
||||
</Collapsible>
|
||||
</Section>
|
||||
</Section>
|
||||
|
||||
@@ -16,30 +16,34 @@ export class ChatPanel extends Component {
|
||||
this.state = {
|
||||
scrollTracking: true,
|
||||
};
|
||||
this.handleScrollTrackingChange = value => this.setState({
|
||||
scrollTracking: value,
|
||||
});
|
||||
this.handleScrollTrackingChange = (value) =>
|
||||
this.setState({
|
||||
scrollTracking: value,
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
chatRenderer.mount(this.ref.current);
|
||||
chatRenderer.events.on('scrollTrackingChanged',
|
||||
this.handleScrollTrackingChange);
|
||||
chatRenderer.events.on(
|
||||
'scrollTrackingChanged',
|
||||
this.handleScrollTrackingChange
|
||||
);
|
||||
this.componentDidUpdate();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
chatRenderer.events.off('scrollTrackingChanged',
|
||||
this.handleScrollTrackingChange);
|
||||
chatRenderer.events.off(
|
||||
'scrollTrackingChanged',
|
||||
this.handleScrollTrackingChange
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
requestAnimationFrame(() => {
|
||||
chatRenderer.ensureScrollTracking();
|
||||
});
|
||||
const shouldUpdateStyle = (
|
||||
!prevProps || shallowDiffers(this.props, prevProps)
|
||||
);
|
||||
const shouldUpdateStyle =
|
||||
!prevProps || shallowDiffers(this.props, prevProps);
|
||||
if (shouldUpdateStyle) {
|
||||
chatRenderer.assignStyle({
|
||||
'width': '100%',
|
||||
@@ -51,9 +55,7 @@ export class ChatPanel extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
scrollTracking,
|
||||
} = this.state;
|
||||
const { scrollTracking } = this.state;
|
||||
return (
|
||||
<>
|
||||
<div className="Chat" ref={this.ref} />
|
||||
|
||||
@@ -32,16 +32,22 @@ export const ChatTabs = (props, context) => {
|
||||
<Flex align="center">
|
||||
<Flex.Item>
|
||||
<Tabs textAlign="center">
|
||||
{pages.map(page => (
|
||||
{pages.map((page) => (
|
||||
<Tabs.Tab
|
||||
key={page.id}
|
||||
selected={page === currentPage}
|
||||
rightSlot={page.unreadCount > 0 && (
|
||||
<UnreadCountWidget value={page.unreadCount} />
|
||||
)}
|
||||
onClick={() => dispatch(changeChatPage({
|
||||
pageId: page.id,
|
||||
}))}>
|
||||
rightSlot={
|
||||
page.unreadCount > 0 && (
|
||||
<UnreadCountWidget value={page.unreadCount} />
|
||||
)
|
||||
}
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
changeChatPage({
|
||||
pageId: page.id,
|
||||
})
|
||||
)
|
||||
}>
|
||||
{page.name}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
@@ -54,7 +60,8 @@ export const ChatTabs = (props, context) => {
|
||||
onClick={() => {
|
||||
dispatch(addChatPage());
|
||||
dispatch(openChatSettings());
|
||||
}} />
|
||||
}}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -59,19 +59,22 @@ export const MESSAGE_TYPES = [
|
||||
type: MESSAGE_TYPE_RADIO,
|
||||
name: 'Radio',
|
||||
description: 'All departments of radio messages',
|
||||
selector: '.alert, .minorannounce, .syndradio, .centcomradio, .aiprivradio, .comradio, .secradio, .gangradio, .engradio, .medradio, .sciradio, .suppradio, .servradio, .radio, .deptradio, .binarysay, .newscaster, .resonate',
|
||||
selector:
|
||||
'.alert, .minorannounce, .syndradio, .centcomradio, .aiprivradio, .comradio, .secradio, .gangradio, .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, .announce, .hear, .smallnotice, .holoparasite, .boldnotice',
|
||||
selector:
|
||||
'.notice:not(.pm), .adminnotice, .info, .sinister, .cult, .infoplain, .announce, .hear, .smallnotice, .holoparasite, .boldnotice',
|
||||
},
|
||||
{
|
||||
type: MESSAGE_TYPE_WARNING,
|
||||
name: 'Warnings',
|
||||
description: 'Urgent messages from the game and items',
|
||||
selector: '.warning:not(.pm), .critical, .userdanger, .italics, .alertsyndie, .warningplain',
|
||||
selector:
|
||||
'.warning:not(.pm), .critical, .userdanger, .italics, .alertsyndie, .warningplain',
|
||||
},
|
||||
{
|
||||
type: MESSAGE_TYPE_DEADCHAT,
|
||||
|
||||
@@ -15,25 +15,22 @@ import { chatRenderer } from './renderer';
|
||||
import { selectChat, selectCurrentChatPage } from './selectors';
|
||||
|
||||
// List of blacklisted tags
|
||||
const FORBID_TAGS = [
|
||||
'a',
|
||||
'iframe',
|
||||
'link',
|
||||
'video',
|
||||
];
|
||||
const FORBID_TAGS = ['a', 'iframe', 'link', 'video'];
|
||||
|
||||
const saveChatToStorage = async store => {
|
||||
const saveChatToStorage = async (store) => {
|
||||
const state = selectChat(store.getState());
|
||||
const fromIndex = Math.max(0,
|
||||
chatRenderer.messages.length - MAX_PERSISTED_MESSAGES);
|
||||
const fromIndex = Math.max(
|
||||
0,
|
||||
chatRenderer.messages.length - MAX_PERSISTED_MESSAGES
|
||||
);
|
||||
const messages = chatRenderer.messages
|
||||
.slice(fromIndex)
|
||||
.map(message => serializeMessage(message));
|
||||
.map((message) => serializeMessage(message));
|
||||
storage.set('chat-state', state);
|
||||
storage.set('chat-messages', messages);
|
||||
};
|
||||
|
||||
const loadChatFromStorage = async store => {
|
||||
const loadChatFromStorage = async (store) => {
|
||||
const [state, messages] = await Promise.all([
|
||||
storage.get('chat-state'),
|
||||
storage.get('chat-messages'),
|
||||
@@ -64,10 +61,10 @@ const loadChatFromStorage = async store => {
|
||||
store.dispatch(loadChat(state));
|
||||
};
|
||||
|
||||
export const chatMiddleware = store => {
|
||||
export const chatMiddleware = (store) => {
|
||||
let initialized = false;
|
||||
let loaded = false;
|
||||
chatRenderer.events.on('batchProcessed', countByType => {
|
||||
chatRenderer.events.on('batchProcessed', (countByType) => {
|
||||
// Use this flag to workaround unread messages caused by
|
||||
// loading them from storage. Side effect of that, is that
|
||||
// message count can not be trusted, only unread count.
|
||||
@@ -75,11 +72,13 @@ export const chatMiddleware = store => {
|
||||
store.dispatch(updateMessageCount(countByType));
|
||||
}
|
||||
});
|
||||
chatRenderer.events.on('scrollTrackingChanged', scrollTracking => {
|
||||
chatRenderer.events.on('scrollTrackingChanged', (scrollTracking) => {
|
||||
store.dispatch(changeScrollTracking(scrollTracking));
|
||||
});
|
||||
setInterval(() => saveChatToStorage(store), MESSAGE_SAVE_INTERVAL);
|
||||
return next => action => {
|
||||
setInterval(() => {
|
||||
saveChatToStorage(store);
|
||||
}, MESSAGE_SAVE_INTERVAL);
|
||||
return (next) => (action) => {
|
||||
const { type, payload } = action;
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
@@ -99,10 +98,12 @@ export const chatMiddleware = store => {
|
||||
loaded = true;
|
||||
return;
|
||||
}
|
||||
if (type === changeChatPage.type
|
||||
|| type === addChatPage.type
|
||||
|| type === removeChatPage.type
|
||||
|| type === toggleAcceptedType.type) {
|
||||
if (
|
||||
type === changeChatPage.type ||
|
||||
type === addChatPage.type ||
|
||||
type === removeChatPage.type ||
|
||||
type === toggleAcceptedType.type
|
||||
) {
|
||||
next(action);
|
||||
const page = selectCurrentChatPage(store.getState());
|
||||
chatRenderer.changePage(page);
|
||||
@@ -119,7 +120,8 @@ export const chatMiddleware = store => {
|
||||
settings.highlightText,
|
||||
settings.highlightColor,
|
||||
settings.matchWord,
|
||||
settings.matchCase);
|
||||
settings.matchCase
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (type === 'roundrestart') {
|
||||
|
||||
@@ -7,11 +7,10 @@
|
||||
import { createUuid } from 'common/uuid';
|
||||
import { MESSAGE_TYPES, MESSAGE_TYPE_INTERNAL } from './constants';
|
||||
|
||||
export const canPageAcceptType = (page, type) => (
|
||||
type.startsWith(MESSAGE_TYPE_INTERNAL) || page.acceptedTypes[type]
|
||||
);
|
||||
export const canPageAcceptType = (page, type) =>
|
||||
type.startsWith(MESSAGE_TYPE_INTERNAL) || page.acceptedTypes[type];
|
||||
|
||||
export const createPage = obj => {
|
||||
export const createPage = (obj) => {
|
||||
let acceptedTypes = {};
|
||||
|
||||
for (let typeDef of MESSAGE_TYPES) {
|
||||
@@ -39,12 +38,12 @@ export const createMainPage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
export const createMessage = payload => ({
|
||||
export const createMessage = (payload) => ({
|
||||
createdAt: Date.now(),
|
||||
...payload,
|
||||
});
|
||||
|
||||
export const serializeMessage = message => ({
|
||||
export const serializeMessage = (message) => ({
|
||||
type: message.type,
|
||||
text: message.text,
|
||||
html: message.html,
|
||||
@@ -52,7 +51,6 @@ export const serializeMessage = message => ({
|
||||
createdAt: message.createdAt,
|
||||
});
|
||||
|
||||
export const isSameMessage = (a, b) => (
|
||||
typeof a.text === 'string' && a.text === b.text
|
||||
|| typeof a.html === 'string' && a.html === b.html
|
||||
);
|
||||
export const isSameMessage = (a, b) =>
|
||||
(typeof a.text === 'string' && a.text === b.text) ||
|
||||
(typeof a.html === 'string' && a.html === b.html);
|
||||
|
||||
@@ -13,9 +13,7 @@ export const initialState = {
|
||||
version: 5,
|
||||
currentPageId: mainPage.id,
|
||||
scrollTracking: true,
|
||||
pages: [
|
||||
mainPage.id,
|
||||
],
|
||||
pages: [mainPage.id],
|
||||
pageById: {
|
||||
[mainPage.id]: mainPage,
|
||||
},
|
||||
@@ -74,7 +72,7 @@ export const chatReducer = (state = initialState, action) => {
|
||||
}
|
||||
if (type === updateMessageCount.type) {
|
||||
const countByType = payload;
|
||||
const pages = state.pages.map(id => state.pageById[id]);
|
||||
const pages = state.pages.map((id) => state.pageById[id]);
|
||||
const currentPage = state.pageById[state.currentPageId];
|
||||
const nextPageById = { ...state.pageById };
|
||||
for (let page of pages) {
|
||||
@@ -170,7 +168,7 @@ export const chatReducer = (state = initialState, action) => {
|
||||
},
|
||||
};
|
||||
delete nextState.pageById[pageId];
|
||||
nextState.pages = nextState.pages.filter(id => id !== pageId);
|
||||
nextState.pages = nextState.pages.filter((id) => id !== pageId);
|
||||
if (nextState.pages.length === 0) {
|
||||
nextState.pages.push(mainPage.id);
|
||||
nextState.pageById[mainPage.id] = mainPage;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { COMBINE_MAX_MESSAGES, COMBINE_MAX_TIME_WINDOW, IMAGE_RETRY_DELAY, IMAGE
|
||||
import { render } from 'inferno';
|
||||
import { canPageAcceptType, createMessage, isSameMessage } from './model';
|
||||
import { highlightNode, linkifyNode } from './replaceInTextNode';
|
||||
import { Tooltip } from "../../tgui/components";
|
||||
import { Tooltip } from '../../tgui/components';
|
||||
|
||||
const logger = createLogger('chatRenderer');
|
||||
|
||||
@@ -27,11 +27,11 @@ export const TGUI_CHAT_COMPONENTS = {
|
||||
// List of injectable attibute names mapped to their proper prop
|
||||
// We need this because attibutes don't support lowercase names
|
||||
export const TGUI_CHAT_ATTRIBUTES_TO_PROPS = {
|
||||
"position": "position",
|
||||
"content": "content",
|
||||
'position': 'position',
|
||||
'content': 'content',
|
||||
};
|
||||
|
||||
const findNearestScrollableParent = startingNode => {
|
||||
const findNearestScrollableParent = (startingNode) => {
|
||||
const body = document.body;
|
||||
let node = startingNode;
|
||||
while (node && node !== body) {
|
||||
@@ -66,7 +66,7 @@ const createReconnectedNode = () => {
|
||||
return node;
|
||||
};
|
||||
|
||||
const handleImageError = e => {
|
||||
const handleImageError = (e) => {
|
||||
setTimeout(() => {
|
||||
/** @type {HTMLImageElement} */
|
||||
const node = e.target;
|
||||
@@ -85,7 +85,7 @@ const handleImageError = e => {
|
||||
/**
|
||||
* Assigns a "times-repeated" badge to the message.
|
||||
*/
|
||||
const updateMessageBadge = message => {
|
||||
const updateMessageBadge = (message) => {
|
||||
const { node, times } = message;
|
||||
if (!node || !times) {
|
||||
// Nothing to update
|
||||
@@ -94,10 +94,7 @@ const updateMessageBadge = message => {
|
||||
const foundBadge = node.querySelector('.Chat__badge');
|
||||
const badge = foundBadge || document.createElement('div');
|
||||
badge.textContent = times;
|
||||
badge.className = classes([
|
||||
'Chat__badge',
|
||||
'Chat__badge--animate',
|
||||
]);
|
||||
badge.className = classes(['Chat__badge', 'Chat__badge--animate']);
|
||||
requestAnimationFrame(() => {
|
||||
badge.className = 'Chat__badge';
|
||||
});
|
||||
@@ -121,13 +118,12 @@ class ChatRenderer {
|
||||
/** @type {HTMLElement} */
|
||||
this.scrollNode = null;
|
||||
this.scrollTracking = true;
|
||||
this.handleScroll = type => {
|
||||
this.handleScroll = (type) => {
|
||||
const node = this.scrollNode;
|
||||
const height = node.scrollHeight;
|
||||
const bottom = node.scrollTop + node.offsetHeight;
|
||||
const scrollTracking = (
|
||||
Math.abs(height - bottom) < SCROLL_TRACKING_TOLERANCE
|
||||
);
|
||||
const scrollTracking =
|
||||
Math.abs(height - bottom) < SCROLL_TRACKING_TOLERANCE;
|
||||
if (scrollTracking !== this.scrollTracking) {
|
||||
this.scrollTracking = scrollTracking;
|
||||
this.events.emit('scrollTrackingChanged', scrollTracking);
|
||||
@@ -193,18 +189,18 @@ class ChatRenderer {
|
||||
const lines = String(text)
|
||||
.split(',')
|
||||
// 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
|
||||
));
|
||||
.map((str) => str.trim().replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'))
|
||||
// Must be longer than one character
|
||||
.filter((str) => str && str.length > 1);
|
||||
// Nothing to match, reset highlighting
|
||||
if (lines.length === 0) {
|
||||
this.highlightRegex = null;
|
||||
this.highlightColor = null;
|
||||
return;
|
||||
}
|
||||
const pattern = `${(matchWord ? '\\b' : '')}(${lines.join('|')})${(matchWord ? '\\b' : '')}`;
|
||||
const pattern = `${matchWord ? '\\b' : ''}(${lines.join('|')})${
|
||||
matchWord ? '\\b' : ''
|
||||
}`;
|
||||
const flags = 'g' + (matchCase ? '' : 'i');
|
||||
this.highlightRegex = new RegExp(pattern, flags);
|
||||
this.highlightColor = color;
|
||||
@@ -249,6 +245,7 @@ class ChatRenderer {
|
||||
const to = Math.max(0, len - COMBINE_MAX_MESSAGES);
|
||||
for (let i = from; i >= to; i--) {
|
||||
const message = this.visibleMessages[i];
|
||||
// prettier-ignore
|
||||
const matches = (
|
||||
// Is not an internal message
|
||||
!message.type.startsWith(MESSAGE_TYPE_INTERNAL)
|
||||
@@ -265,17 +262,13 @@ class ChatRenderer {
|
||||
}
|
||||
|
||||
processBatch(batch, options = {}) {
|
||||
const {
|
||||
prepend,
|
||||
notifyListeners = true,
|
||||
} = options;
|
||||
const { prepend, notifyListeners = true } = options;
|
||||
const now = Date.now();
|
||||
// Queue up messages until chat is ready
|
||||
if (!this.isReady()) {
|
||||
if (prepend) {
|
||||
this.queue = [...batch, ...this.queue];
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.queue = [...this.queue, ...batch];
|
||||
}
|
||||
return;
|
||||
@@ -311,15 +304,14 @@ class ChatRenderer {
|
||||
// Payload is HTML
|
||||
else if (message.html) {
|
||||
node.innerHTML = message.html;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
logger.error('Error: message is missing text payload', message);
|
||||
}
|
||||
// Get all nodes in this message that want to be rendered like jsx
|
||||
const nodes = node.querySelectorAll("[data-component]");
|
||||
const nodes = node.querySelectorAll('[data-component]');
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const childNode = nodes[i];
|
||||
const targetName = childNode.getAttribute("data-component");
|
||||
const targetName = childNode.getAttribute('data-component');
|
||||
// Let's pull out the attibute info we need
|
||||
let outputProps = {};
|
||||
for (let j = 0; j < childNode.attributes.length; j++) {
|
||||
@@ -328,20 +320,18 @@ class ChatRenderer {
|
||||
let working_value = attribute.nodeValue;
|
||||
// We can't do the "if it has no value it's truthy" trick
|
||||
// Because getAttribute returns "", not null. Hate IE
|
||||
if (working_value === "$true") {
|
||||
if (working_value === '$true') {
|
||||
working_value = true;
|
||||
}
|
||||
else if (working_value === "$false") {
|
||||
} else if (working_value === '$false') {
|
||||
working_value = false;
|
||||
}
|
||||
else if (!isNaN(working_value)) {
|
||||
} else if (!isNaN(working_value)) {
|
||||
const parsed_float = parseFloat(working_value);
|
||||
if (!isNaN(parsed_float)) {
|
||||
working_value = parsed_float;
|
||||
}
|
||||
}
|
||||
|
||||
let canon_name = attribute.nodeName.replace("data-", "");
|
||||
let canon_name = attribute.nodeName.replace('data-', '');
|
||||
// html attributes don't support upper case chars, so we need to map
|
||||
canon_name = TGUI_CHAT_ATTRIBUTES_TO_PROPS[canon_name];
|
||||
outputProps[canon_name] = working_value;
|
||||
@@ -355,18 +345,17 @@ class ChatRenderer {
|
||||
render(
|
||||
<Element {...outputProps}>
|
||||
<span dangerouslySetInnerHTML={oldHtml} />
|
||||
</Element>, childNode);
|
||||
</Element>,
|
||||
childNode
|
||||
);
|
||||
/* eslint-enable react/no-danger */
|
||||
|
||||
}
|
||||
|
||||
// Highlight text
|
||||
if (!message.avoidHighlighting && this.highlightRegex) {
|
||||
const highlighted = highlightNode(node,
|
||||
this.highlightRegex,
|
||||
text => (
|
||||
createHighlightNode(text, this.highlightColor)
|
||||
));
|
||||
const highlighted = highlightNode(node, this.highlightRegex, (text) =>
|
||||
createHighlightNode(text, this.highlightColor)
|
||||
);
|
||||
if (highlighted) {
|
||||
node.className += ' ChatMessage--highlighted';
|
||||
}
|
||||
@@ -391,6 +380,7 @@ class ChatRenderer {
|
||||
if (!message.type) {
|
||||
// IE8: Does not support querySelector on elements that
|
||||
// are not yet in the document.
|
||||
// prettier-ignore
|
||||
const typeDef = !Byond.IS_LTE_IE8 && MESSAGE_TYPES
|
||||
.find(typeDef => (
|
||||
typeDef.selector && node.querySelector(typeDef.selector)
|
||||
@@ -413,8 +403,7 @@ class ChatRenderer {
|
||||
const firstChild = this.rootNode.childNodes[0];
|
||||
if (prepend && firstChild) {
|
||||
this.rootNode.insertBefore(fragment, firstChild);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.rootNode.appendChild(fragment);
|
||||
}
|
||||
if (this.scrollTracking) {
|
||||
@@ -440,8 +429,7 @@ class ChatRenderer {
|
||||
// Visible messages
|
||||
{
|
||||
const messages = this.visibleMessages;
|
||||
const fromIndex = Math.max(0,
|
||||
messages.length - MAX_VISIBLE_MESSAGES);
|
||||
const fromIndex = Math.max(0, messages.length - MAX_VISIBLE_MESSAGES);
|
||||
if (fromIndex > 0) {
|
||||
this.visibleMessages = messages.slice(fromIndex);
|
||||
for (let i = 0; i < fromIndex; i++) {
|
||||
@@ -451,6 +439,7 @@ class ChatRenderer {
|
||||
message.node = 'pruned';
|
||||
}
|
||||
// Remove pruned messages from the message array
|
||||
// prettier-ignore
|
||||
this.messages = this.messages.filter(message => (
|
||||
message.node !== 'pruned'
|
||||
));
|
||||
@@ -459,8 +448,10 @@ class ChatRenderer {
|
||||
}
|
||||
// All messages
|
||||
{
|
||||
const fromIndex = Math.max(0,
|
||||
this.messages.length - MAX_PERSISTED_MESSAGES);
|
||||
const fromIndex = Math.max(
|
||||
0,
|
||||
this.messages.length - MAX_PERSISTED_MESSAGES
|
||||
);
|
||||
if (fromIndex > 0) {
|
||||
this.messages = this.messages.slice(fromIndex);
|
||||
logger.log(`pruned ${fromIndex} stored messages`);
|
||||
@@ -473,8 +464,10 @@ class ChatRenderer {
|
||||
return;
|
||||
}
|
||||
// Make a copy of messages
|
||||
const fromIndex = Math.max(0,
|
||||
this.messages.length - MAX_PERSISTED_MESSAGES);
|
||||
const fromIndex = Math.max(
|
||||
0,
|
||||
this.messages.length - MAX_PERSISTED_MESSAGES
|
||||
);
|
||||
const messages = this.messages.slice(fromIndex);
|
||||
// Remove existing nodes
|
||||
for (let message of messages) {
|
||||
@@ -514,6 +507,7 @@ class ChatRenderer {
|
||||
}
|
||||
}
|
||||
// Create a page
|
||||
// prettier-ignore
|
||||
const pageHtml = '<!doctype html>\n'
|
||||
+ '<html>\n'
|
||||
+ '<head>\n'
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
/**
|
||||
* Replaces text matching a regular expression with a custom node.
|
||||
*/
|
||||
export const replaceInTextNode = (regex, createNode) => node => {
|
||||
export const replaceInTextNode = (regex, createNode) => (node) => {
|
||||
const text = node.textContent;
|
||||
const textLength = text.length;
|
||||
let match;
|
||||
@@ -15,7 +15,7 @@ export const replaceInTextNode = (regex, createNode) => node => {
|
||||
let fragment;
|
||||
let n = 0;
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (match = regex.exec(text)) {
|
||||
while ((match = regex.exec(text))) {
|
||||
n += 1;
|
||||
// Lazy init fragment
|
||||
if (!fragment) {
|
||||
@@ -26,8 +26,9 @@ export const replaceInTextNode = (regex, createNode) => node => {
|
||||
const matchIndex = match.index;
|
||||
// Insert previous unmatched chunk
|
||||
if (lastIndex < matchIndex) {
|
||||
fragment.appendChild(document.createTextNode(
|
||||
text.substring(lastIndex, matchIndex)));
|
||||
fragment.appendChild(
|
||||
document.createTextNode(text.substring(lastIndex, matchIndex))
|
||||
);
|
||||
}
|
||||
lastIndex = matchIndex + matchLength;
|
||||
// Create a wrapper node
|
||||
@@ -36,8 +37,9 @@ export const replaceInTextNode = (regex, createNode) => node => {
|
||||
if (fragment) {
|
||||
// Insert the remaining unmatched chunk
|
||||
if (lastIndex < textLength) {
|
||||
fragment.appendChild(document.createTextNode(
|
||||
text.substring(lastIndex, textLength)));
|
||||
fragment.appendChild(
|
||||
document.createTextNode(text.substring(lastIndex, textLength))
|
||||
);
|
||||
}
|
||||
// Commit the fragment
|
||||
node.parentNode.replaceChild(fragment, node);
|
||||
@@ -45,17 +47,15 @@ export const replaceInTextNode = (regex, createNode) => node => {
|
||||
return n;
|
||||
};
|
||||
|
||||
|
||||
// Highlight
|
||||
// --------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Default highlight node.
|
||||
*/
|
||||
const createHighlightNode = text => {
|
||||
const createHighlightNode = (text) => {
|
||||
const node = document.createElement('span');
|
||||
node.setAttribute('style',
|
||||
'background-color:#fd4;color:#000');
|
||||
node.setAttribute('style', 'background-color:#fd4;color:#000');
|
||||
node.textContent = text;
|
||||
return node;
|
||||
};
|
||||
@@ -71,7 +71,7 @@ const createHighlightNode = text => {
|
||||
export const highlightNode = (
|
||||
node,
|
||||
regex,
|
||||
createNode = createHighlightNode,
|
||||
createNode = createHighlightNode
|
||||
) => {
|
||||
if (!createNode) {
|
||||
createNode = createHighlightNode;
|
||||
@@ -83,18 +83,17 @@ export const highlightNode = (
|
||||
// Is a text node
|
||||
if (node.nodeType === 3) {
|
||||
n += replaceInTextNode(regex, createNode)(node);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
n += highlightNode(node, regex, createNode);
|
||||
}
|
||||
}
|
||||
return n;
|
||||
};
|
||||
|
||||
|
||||
// Linkify
|
||||
// --------------------------------------------------------
|
||||
|
||||
// prettier-ignore
|
||||
const URL_REGEX = /(?:(?:https?:\/\/)|(?:www\.))(?:[^ ]*?\.[^ ]*?)+[-A-Za-z0-9+&@#/%?=~_|$!:,.;()]+/ig;
|
||||
|
||||
/**
|
||||
@@ -103,7 +102,7 @@ const URL_REGEX = /(?:(?:https?:\/\/)|(?:www\.))(?:[^ ]*?\.[^ ]*?)+[-A-Za-z0-9+&
|
||||
* @param {Node} node Node which you want to process
|
||||
* @returns {number} Number of matches
|
||||
*/
|
||||
export const linkifyNode = node => {
|
||||
export const linkifyNode = (node) => {
|
||||
let n = 0;
|
||||
const childNodes = node.childNodes;
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
@@ -112,15 +111,14 @@ export const linkifyNode = node => {
|
||||
// Is a text node
|
||||
if (node.nodeType === 3) {
|
||||
n += linkifyTextNode(node);
|
||||
}
|
||||
else if (tag !== 'a') {
|
||||
} else if (tag !== 'a') {
|
||||
n += linkifyNode(node);
|
||||
}
|
||||
}
|
||||
return n;
|
||||
};
|
||||
|
||||
const linkifyTextNode = replaceInTextNode(URL_REGEX, text => {
|
||||
const linkifyTextNode = replaceInTextNode(URL_REGEX, (text) => {
|
||||
const node = document.createElement('a');
|
||||
node.href = text;
|
||||
node.textContent = text;
|
||||
|
||||
@@ -6,16 +6,12 @@
|
||||
|
||||
import { map } from 'common/collections';
|
||||
|
||||
export const selectChat = state => state.chat;
|
||||
export const selectChat = (state) => state.chat;
|
||||
|
||||
export const selectChatPages = state => (
|
||||
map(id => state.chat.pageById[id])(state.chat.pages)
|
||||
);
|
||||
export const selectChatPages = (state) =>
|
||||
map((id) => state.chat.pageById[id])(state.chat.pages);
|
||||
|
||||
export const selectCurrentChatPage = state => (
|
||||
state.chat.pageById[state.chat.currentPageId]
|
||||
);
|
||||
export const selectCurrentChatPage = (state) =>
|
||||
state.chat.pageById[state.chat.currentPageId];
|
||||
|
||||
export const selectChatPageById = id => state => (
|
||||
state.chat.pageById[id]
|
||||
);
|
||||
export const selectChatPageById = (id) => (state) => state.chat.pageById[id];
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
import { useSelector } from 'common/redux';
|
||||
import { selectGame } from './selectors';
|
||||
|
||||
export const useGame = context => {
|
||||
export const useGame = (context) => {
|
||||
return useSelector(context, selectGame);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import { connectionLost, connectionRestored, roundRestarted } from './actions';
|
||||
import { selectGame } from './selectors';
|
||||
import { CONNECTION_LOST_AFTER } from './constants';
|
||||
|
||||
const withTimestamp = action => ({
|
||||
const withTimestamp = (action) => ({
|
||||
...action,
|
||||
meta: {
|
||||
...action.meta,
|
||||
@@ -17,7 +17,7 @@ const withTimestamp = action => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const gameMiddleware = store => {
|
||||
export const gameMiddleware = (store) => {
|
||||
let lastPingedAt;
|
||||
|
||||
setInterval(() => {
|
||||
@@ -26,8 +26,8 @@ export const gameMiddleware = store => {
|
||||
return;
|
||||
}
|
||||
const game = selectGame(state);
|
||||
const pingsAreFailing = lastPingedAt
|
||||
&& Date.now() >= lastPingedAt + CONNECTION_LOST_AFTER;
|
||||
const pingsAreFailing =
|
||||
lastPingedAt && Date.now() >= lastPingedAt + CONNECTION_LOST_AFTER;
|
||||
if (!game.connectionLostAt && pingsAreFailing) {
|
||||
store.dispatch(withTimestamp(connectionLost()));
|
||||
}
|
||||
@@ -36,7 +36,7 @@ export const gameMiddleware = store => {
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return next => action => {
|
||||
return (next) => (action) => {
|
||||
const { type } = action;
|
||||
|
||||
if (type === pingSuccess.type || type === pingSoft.type) {
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export const selectGame = state => state.game;
|
||||
export const selectGame = (state) => state.game;
|
||||
|
||||
@@ -86,7 +86,7 @@ const setupApp = () => {
|
||||
});
|
||||
|
||||
// Resize the panel to match the non-browser output
|
||||
Byond.winget('output').then(output => {
|
||||
Byond.winget('output').then((output) => {
|
||||
Byond.winset('browseroutput', {
|
||||
'size': output.size,
|
||||
});
|
||||
@@ -95,6 +95,7 @@ const setupApp = () => {
|
||||
// Enable hot module reloading
|
||||
if (module.hot) {
|
||||
setupHotReloading();
|
||||
// prettier-ignore
|
||||
module.hot.accept([
|
||||
'./audio',
|
||||
'./chat',
|
||||
|
||||
@@ -20,13 +20,13 @@ const deferredFocusMap = () => setImmediate(() => focusMap());
|
||||
export const setupPanelFocusHacks = () => {
|
||||
let focusStolen = false;
|
||||
let clickStartPos = null;
|
||||
window.addEventListener('focusin', e => {
|
||||
window.addEventListener('focusin', (e) => {
|
||||
focusStolen = canStealFocus(e.target);
|
||||
});
|
||||
window.addEventListener('mousedown', e => {
|
||||
window.addEventListener('mousedown', (e) => {
|
||||
clickStartPos = [e.screenX, e.screenY];
|
||||
});
|
||||
window.addEventListener('mouseup', e => {
|
||||
window.addEventListener('mouseup', (e) => {
|
||||
if (clickStartPos) {
|
||||
const clickEndPos = [e.screenX, e.screenY];
|
||||
const dist = vecLength(vecSubtract(clickEndPos, clickStartPos));
|
||||
@@ -38,7 +38,7 @@ export const setupPanelFocusHacks = () => {
|
||||
deferredFocusMap();
|
||||
}
|
||||
});
|
||||
globalEvents.on('keydown', key => {
|
||||
globalEvents.on('keydown', (key) => {
|
||||
if (key.isModifierKey()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -17,14 +17,10 @@ export const PingIndicator = (props, context) => {
|
||||
new Color(220, 200, 40),
|
||||
new Color(60, 220, 40),
|
||||
]);
|
||||
const roundtrip = ping.roundtrip
|
||||
? toFixed(ping.roundtrip)
|
||||
: '--';
|
||||
const roundtrip = ping.roundtrip ? toFixed(ping.roundtrip) : '--';
|
||||
return (
|
||||
<div className="Ping">
|
||||
<Box
|
||||
className="Ping__indicator"
|
||||
backgroundColor={color} />
|
||||
<Box className="Ping__indicator" backgroundColor={color} />
|
||||
{roundtrip}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { pingFail, pingReply, pingSoft, pingSuccess } from './actions';
|
||||
import { PING_QUEUE_SIZE, PING_TIMEOUT } from './constants';
|
||||
|
||||
export const pingMiddleware = store => {
|
||||
export const pingMiddleware = (store) => {
|
||||
let initialized = false;
|
||||
let index = 0;
|
||||
const pings = [];
|
||||
@@ -26,7 +26,7 @@ export const pingMiddleware = store => {
|
||||
index = (index + 1) % PING_QUEUE_SIZE;
|
||||
};
|
||||
|
||||
return next => action => {
|
||||
return (next) => (action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
if (!initialized) {
|
||||
|
||||
@@ -15,8 +15,8 @@ export const pingReducer = (state = {}, action) => {
|
||||
const { roundtrip } = payload;
|
||||
const prevRoundtrip = state.roundtripAvg || roundtrip;
|
||||
const roundtripAvg = Math.round(prevRoundtrip * 0.4 + roundtrip * 0.6);
|
||||
const networkQuality = 1 - scale(roundtripAvg,
|
||||
PING_ROUNDTRIP_BEST, PING_ROUNDTRIP_WORST);
|
||||
const networkQuality =
|
||||
1 - scale(roundtripAvg, PING_ROUNDTRIP_BEST, PING_ROUNDTRIP_WORST);
|
||||
return {
|
||||
roundtrip,
|
||||
roundtripAvg,
|
||||
@@ -27,8 +27,9 @@ export const pingReducer = (state = {}, action) => {
|
||||
|
||||
if (type === pingFail.type) {
|
||||
const { failCount = 0 } = state;
|
||||
const networkQuality = clamp01(state.networkQuality
|
||||
- failCount / PING_MAX_FAILS);
|
||||
const networkQuality = clamp01(
|
||||
state.networkQuality - failCount / PING_MAX_FAILS
|
||||
);
|
||||
const nextState = {
|
||||
...state,
|
||||
failCount: failCount + 1,
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export const selectPing = state => state.ping;
|
||||
export const selectPing = (state) => state.ping;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button } from 'tgui/components';
|
||||
let url: string | null = null;
|
||||
|
||||
setInterval(() => {
|
||||
Byond.winget('', 'url').then(currentUrl => {
|
||||
Byond.winget('', 'url').then((currentUrl) => {
|
||||
// Sometimes, for whatever reason, BYOND will give an IP with a :0 port.
|
||||
if (currentUrl && !currentUrl.match(/:0$/)) {
|
||||
url = currentUrl;
|
||||
@@ -11,19 +11,25 @@ setInterval(() => {
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
export const ReconnectButton = (props, context) => {
|
||||
return url && (
|
||||
export const ReconnectButton = () => {
|
||||
if (!url) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Button color="white" onClick={() => {
|
||||
Byond.command('.reconnect');
|
||||
}}>
|
||||
<Button
|
||||
color="white"
|
||||
onClick={() => {
|
||||
Byond.command('.reconnect');
|
||||
}}>
|
||||
Reconnect
|
||||
</Button>
|
||||
|
||||
<Button color="white" onClick={() => {
|
||||
location.href = `byond://${url}`;
|
||||
Byond.command('.quit');
|
||||
}}>
|
||||
<Button
|
||||
color="white"
|
||||
onClick={() => {
|
||||
location.href = `byond://${url}`;
|
||||
Byond.command('.quit');
|
||||
}}>
|
||||
Relaunch game
|
||||
</Button>
|
||||
</>
|
||||
|
||||
@@ -23,13 +23,17 @@ export const SettingsPanel = (props, context) => {
|
||||
<Stack.Item>
|
||||
<Section fitted fill minHeight="8em">
|
||||
<Tabs vertical>
|
||||
{SETTINGS_TABS.map(tab => (
|
||||
{SETTINGS_TABS.map((tab) => (
|
||||
<Tabs.Tab
|
||||
key={tab.id}
|
||||
selected={tab.id === activeTab}
|
||||
onClick={() => dispatch(changeSettingsTab({
|
||||
tabId: tab.id,
|
||||
}))}>
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
changeSettingsTab({
|
||||
tabId: tab.id,
|
||||
})
|
||||
)
|
||||
}>
|
||||
{tab.name}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
@@ -37,12 +41,8 @@ export const SettingsPanel = (props, context) => {
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={1} basis={0}>
|
||||
{activeTab === 'general' && (
|
||||
<SettingsGeneral />
|
||||
)}
|
||||
{activeTab === 'chatPage' && (
|
||||
<ChatPageSettings />
|
||||
)}
|
||||
{activeTab === 'general' && <SettingsGeneral />}
|
||||
{activeTab === 'chatPage' && <ChatPageSettings />}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
@@ -60,7 +60,7 @@ export const SettingsGeneral = (props, context) => {
|
||||
matchCase,
|
||||
} = useSelector(context, selectSettings);
|
||||
const dispatch = useDispatch(context);
|
||||
const [freeFont, setFreeFont] = useLocalState(context, "freeFont", false);
|
||||
const [freeFont, setFreeFont] = useLocalState(context, 'freeFont', false);
|
||||
return (
|
||||
<Section>
|
||||
<LabeledList>
|
||||
@@ -68,34 +68,48 @@ export const SettingsGeneral = (props, context) => {
|
||||
<Dropdown
|
||||
selected={theme}
|
||||
options={THEMES}
|
||||
onSelected={value => dispatch(updateSettings({
|
||||
theme: value,
|
||||
}))} />
|
||||
onSelected={(value) =>
|
||||
dispatch(
|
||||
updateSettings({
|
||||
theme: value,
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Font style">
|
||||
<Stack inline align="baseline">
|
||||
<Stack.Item>
|
||||
{!freeFont && (
|
||||
{(!freeFont && (
|
||||
<Dropdown
|
||||
selected={fontFamily}
|
||||
options={FONTS}
|
||||
onSelected={value => dispatch(updateSettings({
|
||||
fontFamily: value,
|
||||
}))} />
|
||||
) || (
|
||||
onSelected={(value) =>
|
||||
dispatch(
|
||||
updateSettings({
|
||||
fontFamily: value,
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
)) || (
|
||||
<Input
|
||||
value={fontFamily}
|
||||
onChange={(e, value) => dispatch(updateSettings({
|
||||
fontFamily: value,
|
||||
}))}
|
||||
onChange={(e, value) =>
|
||||
dispatch(
|
||||
updateSettings({
|
||||
fontFamily: value,
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
content="Custom font"
|
||||
icon={freeFont? "lock-open" : "lock"}
|
||||
color={freeFont? "good" : "bad"}
|
||||
icon={freeFont ? 'lock-open' : 'lock'}
|
||||
color={freeFont ? 'good' : 'bad'}
|
||||
ml={1}
|
||||
onClick={() => {
|
||||
setFreeFont(!freeFont);
|
||||
@@ -113,10 +127,15 @@ export const SettingsGeneral = (props, context) => {
|
||||
maxValue={32}
|
||||
value={fontSize}
|
||||
unit="px"
|
||||
format={value => toFixed(value)}
|
||||
onChange={(e, value) => dispatch(updateSettings({
|
||||
fontSize: value,
|
||||
}))} />
|
||||
format={(value) => toFixed(value)}
|
||||
onChange={(e, value) =>
|
||||
dispatch(
|
||||
updateSettings({
|
||||
fontSize: value,
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Line height">
|
||||
<NumberInput
|
||||
@@ -126,18 +145,21 @@ export const SettingsGeneral = (props, context) => {
|
||||
minValue={0.8}
|
||||
maxValue={5}
|
||||
value={lineHeight}
|
||||
format={value => toFixed(value, 2)}
|
||||
onDrag={(e, value) => dispatch(updateSettings({
|
||||
lineHeight: value,
|
||||
}))} />
|
||||
format={(value) => toFixed(value, 2)}
|
||||
onDrag={(e, value) =>
|
||||
dispatch(
|
||||
updateSettings({
|
||||
lineHeight: value,
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
<Divider />
|
||||
<Box>
|
||||
<Flex mb={1} color="label" align="baseline">
|
||||
<Flex.Item grow={1}>
|
||||
Highlight text (comma separated):
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={1}>Highlight text (comma separated):</Flex.Item>
|
||||
<Flex.Item shrink={0}>
|
||||
<ColorBox mr={1} color={highlightColor} />
|
||||
<Input
|
||||
@@ -145,39 +167,55 @@ export const SettingsGeneral = (props, context) => {
|
||||
monospace
|
||||
placeholder="#ffffff"
|
||||
value={highlightColor}
|
||||
onInput={(e, value) => dispatch(updateSettings({
|
||||
highlightColor: value,
|
||||
}))} />
|
||||
onInput={(e, value) =>
|
||||
dispatch(
|
||||
updateSettings({
|
||||
highlightColor: value,
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
<TextArea
|
||||
height="3em"
|
||||
value={highlightText}
|
||||
onChange={(e, value) => dispatch(updateSettings({
|
||||
highlightText: value,
|
||||
}))} />
|
||||
onChange={(e, value) =>
|
||||
dispatch(
|
||||
updateSettings({
|
||||
highlightText: value,
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Button.Checkbox
|
||||
checked={matchWord}
|
||||
tooltipPosition="bottom-start"
|
||||
tooltip="Not compatible with punctuation."
|
||||
onClick={() => dispatch(updateSettings({
|
||||
matchWord: !matchWord,
|
||||
}))}>
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
updateSettings({
|
||||
matchWord: !matchWord,
|
||||
})
|
||||
)
|
||||
}>
|
||||
Match word
|
||||
</Button.Checkbox>
|
||||
<Button.Checkbox
|
||||
checked={matchCase}
|
||||
onClick={() => dispatch(updateSettings({
|
||||
matchCase: !matchCase,
|
||||
}))}>
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
updateSettings({
|
||||
matchCase: !matchCase,
|
||||
})
|
||||
)
|
||||
}>
|
||||
Match case
|
||||
</Button.Checkbox>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box>
|
||||
<Button
|
||||
icon="check"
|
||||
onClick={() => dispatch(rebuildChat())}>
|
||||
<Button icon="check" onClick={() => dispatch(rebuildChat())}>
|
||||
Apply now
|
||||
</Button>
|
||||
<Box inline fontSize="0.9em" ml={1} color="label">
|
||||
@@ -185,9 +223,7 @@ export const SettingsGeneral = (props, context) => {
|
||||
</Box>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Button
|
||||
icon="save"
|
||||
onClick={() => dispatch(saveChatToDisk())}>
|
||||
<Button icon="save" onClick={() => dispatch(saveChatToDisk())}>
|
||||
Save chat log
|
||||
</Button>
|
||||
</Section>
|
||||
|
||||
@@ -15,8 +15,7 @@ export const SETTINGS_TABS = [
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
export const FONTS_DISABLED = "Default";
|
||||
export const FONTS_DISABLED = 'Default';
|
||||
|
||||
export const FONTS = [
|
||||
FONTS_DISABLED,
|
||||
|
||||
@@ -8,13 +8,13 @@ import { useDispatch, useSelector } from 'common/redux';
|
||||
import { updateSettings, toggleSettings } from './actions';
|
||||
import { selectSettings } from './selectors';
|
||||
|
||||
export const useSettings = context => {
|
||||
export const useSettings = (context) => {
|
||||
const settings = useSelector(context, selectSettings);
|
||||
const dispatch = useDispatch(context);
|
||||
return {
|
||||
...settings,
|
||||
visible: settings.view.visible,
|
||||
toggle: () => dispatch(toggleSettings()),
|
||||
update: obj => dispatch(updateSettings(obj)),
|
||||
update: (obj) => dispatch(updateSettings(obj)),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,29 +10,25 @@ import { loadSettings, updateSettings } from './actions';
|
||||
import { selectSettings } from './selectors';
|
||||
import { FONTS_DISABLED } from './constants';
|
||||
|
||||
const setGlobalFontSize = fontSize => {
|
||||
document.documentElement.style
|
||||
.setProperty('font-size', fontSize + 'px');
|
||||
document.body.style
|
||||
.setProperty('font-size', fontSize + 'px');
|
||||
const setGlobalFontSize = (fontSize) => {
|
||||
document.documentElement.style.setProperty('font-size', fontSize + 'px');
|
||||
document.body.style.setProperty('font-size', fontSize + 'px');
|
||||
};
|
||||
|
||||
const setGlobalFontFamily = fontFamily => {
|
||||
const setGlobalFontFamily = (fontFamily) => {
|
||||
if (fontFamily === FONTS_DISABLED) fontFamily = null;
|
||||
|
||||
document.documentElement.style
|
||||
.setProperty('font-family', fontFamily);
|
||||
document.body.style
|
||||
.setProperty('font-family', fontFamily);
|
||||
document.documentElement.style.setProperty('font-family', fontFamily);
|
||||
document.body.style.setProperty('font-family', fontFamily);
|
||||
};
|
||||
|
||||
export const settingsMiddleware = store => {
|
||||
export const settingsMiddleware = (store) => {
|
||||
let initialized = false;
|
||||
return next => action => {
|
||||
return (next) => (action) => {
|
||||
const { type, payload } = action;
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
storage.get('panel-settings').then(settings => {
|
||||
storage.get('panel-settings').then((settings) => {
|
||||
store.dispatch(loadSettings(settings));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export const selectSettings = state => state.settings;
|
||||
export const selectActiveTab = state => state.settings.view.activeTab;
|
||||
export const selectSettings = (state) => state.settings;
|
||||
export const selectActiveTab = (state) => state.settings.view.activeTab;
|
||||
|
||||
@@ -86,13 +86,13 @@ a.popt {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.popup input[type=text]:hover,
|
||||
.popup input[type=text]:active,
|
||||
.popup input[type=text]:focus {
|
||||
.popup input[type='text']:hover,
|
||||
.popup input[type='text']:active,
|
||||
.popup input[type='text']:focus {
|
||||
border-color: green;
|
||||
}
|
||||
|
||||
.popup input[type=submit] {
|
||||
.popup input[type='submit'] {
|
||||
padding: 5px 10px;
|
||||
background: #999;
|
||||
color: #ddd;
|
||||
@@ -101,9 +101,9 @@ a.popt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.popup input[type=submit]:hover,
|
||||
.popup input[type=submit]:focus,
|
||||
.popup input[type=submit]:active {
|
||||
.popup input[type='submit']:hover,
|
||||
.popup input[type='submit']:focus,
|
||||
.popup input[type='submit']:active {
|
||||
background: #aaa;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -128,7 +128,7 @@ a.popt {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.highlightPopup input[type=text] {
|
||||
.highlightPopup input[type='text'] {
|
||||
display: block;
|
||||
width: 215px;
|
||||
text-align: left;
|
||||
@@ -136,18 +136,15 @@ a.popt {
|
||||
}
|
||||
|
||||
.highlightPopup input.highlightColor {
|
||||
background-color: #FFFF00;
|
||||
background-color: #ffff00;
|
||||
}
|
||||
|
||||
.highlightPopup input.highlightTermSubmit {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ADMIN CONTEXT MENU */
|
||||
|
||||
|
||||
.contextMenu {
|
||||
background-color: #ddd;
|
||||
position: fixed;
|
||||
@@ -166,11 +163,8 @@ a.popt {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ADMIN FILTER MESSAGES MENU */
|
||||
|
||||
|
||||
.filterMessages {
|
||||
padding: 5px;
|
||||
}
|
||||
@@ -193,22 +187,14 @@ a.popt {
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*****************************************
|
||||
*
|
||||
* OUTPUT ACTUALLY RELATED TO MESSAGES
|
||||
*
|
||||
******************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* MOTD */
|
||||
|
||||
|
||||
.motd {
|
||||
color: #a4bad6;
|
||||
font-family: Verdana, sans-serif;
|
||||
@@ -233,11 +219,8 @@ a.popt {
|
||||
color: #a4bad6;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ADD HERE FOR BOLD */
|
||||
|
||||
|
||||
.bold,
|
||||
.name,
|
||||
.prefix,
|
||||
@@ -250,30 +233,31 @@ a.popt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ADD HERE FOR ITALIC */
|
||||
|
||||
|
||||
.italic, .italics {
|
||||
.italic,
|
||||
.italics {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* OUTPUT COLORS */
|
||||
|
||||
|
||||
.highlight {
|
||||
background: yellow;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: #a4bad6;
|
||||
font-family: Georgia, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
h1.alert, h2.alert {
|
||||
h1.alert,
|
||||
h2.alert {
|
||||
color: #a4bad6;
|
||||
}
|
||||
|
||||
@@ -326,7 +310,8 @@ em {
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
.binarysay a:active, .binarysay a:visited {
|
||||
.binarysay a:active,
|
||||
.binarysay a:visited {
|
||||
color: #88ff88;
|
||||
}
|
||||
|
||||
@@ -426,7 +411,7 @@ em {
|
||||
}
|
||||
|
||||
.alertwarning {
|
||||
color: #FF0000;
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -528,7 +513,7 @@ em {
|
||||
}
|
||||
|
||||
.red {
|
||||
color: #FF0000;
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.blue {
|
||||
@@ -584,7 +569,7 @@ em {
|
||||
}
|
||||
|
||||
.colossus {
|
||||
color: #7F282A;
|
||||
color: #7f282a;
|
||||
font-size: 310%;
|
||||
}
|
||||
|
||||
@@ -668,7 +653,7 @@ em {
|
||||
}
|
||||
|
||||
.alertsyndie {
|
||||
color: #FF0000;
|
||||
color: #ff0000;
|
||||
font-size: 185%;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -684,20 +669,20 @@ em {
|
||||
}
|
||||
|
||||
.sans {
|
||||
font-family: "Comic Sans MS", cursive, sans-serif;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif;
|
||||
}
|
||||
|
||||
.papyrus {
|
||||
font-family: "Papyrus", cursive, sans-serif;
|
||||
font-family: 'Papyrus', cursive, sans-serif;
|
||||
}
|
||||
|
||||
.robot {
|
||||
font-family: "Courier New", cursive, sans-serif;
|
||||
font-family: 'Courier New', cursive, sans-serif;
|
||||
}
|
||||
|
||||
.tape_recorder {
|
||||
color: #FF0000;
|
||||
font-family: "Courier New", cursive, sans-serif;
|
||||
color: #ff0000;
|
||||
font-family: 'Courier New', cursive, sans-serif;
|
||||
}
|
||||
|
||||
.command_headset {
|
||||
@@ -734,18 +719,18 @@ em {
|
||||
.clown {
|
||||
color: #ff70c1;
|
||||
font-size: 160%;
|
||||
font-family: "Comic Sans MS", cursive, sans-serif;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.singing {
|
||||
font-family: "Trebuchet MS", cursive, sans-serif;
|
||||
font-family: 'Trebuchet MS', cursive, sans-serif;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.his_grace {
|
||||
color: #15D512;
|
||||
font-family: "Courier New", cursive, sans-serif;
|
||||
color: #15d512;
|
||||
font-family: 'Courier New', cursive, sans-serif;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -905,7 +890,7 @@ em {
|
||||
}
|
||||
|
||||
.slime {
|
||||
color: #00CED1;
|
||||
color: #00ced1;
|
||||
}
|
||||
|
||||
.drone {
|
||||
@@ -917,11 +902,11 @@ em {
|
||||
}
|
||||
|
||||
.swarmer {
|
||||
color: #2C75FF;
|
||||
color: #2c75ff;
|
||||
}
|
||||
|
||||
.resonate {
|
||||
color: #298F85;
|
||||
color: #298f85;
|
||||
}
|
||||
|
||||
.monkeyhive {
|
||||
@@ -933,7 +918,8 @@ em {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.connectionClosed, .fatalError {
|
||||
.connectionClosed,
|
||||
.fatalError {
|
||||
background: red;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
@@ -948,11 +934,8 @@ em {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* HELPER CLASSES */
|
||||
|
||||
|
||||
.text-normal {
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
html, body {
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
@@ -103,13 +104,13 @@ a.popt {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.popup input[type=text]:hover,
|
||||
.popup input[type=text]:active,
|
||||
.popup input[type=text]:focus {
|
||||
.popup input[type='text']:hover,
|
||||
.popup input[type='text']:active,
|
||||
.popup input[type='text']:focus {
|
||||
border-color: green;
|
||||
}
|
||||
|
||||
.popup input[type=submit] {
|
||||
.popup input[type='submit'] {
|
||||
padding: 5px 10px;
|
||||
background: #999;
|
||||
color: #ddd;
|
||||
@@ -118,9 +119,9 @@ a.popt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.popup input[type=submit]:hover,
|
||||
.popup input[type=submit]:focus,
|
||||
.popup input[type=submit]:active {
|
||||
.popup input[type='submit']:hover,
|
||||
.popup input[type='submit']:focus,
|
||||
.popup input[type='submit']:active {
|
||||
background: #aaa;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -145,7 +146,7 @@ a.popt {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.highlightPopup input[type=text] {
|
||||
.highlightPopup input[type='text'] {
|
||||
display: block;
|
||||
width: 215px;
|
||||
text-align: left;
|
||||
@@ -153,18 +154,15 @@ a.popt {
|
||||
}
|
||||
|
||||
.highlightPopup input.highlightColor {
|
||||
background-color: #FFFF00;
|
||||
background-color: #ffff00;
|
||||
}
|
||||
|
||||
.highlightPopup input.highlightTermSubmit {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ADMIN CONTEXT MENU */
|
||||
|
||||
|
||||
.contextMenu {
|
||||
background-color: #ddd;
|
||||
position: fixed;
|
||||
@@ -183,11 +181,8 @@ a.popt {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ADMIN FILTER MESSAGES MENU */
|
||||
|
||||
|
||||
.filterMessages {
|
||||
padding: 5px;
|
||||
}
|
||||
@@ -210,22 +205,14 @@ a.popt {
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*****************************************
|
||||
*
|
||||
* OUTPUT ACTUALLY RELATED TO MESSAGES
|
||||
*
|
||||
******************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* MOTD */
|
||||
|
||||
|
||||
.motd {
|
||||
color: #638500;
|
||||
font-family: Verdana, sans-serif;
|
||||
@@ -250,11 +237,8 @@ a.popt {
|
||||
color: #638500;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ADD HERE FOR BOLD */
|
||||
|
||||
|
||||
.bold,
|
||||
.name,
|
||||
.prefix,
|
||||
@@ -267,31 +251,31 @@ a.popt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ADD HERE FOR ITALIC */
|
||||
|
||||
|
||||
.italic,
|
||||
.italics {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* OUTPUT COLORS */
|
||||
|
||||
|
||||
.highlight {
|
||||
background: yellow;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
color: #0000ff;
|
||||
font-family: Georgia, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
h1.alert, h2.alert {
|
||||
h1.alert,
|
||||
h2.alert {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
@@ -423,7 +407,8 @@ em {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
h1.alert, h2.alert {
|
||||
h1.alert,
|
||||
h2.alert {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
@@ -458,7 +443,7 @@ h1.alert, h2.alert {
|
||||
}
|
||||
|
||||
.alertwarning {
|
||||
color: #FF0000;
|
||||
color: #ff0000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -493,7 +478,7 @@ h1.alert, h2.alert {
|
||||
}
|
||||
|
||||
.info {
|
||||
color: #0000CC;
|
||||
color: #0000cc;
|
||||
}
|
||||
|
||||
.notice {
|
||||
@@ -560,11 +545,11 @@ h1.alert, h2.alert {
|
||||
}
|
||||
|
||||
.red {
|
||||
color: #FF0000;
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.blue {
|
||||
color: #0000FF;
|
||||
color: #0000ff;
|
||||
}
|
||||
|
||||
.nicegreen {
|
||||
@@ -616,7 +601,7 @@ h1.alert, h2.alert {
|
||||
}
|
||||
|
||||
.colossus {
|
||||
color: #7F282A;
|
||||
color: #7f282a;
|
||||
font-size: 310%;
|
||||
}
|
||||
|
||||
@@ -670,7 +655,7 @@ h1.alert, h2.alert {
|
||||
}
|
||||
|
||||
.deconversion_message {
|
||||
color: #5000A0;
|
||||
color: #5000a0;
|
||||
font-size: 185%;
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -700,7 +685,7 @@ h1.alert, h2.alert {
|
||||
}
|
||||
|
||||
.alertsyndie {
|
||||
color: #FF0000;
|
||||
color: #ff0000;
|
||||
font-size: 185%;
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -716,20 +701,20 @@ h1.alert, h2.alert {
|
||||
}
|
||||
|
||||
.sans {
|
||||
font-family: "Comic Sans MS", cursive, sans-serif;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif;
|
||||
}
|
||||
|
||||
.papyrus {
|
||||
font-family: "Papyrus", cursive, sans-serif;
|
||||
font-family: 'Papyrus', cursive, sans-serif;
|
||||
}
|
||||
|
||||
.robot {
|
||||
font-family: "Courier New", cursive, sans-serif;
|
||||
font-family: 'Courier New', cursive, sans-serif;
|
||||
}
|
||||
|
||||
.tape_recorder {
|
||||
color: #800000;
|
||||
font-family: "Courier New", cursive, sans-serif;
|
||||
font-family: 'Courier New', cursive, sans-serif;
|
||||
}
|
||||
|
||||
.command_headset {
|
||||
@@ -754,30 +739,30 @@ h1.alert, h2.alert {
|
||||
}
|
||||
|
||||
.greentext {
|
||||
color: #00FF00;
|
||||
color: #00ff00;
|
||||
font-size: 185%;
|
||||
}
|
||||
|
||||
.redtext {
|
||||
color: #FF0000;
|
||||
color: #ff0000;
|
||||
font-size: 185%;
|
||||
}
|
||||
|
||||
.clown {
|
||||
color: #FF69Bf;
|
||||
color: #ff69bf;
|
||||
font-size: 160%;
|
||||
font-family: "Comic Sans MS", cursive, sans-serif;
|
||||
font-family: 'Comic Sans MS', cursive, sans-serif;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.singing {
|
||||
font-family: "Trebuchet MS", cursive, sans-serif;
|
||||
font-family: 'Trebuchet MS', cursive, sans-serif;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.his_grace {
|
||||
color: #15D512;
|
||||
font-family: "Courier New", cursive, sans-serif;
|
||||
color: #15d512;
|
||||
font-family: 'Courier New', cursive, sans-serif;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -855,14 +840,14 @@ h1.alert, h2.alert {
|
||||
}
|
||||
|
||||
.mind_control {
|
||||
color: #A00D6F;
|
||||
color: #a00d6f;
|
||||
font-size: 100%;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.slime {
|
||||
color: #00CED1;
|
||||
color: #00ced1;
|
||||
}
|
||||
|
||||
.drone {
|
||||
@@ -874,11 +859,11 @@ h1.alert, h2.alert {
|
||||
}
|
||||
|
||||
.swarmer {
|
||||
color: #2C75FF;
|
||||
color: #2c75ff;
|
||||
}
|
||||
|
||||
.resonate {
|
||||
color: #298F85;
|
||||
color: #298f85;
|
||||
}
|
||||
|
||||
.monkeyhive {
|
||||
@@ -906,11 +891,8 @@ h1.alert, h2.alert {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* HELPER CLASSES */
|
||||
|
||||
|
||||
.text-normal {
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
||||
@@ -32,23 +32,29 @@
|
||||
@include meta.load-css('~tgui/styles/atomic/color.scss');
|
||||
|
||||
// Components
|
||||
@include meta.load-css('~tgui/styles/components/Tabs.scss', $with: (
|
||||
'text-color': rgba(0, 0, 0, 0.5),
|
||||
'color-default': rgba(0, 0, 0, 1),
|
||||
));
|
||||
@include meta.load-css(
|
||||
'~tgui/styles/components/Tabs.scss',
|
||||
$with: ('text-color': rgba(0, 0, 0, 0.5), 'color-default': rgba(0, 0, 0, 1))
|
||||
);
|
||||
@include meta.load-css('~tgui/styles/components/Section.scss');
|
||||
@include meta.load-css('~tgui/styles/components/Button.scss', $with: (
|
||||
'color-default': #bbbbbb,
|
||||
'color-disabled': #363636,
|
||||
'color-selected': #0668b8,
|
||||
'color-caution': #be6209,
|
||||
'color-danger': #9a9d00,
|
||||
'color-transparent-text': rgba(0, 0, 0, 0.5),
|
||||
));
|
||||
@include meta.load-css('~tgui/styles/components/Input.scss', $with: (
|
||||
'border-color': colors.fg(colors.$label),
|
||||
'background-color': #ffffff,
|
||||
));
|
||||
@include meta.load-css(
|
||||
'~tgui/styles/components/Button.scss',
|
||||
$with: (
|
||||
'color-default': #bbbbbb,
|
||||
'color-disabled': #363636,
|
||||
'color-selected': #0668b8,
|
||||
'color-caution': #be6209,
|
||||
'color-danger': #9a9d00,
|
||||
'color-transparent-text': rgba(0, 0, 0, 0.5)
|
||||
)
|
||||
);
|
||||
@include meta.load-css(
|
||||
'~tgui/styles/components/Input.scss',
|
||||
$with: (
|
||||
'border-color': colors.fg(colors.$label),
|
||||
'background-color': #ffffff
|
||||
)
|
||||
);
|
||||
@include meta.load-css('~tgui/styles/components/NumberInput.scss');
|
||||
@include meta.load-css('~tgui/styles/components/TextArea.scss');
|
||||
@include meta.load-css('~tgui/styles/components/Knob.scss');
|
||||
@@ -56,20 +62,25 @@
|
||||
@include meta.load-css('~tgui/styles/components/ProgressBar.scss');
|
||||
|
||||
// Components specific to tgui-panel
|
||||
@include meta.load-css('../components/Chat.scss', $with: (
|
||||
'text-color': #000000,
|
||||
));
|
||||
@include meta.load-css(
|
||||
'../components/Chat.scss',
|
||||
$with: ('text-color': #000000)
|
||||
);
|
||||
|
||||
// Layouts
|
||||
@include meta.load-css('~tgui/styles/layouts/Layout.scss', $with: (
|
||||
'scrollbar-color-multiplier': -1,
|
||||
));
|
||||
@include meta.load-css(
|
||||
'~tgui/styles/layouts/Layout.scss',
|
||||
$with: ('scrollbar-color-multiplier': -1)
|
||||
);
|
||||
@include meta.load-css('~tgui/styles/layouts/Window.scss');
|
||||
@include meta.load-css('~tgui/styles/layouts/TitleBar.scss', $with: (
|
||||
'text-color': rgba(0, 0, 0, 0.75),
|
||||
'background-color': base.$color-bg,
|
||||
'shadow-color-core': rgba(0, 0, 0, 0.25),
|
||||
));
|
||||
@include meta.load-css(
|
||||
'~tgui/styles/layouts/TitleBar.scss',
|
||||
$with: (
|
||||
'text-color': rgba(0, 0, 0, 0.75),
|
||||
'background-color': base.$color-bg,
|
||||
'shadow-color-core': rgba(0, 0, 0, 0.25)
|
||||
)
|
||||
);
|
||||
|
||||
// Goonchat styles
|
||||
@include meta.load-css('../goon/chat-light.scss');
|
||||
|
||||
@@ -11,16 +11,17 @@ const logger = createLogger('telemetry');
|
||||
|
||||
const MAX_CONNECTIONS_STORED = 10;
|
||||
|
||||
// prettier-ignore
|
||||
const connectionsMatch = (a, b) => (
|
||||
a.ckey === b.ckey
|
||||
&& a.address === b.address
|
||||
&& a.computer_id === b.computer_id
|
||||
);
|
||||
|
||||
export const telemetryMiddleware = store => {
|
||||
export const telemetryMiddleware = (store) => {
|
||||
let telemetry;
|
||||
let wasRequestedWithPayload;
|
||||
return next => action => {
|
||||
return (next) => (action) => {
|
||||
const { type, payload } = action;
|
||||
// Handle telemetry requests
|
||||
if (type === 'telemetry/request') {
|
||||
@@ -49,7 +50,7 @@ export const telemetryMiddleware = store => {
|
||||
}
|
||||
// Load telemetry
|
||||
if (!telemetry) {
|
||||
telemetry = await storage.get('telemetry') || {};
|
||||
telemetry = (await storage.get('telemetry')) || {};
|
||||
if (!telemetry.connections) {
|
||||
telemetry.connections = [];
|
||||
}
|
||||
@@ -57,6 +58,7 @@ export const telemetryMiddleware = store => {
|
||||
}
|
||||
// Append a connection record
|
||||
let telemetryMutated = false;
|
||||
// prettier-ignore
|
||||
const duplicateConnection = telemetry.connections
|
||||
.find(conn => connectionsMatch(conn, client));
|
||||
if (!duplicateConnection) {
|
||||
|
||||
@@ -22,7 +22,7 @@ let setClientThemeTimer = null;
|
||||
* There's no way round it. We're essentially changing the skin by hand.
|
||||
* It's painful but it works, and is the way Lummox suggested.
|
||||
*/
|
||||
export const setClientTheme = name => {
|
||||
export const setClientTheme = (name) => {
|
||||
// Transmit once for fast updates and again in a little while in case we won
|
||||
// the race against statbrowser init.
|
||||
clearInterval(setClientThemeTimer);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
// prettier-ignore
|
||||
(function(window, document) {
|
||||
/*jshint evil:true */
|
||||
/** version */
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
// prettier-ignore
|
||||
(function(window){
|
||||
/*! (C) WebReflection Mit Style License */
|
||||
if (document.createEvent) return;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
// prettier-ignore
|
||||
(function(window){
|
||||
'use strict';
|
||||
/* jshint loopfunc: true, noempty: false*/
|
||||
@@ -745,6 +746,8 @@
|
||||
}());
|
||||
}
|
||||
}(window));
|
||||
|
||||
// prettier-ignore
|
||||
(function (global){'use strict';
|
||||
|
||||
// a WeakMap fallback for DOM nodes only used as key
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
// prettier-ignore
|
||||
(function(Proto) {
|
||||
'use strict';
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
// prettier-ignore
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ import { DragzoneProps } from '../types';
|
||||
export const Dragzone = (props: Partial<DragzoneProps>) => {
|
||||
const { theme } = props;
|
||||
if (!theme) return null;
|
||||
const direction
|
||||
= (props.top && 'top')
|
||||
|| (props.right && 'right')
|
||||
|| (props.bottom && 'bottom')
|
||||
|| (props.left && 'left');
|
||||
const direction =
|
||||
(props.top && 'top') ||
|
||||
(props.right && 'right') ||
|
||||
(props.bottom && 'bottom') ||
|
||||
(props.left && 'left');
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Modal } from '../types';
|
||||
*
|
||||
* return -- object: events
|
||||
*/
|
||||
export const eventHandlerMap = (parent: Modal): Modal["events"] => {
|
||||
export const eventHandlerMap = (parent: Modal): Modal['events'] => {
|
||||
return {
|
||||
onArrowKeys: handleArrowKeys.bind(parent),
|
||||
onBackspaceDelete: handleBackspaceDelete.bind(parent),
|
||||
|
||||
@@ -9,9 +9,9 @@ export const handleSetSize = function (this: Modal, value: number) {
|
||||
this.setState({ size: WINDOW_SIZES.large });
|
||||
windowSet(WINDOW_SIZES.large);
|
||||
} else if (
|
||||
value <= LINE_LENGTHS.medium
|
||||
&& value > LINE_LENGTHS.small
|
||||
&& size !== WINDOW_SIZES.medium
|
||||
value <= LINE_LENGTHS.medium &&
|
||||
value > LINE_LENGTHS.small &&
|
||||
size !== WINDOW_SIZES.medium
|
||||
) {
|
||||
this.setState({ size: WINDOW_SIZES.medium });
|
||||
windowSet(WINDOW_SIZES.medium);
|
||||
|
||||
@@ -131,9 +131,9 @@ export const getTheme = (
|
||||
channel: number
|
||||
): string => {
|
||||
return (
|
||||
(lightMode && 'lightMode')
|
||||
|| RADIO_PREFIXES[radioPrefix]?.id
|
||||
|| CHANNELS[channel]?.toLowerCase()
|
||||
(lightMode && 'lightMode') ||
|
||||
RADIO_PREFIXES[radioPrefix]?.id ||
|
||||
CHANNELS[channel]?.toLowerCase()
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -33,8 +33,7 @@
|
||||
}
|
||||
|
||||
/** Creates a button for each channel */
|
||||
@each $channel,
|
||||
$color in colors.$channel-map {
|
||||
@each $channel, $color in colors.$channel-map {
|
||||
.button-#{$channel} {
|
||||
color: $color;
|
||||
border-color: darken($color, 10%);
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
$background: #131313;
|
||||
$button: #1f1f1f;
|
||||
$lightMode: #FFFFFF;
|
||||
$lightBorder: #BBBBBB;
|
||||
$lightHover: #EAEAEA;
|
||||
$lightMode: #ffffff;
|
||||
$lightBorder: #bbbbbb;
|
||||
$lightHover: #eaeaea;
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// Normal chat colors
|
||||
@@ -34,7 +34,6 @@ $_channel_map: (
|
||||
'radio': $radio,
|
||||
'me': $me,
|
||||
'ooc': $ooc,
|
||||
|
||||
'ai': $ai,
|
||||
'binary': $binary,
|
||||
'centcom': $centcom,
|
||||
@@ -51,10 +50,13 @@ $_channel_map: (
|
||||
|
||||
$channel_keys: map.keys($_channel_map) !default;
|
||||
|
||||
$channel-map: (
|
||||
);
|
||||
$channel-map: ();
|
||||
|
||||
@each $channel in $channel_keys {
|
||||
$channel-map: map-merge($channel-map, ($channel: map.get($_channel_map, $channel),
|
||||
));
|
||||
$channel-map: map-merge(
|
||||
$channel-map,
|
||||
(
|
||||
$channel: map.get($_channel_map, $channel),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
@each $channel,
|
||||
$color in colors.$channel-map {
|
||||
@each $channel, $color in colors.$channel-map {
|
||||
$darkened: darken($color, 20%);
|
||||
|
||||
.dragzone-bottom-#{$channel} {
|
||||
|
||||
@@ -45,17 +45,18 @@
|
||||
}
|
||||
|
||||
/** Creates an animated border for each channel */
|
||||
@each $channel,
|
||||
$color in colors.$channel-map {
|
||||
@each $channel, $color in colors.$channel-map {
|
||||
.modal-#{$channel} {
|
||||
&:after {
|
||||
animation: gradient 10s linear infinite;
|
||||
background: linear-gradient(to right,
|
||||
darken($color, 35%),
|
||||
$color,
|
||||
lighten($color, 10%),
|
||||
$color,
|
||||
darken($color, 35%));
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
darken($color, 35%),
|
||||
$color,
|
||||
lighten($color, 10%),
|
||||
$color,
|
||||
darken($color, 35%)
|
||||
);
|
||||
background-position: 0% 0%;
|
||||
background-size: 500% auto;
|
||||
bottom: 0px;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
border: none;
|
||||
color: colors.$background;
|
||||
flex-grow: 1;
|
||||
font-family: "Lucida Console", monospace;
|
||||
font-family: 'Lucida Console', monospace;
|
||||
font-size: 1.1rem;
|
||||
margin-top: 2px;
|
||||
}
|
||||
@@ -16,8 +16,7 @@
|
||||
}
|
||||
|
||||
/** Creates an input for each channel */
|
||||
@each $channel,
|
||||
$color in colors.$channel-map {
|
||||
@each $channel, $color in colors.$channel-map {
|
||||
.textarea-#{$channel} {
|
||||
color: $color;
|
||||
}
|
||||
|
||||
@@ -7,11 +7,9 @@
|
||||
const EXCLUDED_PATTERNS = [/v4shim/i];
|
||||
const loadedMappings = {};
|
||||
|
||||
export const resolveAsset = name => (
|
||||
loadedMappings[name] || name
|
||||
);
|
||||
export const resolveAsset = (name) => loadedMappings[name] || name;
|
||||
|
||||
export const assetMiddleware = store => next => action => {
|
||||
export const assetMiddleware = (store) => (next) => (action) => {
|
||||
const { type, payload } = action;
|
||||
if (type === 'asset/stylesheet') {
|
||||
Byond.loadCss(payload);
|
||||
@@ -20,7 +18,7 @@ export const assetMiddleware = store => next => action => {
|
||||
if (type === 'asset/mappings') {
|
||||
for (let name of Object.keys(payload)) {
|
||||
// Skip anything that matches excluded patterns
|
||||
if (EXCLUDED_PATTERNS.some(regex => regex.test(name))) {
|
||||
if (EXCLUDED_PATTERNS.some((regex) => regex.test(name))) {
|
||||
continue;
|
||||
}
|
||||
const url = payload[name];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user