[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:
SkyratBot
2022-06-25 02:17:32 +02:00
committed by GitHub
parent eb1e9017d9
commit 332a74ea76
662 changed files with 21462 additions and 21085 deletions
+4 -1
View File
@@ -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
+2 -1
View File
@@ -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"
]
}
+21 -6
View File
@@ -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
View File
@@ -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
+15
View File
@@ -0,0 +1,15 @@
## NPM
/**/node_modules
## Yarn
/.yarn
/yarn.lock
/.pnp.*
/docs
/public
/packages/tgui-polyfill
/packages/tgfont/static
**/*.json
**/*.yml
**/*.md
+4 -1
View File
@@ -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
+3 -3
View File
@@ -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`);
+20
View File
@@ -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
View File
@@ -0,0 +1,6 @@
{
"name": "prettier",
"version": "0.19.0-sdk",
"main": "./index.js",
"type": "commonjs"
}
+78 -12
View File
@@ -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
View File
@@ -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) {
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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",
+9 -9
View File
@@ -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],
]);
});
});
+47 -57
View File
@@ -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;
};
};
+6 -6
View File
@@ -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
+3 -4
View File
@@ -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) {
+3 -1
View File
@@ -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));
};
+3 -5
View File
@@ -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;
}
+2 -1
View File
@@ -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)';
};
+2 -3
View File
@@ -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;
};
+2 -1
View File
@@ -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';
+7 -9
View File
@@ -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;
};
+8 -8
View File
@@ -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();
+4 -4
View File
@@ -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) {
+18 -16
View File
@@ -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
View File
@@ -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;
+2 -1
View File
@@ -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);
});
};
+5 -5
View File
@@ -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));
};
+1 -2
View File
@@ -10,5 +10,4 @@ process.chdir(__dirname);
// Silently make a dist folder
try {
require('fs').mkdirSync('dist');
}
catch (err) {}
} catch (err) {}
+1 -2
View File
@@ -62,8 +62,7 @@ const setupApp = async () => {
}
suite.run();
});
}
catch (error) {
} catch (error) {
sendMessage({ type: 'error', error });
}
}
+3 -3
View File
@@ -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';
+4 -18
View File
@@ -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 />
+1 -3
View File
@@ -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();
+6 -8
View File
@@ -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);
+16 -14
View File
@@ -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) {
+5 -4
View 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__) {
+5 -5
View File
@@ -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),
+15 -11
View File
@@ -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);
}
+1 -2
View File
@@ -25,8 +25,7 @@ export const resolveGlob = (...sections) => {
try {
fs.statSync(path);
safePaths.push(path);
}
catch {}
} catch {}
}
return safePaths;
};
+3 -3
View File
@@ -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));
});
}
}
+2 -5
View File
@@ -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;
}
+7 -20
View File
@@ -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>
);
+13 -18
View File
@@ -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>
);
+1 -1
View File
@@ -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 {
+2 -2
View File
@@ -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;
+3 -3
View File
@@ -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();
}
+1 -1
View File
@@ -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>
+15 -13
View File
@@ -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} />
+15 -8
View File
@@ -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>
);
+6 -3
View File
@@ -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,
+23 -21
View File
@@ -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') {
+8 -10
View File
@@ -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);
+3 -5
View File
@@ -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;
+45 -51
View File
@@ -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 -10
View File
@@ -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];
+1 -1
View File
@@ -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);
};
+5 -5
View File
@@ -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) {
+1 -1
View File
@@ -4,4 +4,4 @@
* @license MIT
*/
export const selectGame = state => state.game;
export const selectGame = (state) => state.game;
+2 -1
View File
@@ -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',
+4 -4
View File
@@ -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>
);
+2 -2
View File
@@ -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) {
+5 -4
View File
@@ -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,
+1 -1
View File
@@ -4,4 +4,4 @@
* @license MIT
*/
export const selectPing = state => state.ping;
export const selectPing = (state) => state.ping;
+17 -11
View File
@@ -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,
+2 -2
View File
@@ -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');
+5 -3
View File
@@ -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) {
+1 -1
View File
@@ -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 */
+1
View File
@@ -5,6 +5,7 @@
*/
/* eslint-disable */
// prettier-ignore
(function(window){
/*! (C) WebReflection Mit Style License */
if (document.createEvent) return;
+3
View File
@@ -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
+1
View File
@@ -9,6 +9,7 @@
*/
/* eslint-disable */
// prettier-ignore
(function(Proto) {
'use strict';
+1
View File
@@ -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
+1 -1
View File
@@ -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),
+3 -3
View File
@@ -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);
+3 -3
View File
@@ -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()
);
};
+1 -2
View File
@@ -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%);
+10 -8
View File
@@ -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),
)
);
}
+1 -2
View File
@@ -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} {
+9 -8
View File
@@ -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;
+2 -3
View File
@@ -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;
}
+3 -5
View File
@@ -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