mirror of
https://github.com/ApfelTeeSaft/LiveSplitOne.git
synced 2026-08-26 19:23:40 +00:00
Context Menu, CSS Modules and React 19 (#1015)
This replaces the context menu with our own. By doing that we no longer need to force install our npm packages, as there are no longer any conflicts. In fact, as part of this change, I updated us to the latest React 19 and even enabled the React compiler. I also explored CSS modules for the first time, which are going to allow us to reduce conflicts between CSS rules and possibly even allow us to fully remove SASS, as the need for it is going to be reduced. I also found out that the latest Rust nightly is broken due to their update to LLVM 20. So I pinned the version to the last working nightly version.
This commit is contained in:
@@ -70,7 +70,7 @@ jobs:
|
||||
- name: Install Rust
|
||||
uses: hecrj/setup-rust-action@v2
|
||||
with:
|
||||
rust-version: nightly
|
||||
rust-version: nightly-2025-02-17
|
||||
components: rust-src
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
mv wasm-bindgen-${{ steps.wasm-bindgen.outputs.version }}-${{ matrix.target }}/wasm* .
|
||||
|
||||
- name: Install npm packages
|
||||
run: npm ci -f
|
||||
run: npm ci
|
||||
env:
|
||||
DETECT_CHROMEDRIVER_VERSION: true
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ cargo install wasm-bindgen-cli
|
||||
You need to set up some npm modules before compiling the project:
|
||||
|
||||
```bash
|
||||
npm install -f
|
||||
npm install
|
||||
```
|
||||
|
||||
You are now ready to build livesplit-core, which powers LiveSplit One:
|
||||
|
||||
+7
-1
@@ -28,11 +28,17 @@ if (process.argv.some((v) => v === "--max-opt")) {
|
||||
if (process.argv.some((v) => v === "--unstable")) {
|
||||
// Relaxed SIMD is not supported by Firefox and Safari yet.
|
||||
rustFlags += ",+relaxed-simd";
|
||||
|
||||
// Not supported by any browser yet.
|
||||
rustFlags += ",+wide-arithmetic";
|
||||
}
|
||||
|
||||
// Use the nightly toolchain, which enables some more optimizations.
|
||||
if (process.argv.some((v) => v === "--nightly")) {
|
||||
toolchain = "+nightly";
|
||||
// FIXME: Nightly is broken since the LLVM 20 upgrade. We need to wait for
|
||||
// stdarch (which has the fix already) to be updated:
|
||||
// https://github.com/rust-lang/stdarch/pull/1719
|
||||
toolchain = "+nightly-2025-02-17";
|
||||
cargoFlags +=
|
||||
" -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort";
|
||||
rustFlags += " -Z wasm-c-abi=spec";
|
||||
|
||||
Generated
+215
-178
@@ -8,38 +8,39 @@
|
||||
"dependencies": {
|
||||
"idb": "^8.0.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-contextmenu": "^2.14.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-resizable": "^3.0.4",
|
||||
"react-sidebar": "^3.0.2",
|
||||
"react-toastify": "11.0.3"
|
||||
"react-toastify": "^11.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-explicit-resource-management": "^7.25.9",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.13",
|
||||
"@tauri-apps/cli": "^1.6.0",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/node": "22.13.1",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/node": "^22.13.1",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/react-resizable": "^3.0.3",
|
||||
"@types/react-sidebar": "^3.0.2",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"css-loader": "7.1.2",
|
||||
"css-loader": "^7.1.2",
|
||||
"eslint": "^9.20.1",
|
||||
"favicons-webpack-plugin": "^6.0.0",
|
||||
"html-inline-script-webpack-plugin": "^3.1.0",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"react-compiler-webpack": "^0.1.2",
|
||||
"react-refresh-typescript": "^2.0.7",
|
||||
"sass": "^1.77.2",
|
||||
"sass-loader": "16.0.4",
|
||||
"sass-loader": "^16.0.4",
|
||||
"style-loader": "^4.0.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.24.0",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "6.0.1",
|
||||
"webpack-dev-server": "5.2.0",
|
||||
"webpack-cli": "^6.0.1",
|
||||
"webpack-dev-server": "^5.2.0",
|
||||
"workbox-webpack-plugin": "^7.0.0"
|
||||
}
|
||||
},
|
||||
@@ -535,6 +536,22 @@
|
||||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-proposal-explicit-resource-management": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-explicit-resource-management/-/plugin-proposal-explicit-resource-management-7.25.9.tgz",
|
||||
"integrity": "sha512-EbtfSvb6s4lZwef1nH52nw4DTUAvHY6bl1mbLgEHUkR6L8j4WY70mM/InB8Ozgdlok/7etbiSW+Wc88ZebZAKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-proposal-private-property-in-object": {
|
||||
"version": "7.21.0-placeholder-for-preset-env.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
|
||||
@@ -580,6 +597,38 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-jsx": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz",
|
||||
"integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-typescript": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz",
|
||||
"integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-unicode-sets-regex": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
|
||||
@@ -1760,32 +1809,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
|
||||
"integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.6.tgz",
|
||||
"integrity": "sha512-+0TjwR1eAUdZtvv/ir1mGX+v0tUoR3VEPB8Up0LLJC+whRW0GgBBtpbOkg/a/U4Dxa6l5a3l9AJ1aWIQVyoWJA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.10.0",
|
||||
"@eslint/core": "^0.11.0",
|
||||
"levn": "^0.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
|
||||
"integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@@ -1839,9 +1875,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/retry": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
|
||||
"integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
|
||||
"integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
@@ -3337,9 +3373,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz",
|
||||
"integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==",
|
||||
"version": "22.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.4.tgz",
|
||||
"integrity": "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3356,13 +3392,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
||||
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.9.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
|
||||
@@ -3378,24 +3407,23 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
||||
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
||||
"version": "19.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz",
|
||||
"integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-dom": {
|
||||
"version": "18.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz",
|
||||
"integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
|
||||
"version": "19.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz",
|
||||
"integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-resizable": {
|
||||
@@ -3493,17 +3521,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.0.tgz",
|
||||
"integrity": "sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==",
|
||||
"version": "8.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.1.tgz",
|
||||
"integrity": "sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.24.0",
|
||||
"@typescript-eslint/type-utils": "8.24.0",
|
||||
"@typescript-eslint/utils": "8.24.0",
|
||||
"@typescript-eslint/visitor-keys": "8.24.0",
|
||||
"@typescript-eslint/scope-manager": "8.24.1",
|
||||
"@typescript-eslint/type-utils": "8.24.1",
|
||||
"@typescript-eslint/utils": "8.24.1",
|
||||
"@typescript-eslint/visitor-keys": "8.24.1",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.3.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
@@ -3523,16 +3551,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.0.tgz",
|
||||
"integrity": "sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==",
|
||||
"version": "8.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.1.tgz",
|
||||
"integrity": "sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.24.0",
|
||||
"@typescript-eslint/types": "8.24.0",
|
||||
"@typescript-eslint/typescript-estree": "8.24.0",
|
||||
"@typescript-eslint/visitor-keys": "8.24.0",
|
||||
"@typescript-eslint/scope-manager": "8.24.1",
|
||||
"@typescript-eslint/types": "8.24.1",
|
||||
"@typescript-eslint/typescript-estree": "8.24.1",
|
||||
"@typescript-eslint/visitor-keys": "8.24.1",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3548,14 +3576,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.0.tgz",
|
||||
"integrity": "sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==",
|
||||
"version": "8.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.1.tgz",
|
||||
"integrity": "sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.24.0",
|
||||
"@typescript-eslint/visitor-keys": "8.24.0"
|
||||
"@typescript-eslint/types": "8.24.1",
|
||||
"@typescript-eslint/visitor-keys": "8.24.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -3566,14 +3594,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.0.tgz",
|
||||
"integrity": "sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==",
|
||||
"version": "8.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.1.tgz",
|
||||
"integrity": "sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.24.0",
|
||||
"@typescript-eslint/utils": "8.24.0",
|
||||
"@typescript-eslint/typescript-estree": "8.24.1",
|
||||
"@typescript-eslint/utils": "8.24.1",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.0.1"
|
||||
},
|
||||
@@ -3590,9 +3618,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.0.tgz",
|
||||
"integrity": "sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==",
|
||||
"version": "8.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.1.tgz",
|
||||
"integrity": "sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -3604,14 +3632,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.0.tgz",
|
||||
"integrity": "sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==",
|
||||
"version": "8.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.1.tgz",
|
||||
"integrity": "sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.24.0",
|
||||
"@typescript-eslint/visitor-keys": "8.24.0",
|
||||
"@typescript-eslint/types": "8.24.1",
|
||||
"@typescript-eslint/visitor-keys": "8.24.1",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -3657,16 +3685,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.0.tgz",
|
||||
"integrity": "sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==",
|
||||
"version": "8.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.1.tgz",
|
||||
"integrity": "sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@typescript-eslint/scope-manager": "8.24.0",
|
||||
"@typescript-eslint/types": "8.24.0",
|
||||
"@typescript-eslint/typescript-estree": "8.24.0"
|
||||
"@typescript-eslint/scope-manager": "8.24.1",
|
||||
"@typescript-eslint/types": "8.24.1",
|
||||
"@typescript-eslint/typescript-estree": "8.24.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -3681,13 +3709,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.24.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.0.tgz",
|
||||
"integrity": "sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==",
|
||||
"version": "8.24.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.1.tgz",
|
||||
"integrity": "sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.24.0",
|
||||
"@typescript-eslint/types": "8.24.1",
|
||||
"eslint-visitor-keys": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4272,6 +4300,17 @@
|
||||
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-react-compiler": {
|
||||
"version": "19.0.0-beta-21e868a-20250216",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.0.0-beta-21e868a-20250216.tgz",
|
||||
"integrity": "sha512-WDOBsm9t9P0RADm8CSlav5OqWvs+3mZFvrBo/qf3vuNtdz78OG5TFxOy7De8ePR3rA6qg1Qmcjjae6nR1pOpCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -4531,9 +4570,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001699",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz",
|
||||
"integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==",
|
||||
"version": "1.0.30001700",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz",
|
||||
"integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4594,12 +4633,6 @@
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/clean-css": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
|
||||
@@ -4655,9 +4688,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -5365,9 +5398,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.101",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.101.tgz",
|
||||
"integrity": "sha512-L0ISiQrP/56Acgu4/i/kfPwWSgrzYZUnQrC0+QPFuhqlLP1Ir7qzPPDVS9BcKIyWTRU8+o6CC8dKw38tSWhYIA==",
|
||||
"version": "1.5.102",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz",
|
||||
"integrity": "sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -6145,9 +6178,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
|
||||
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
|
||||
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -7762,9 +7795,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/launch-editor": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.9.1.tgz",
|
||||
"integrity": "sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==",
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz",
|
||||
"integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8682,9 +8715,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz",
|
||||
"integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==",
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
|
||||
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -8963,43 +8996,64 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
|
||||
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-contextmenu": {
|
||||
"version": "2.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-contextmenu/-/react-contextmenu-2.14.0.tgz",
|
||||
"integrity": "sha512-ktqMOuad6sCFNJs/ltEwppN8F0YeXmqoZfwycgtZR/MxOXMYx1xgYC44SzWH259HdGyshk1/7sXGuIRwj9hzbw==",
|
||||
"node_modules/react-compiler-webpack": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-compiler-webpack/-/react-compiler-webpack-0.1.2.tgz",
|
||||
"integrity": "sha512-NaT5Ft4FRqiDAllr8ywjmXWcv0+ouL/4GsYiLHj783C2HbTp4SGUtUC/6vpWT/UQ5LggV+PUMLUrdEdua+LOaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5",
|
||||
"object-assign": "^4.1.0"
|
||||
"@babel/core": "^7.24.6",
|
||||
"@babel/plugin-syntax-jsx": "^7.24.6",
|
||||
"@babel/plugin-syntax-typescript": "^7.24.6",
|
||||
"loader-utils": "^3.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prop-types": "^15.0.0",
|
||||
"react": "^0.14.0 || ^15.0.0 || ^16.0.1",
|
||||
"react-dom": "^0.14.0 || ^15.0.0 || ^16.0.1"
|
||||
"babel-plugin-react-compiler": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-compiler-webpack/node_modules/loader-utils": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz",
|
||||
"integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
|
||||
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
"scheduler": "^0.25.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1"
|
||||
"react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-draggable": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz",
|
||||
"integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clsx": "^1.1.1",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.3.0",
|
||||
"react-dom": ">= 16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
@@ -9043,29 +9097,6 @@
|
||||
"react": ">= 16.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-resizable/node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/react-resizable/node_modules/react-draggable": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz",
|
||||
"integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"clsx": "^1.1.1",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 16.3.0",
|
||||
"react-dom": ">= 16.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-sidebar": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-sidebar/-/react-sidebar-3.0.2.tgz",
|
||||
@@ -9093,6 +9124,15 @@
|
||||
"react-dom": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/react-toastify/node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
@@ -9554,9 +9594,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass-loader": {
|
||||
"version": "16.0.4",
|
||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.4.tgz",
|
||||
"integrity": "sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==",
|
||||
"version": "16.0.5",
|
||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz",
|
||||
"integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -9603,13 +9643,10 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
|
||||
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/schema-utils": {
|
||||
"version": "4.3.0",
|
||||
@@ -10663,9 +10700,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "4.34.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.34.1.tgz",
|
||||
"integrity": "sha512-6kSc32kT0rbwxD6QL1CYe8IqdzN/J/ILMrNK+HMQCKH3insCDRY/3ITb0vcBss0a3t72fzh2YSzj8ko1HgwT3g==",
|
||||
"version": "4.35.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.35.0.tgz",
|
||||
"integrity": "sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"optional": true,
|
||||
@@ -10784,15 +10821,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.24.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.24.0.tgz",
|
||||
"integrity": "sha512-/lmv4366en/qbB32Vz5+kCNZEMf6xYHwh1z48suBwZvAtnXKbP+YhGe8OLE2BqC67LMqKkCNLtjejdwsdW6uOQ==",
|
||||
"version": "8.24.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.24.1.tgz",
|
||||
"integrity": "sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.24.0",
|
||||
"@typescript-eslint/parser": "8.24.0",
|
||||
"@typescript-eslint/utils": "8.24.0"
|
||||
"@typescript-eslint/eslint-plugin": "8.24.1",
|
||||
"@typescript-eslint/parser": "8.24.1",
|
||||
"@typescript-eslint/utils": "8.24.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
||||
+12
-11
@@ -17,38 +17,39 @@
|
||||
"dependencies": {
|
||||
"idb": "^8.0.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-contextmenu": "^2.14.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-resizable": "^3.0.4",
|
||||
"react-sidebar": "^3.0.2",
|
||||
"react-toastify": "11.0.3"
|
||||
"react-toastify": "^11.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-explicit-resource-management": "^7.25.9",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.13",
|
||||
"@tauri-apps/cli": "^1.6.0",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/node": "22.13.1",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/node": "^22.13.1",
|
||||
"@types/react": "^19.0.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/react-resizable": "^3.0.3",
|
||||
"@types/react-sidebar": "^3.0.2",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"css-loader": "7.1.2",
|
||||
"css-loader": "^7.1.2",
|
||||
"eslint": "^9.20.1",
|
||||
"favicons-webpack-plugin": "^6.0.0",
|
||||
"html-inline-script-webpack-plugin": "^3.1.0",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"react-compiler-webpack": "^0.1.2",
|
||||
"react-refresh-typescript": "^2.0.7",
|
||||
"sass": "^1.77.2",
|
||||
"sass-loader": "16.0.4",
|
||||
"sass-loader": "^16.0.4",
|
||||
"style-loader": "^4.0.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.24.0",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "6.0.1",
|
||||
"webpack-dev-server": "5.2.0",
|
||||
"webpack-cli": "^6.0.1",
|
||||
"webpack-dev-server": "^5.2.0",
|
||||
"workbox-webpack-plugin": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
@use 'mobile';
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
$icon-size: 40px;
|
||||
$title-font-size: 40px;
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
@use 'variables.icss';
|
||||
|
||||
.colorPickerButton {
|
||||
border: 2px solid white;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 1px rgba(0,0,0,.1);
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
height: variables.$settings-row-height;
|
||||
}
|
||||
|
||||
.colorPickerDialogPositioning {
|
||||
margin: 0 auto;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
inset: 0;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.glassPanel {
|
||||
background-color: rgba(28, 28, 28, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
margin-top: 5px;
|
||||
margin-left: -113.5px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
box-shadow: 0 5px 10px 0px rgba(28, 28, 28, 0.8);
|
||||
}
|
||||
|
||||
.hr {
|
||||
margin: 0;
|
||||
height: 1px;
|
||||
border-width: 0px;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.gradientSelector {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.whiteGradient {
|
||||
background: linear-gradient(to right, white, transparent);
|
||||
|
||||
.cursor {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
box-shadow: black 0 0 0 2px inset;
|
||||
|
||||
>div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
box-shadow: white 0 0 0 1px inset;
|
||||
}
|
||||
}
|
||||
|
||||
.blackGradient {
|
||||
width: 225px;
|
||||
height: 125px;
|
||||
background: linear-gradient(to top, black, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.controlPanel {
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.controlPanelTop {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
.checker {
|
||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAADFJREFUOE9jZGBgEGHAD97gk2YcNYBhmIQBgWSAP52AwoAQwJvQRg1gACckQoC2gQgAIF8IscwEtKYAAAAASUVORK5CYII=") left center;
|
||||
}
|
||||
|
||||
.sliders {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.hueSlider {
|
||||
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
|
||||
height: 18px;
|
||||
position: relative;
|
||||
border-radius: 9px;
|
||||
|
||||
input {
|
||||
margin: 0;
|
||||
background: none;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.alphaSlider {
|
||||
background: white;
|
||||
border-radius: 9px;
|
||||
overflow: hidden;
|
||||
height: 18px;
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
margin: 0;
|
||||
background: none;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.controlPanelBottom {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
/* Hack for LSO to prevent global style */
|
||||
all: revert;
|
||||
appearance: none;
|
||||
accent-color: white;
|
||||
width: calc(100% - 2px);
|
||||
}
|
||||
}
|
||||
|
||||
.colorPreview {
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
border-radius: 50%;
|
||||
background: white;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.colorPreviewInner {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.colorInput {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
width: 0;
|
||||
user-select: none;
|
||||
|
||||
>input {
|
||||
width: 100%;
|
||||
/* Hack for LSO to prevent global input height style */
|
||||
height: 25px !important;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-variant-numeric: tabular-nums;
|
||||
background: transparent;
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
border-radius: 5px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.predefinedColors {
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.predefinedColorsRow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.predefinedColor {
|
||||
/* Hack for LSO to prevent global button style */
|
||||
all: revert;
|
||||
border: 3px solid transparent;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
transition: all .05s, border-color .25s;
|
||||
/* Fix for iOS minimum button width */
|
||||
font-size: 0;
|
||||
|
||||
&:hover {
|
||||
/* Hack for LSO to prevent global button style */
|
||||
background: revert;
|
||||
border: 3px solid white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
margin-top: 3px;
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
&:active:hover {
|
||||
/* Hack for LSO to prevent global button style */
|
||||
background: revert;
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
@use 'variables';
|
||||
|
||||
.color-picker-button {
|
||||
border: 2px solid white;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 1px rgba(0,0,0,.1);
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
height: variables.$settings-row-height;
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
.blur {
|
||||
background-color: rgba(28, 28, 28, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
/* Hack for LSO to prevent global style */
|
||||
all: revert;
|
||||
appearance: none;
|
||||
accent-color: white;
|
||||
width: calc(100% - 2px);
|
||||
}
|
||||
|
||||
.predefined-color {
|
||||
/* Hack for LSO to prevent global button style */
|
||||
all: revert;
|
||||
border: 3px solid transparent;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
transition: all .05s, border-color .25s;
|
||||
/* Fix for iOS minimum button width */
|
||||
font-size: 0;
|
||||
|
||||
&:hover {
|
||||
border: 3px solid white;
|
||||
}
|
||||
|
||||
&:active {
|
||||
margin-top: 3px;
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
&:active:hover {
|
||||
/* Hack for LSO to prevent global button style */
|
||||
background: revert;
|
||||
}
|
||||
}
|
||||
|
||||
.color-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
width: 0;
|
||||
user-select: none;
|
||||
|
||||
>input {
|
||||
width: 100%;
|
||||
/* Hack for LSO to prevent global input height style */
|
||||
height: 25px !important;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-variant-numeric: tabular-nums;
|
||||
background: transparent;
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
border-radius: 5px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.checker {
|
||||
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAADFJREFUOE9jZGBgEGHAD97gk2YcNYBhmIQBgWSAP52AwoAQwJvQRg1gACckQoC2gQgAIF8IscwEtKYAAAAASUVORK5CYII=") left center;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
inset: 0;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
.contextMenu {
|
||||
position: fixed;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.panel {
|
||||
z-index: 3;
|
||||
background-color: rgba(28, 28, 28, .8);
|
||||
backdrop-filter: blur(5px);
|
||||
min-width: 100px;
|
||||
padding: 4px 0;
|
||||
border: 1px solid rgba(255, 255, 255, .25);
|
||||
border-radius: 9px;
|
||||
box-shadow: 0 5px 10px 0px rgba(28, 28, 28, .8);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
text-align: left;
|
||||
font-weight: initial;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
inset: 0;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.entry {
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background-color .25s;
|
||||
border-radius: 5px;
|
||||
margin: 0 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, .25);
|
||||
}
|
||||
}
|
||||
|
||||
.hr {
|
||||
height: 1px;
|
||||
border-width: 0px;
|
||||
background: rgba(255, 255, 255, .25);
|
||||
margin: 4px 0;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
@use 'sass:math';
|
||||
|
||||
@use 'variables';
|
||||
|
||||
@mixin context-menu {
|
||||
.react-contextmenu {
|
||||
min-width: 160px;
|
||||
padding: math.div(variables.$ui-margin, 2) 0;
|
||||
margin: 2px 0 0;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
background-color: variables.$dark-row-color;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid variables.$border-color;
|
||||
border-radius: 0.25rem;
|
||||
outline: none;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 250ms ease !important;
|
||||
z-index: 3;
|
||||
|
||||
&.react-contextmenu--visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.react-contextmenu-item {
|
||||
padding: math.div(variables.$ui-margin, 2) variables.$ui-margin;
|
||||
font-weight: 400;
|
||||
text-align: inherit;
|
||||
white-space: nowrap;
|
||||
background: 0 0;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.react-contextmenu-item--divider {
|
||||
margin-bottom: math.div(variables.$ui-margin, 2);
|
||||
padding: math.div(variables.$ui-margin, 2) 0 0 0;
|
||||
border-bottom: 1px solid variables.$border-color;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.react-contextmenu-item--selected {
|
||||
background: variables.$selected-row-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -1,4 +1,4 @@
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
.is-mobile+dialog>.dialog {
|
||||
min-width: auto;
|
||||
@@ -15,8 +15,9 @@ dialog {
|
||||
|
||||
.dialog {
|
||||
color: #eee;
|
||||
background: variables.$main-background-color;
|
||||
border: 2px solid variables.$border-color;
|
||||
background-color: rgba(28, 28, 28, .8);
|
||||
backdrop-filter: blur(5px);
|
||||
border: 2px solid rgba(255, 255, 255, .25);
|
||||
border-radius: 10px;
|
||||
min-width: 225px;
|
||||
|
||||
@@ -50,7 +51,7 @@ dialog {
|
||||
input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-bottom: 1px solid hsla(0, 0%, 100%, 0.25);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, .25);
|
||||
background: transparent;
|
||||
color: white;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@use 'mobile';
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
#upload-drop-zone {
|
||||
position: absolute;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
.hotkey-box {
|
||||
button {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
@use 'mobile';
|
||||
@use 'ContextMenu';
|
||||
@use 'Table';
|
||||
@use 'Toggle';
|
||||
@use "variables";
|
||||
@use "variables.icss";
|
||||
|
||||
.layout-editor-outer {
|
||||
display: inline-flex;
|
||||
|
||||
@include ContextMenu.context-menu;
|
||||
@include Table.table;
|
||||
@include Toggle.toggle;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@use 'mobile';
|
||||
@use 'Sidebar';
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
$sidebar-button-height: 42px;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
.livesplit-server-button {
|
||||
margin: 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
@mixin markdown {
|
||||
.flag {
|
||||
|
||||
+4
-14
@@ -1,11 +1,10 @@
|
||||
@use 'sass:math';
|
||||
|
||||
@use "ContextMenu";
|
||||
@use "Markdown";
|
||||
@use "mobile";
|
||||
@use "Table";
|
||||
@use "Toggle";
|
||||
@use "variables";
|
||||
@use "variables.icss";
|
||||
|
||||
$tab-bar-height: 30px + math.div(variables.$ui-margin, 2);
|
||||
$tab-width: 625px;
|
||||
@@ -18,7 +17,6 @@ $small-font-size: 13px;
|
||||
$small-button-padding: 1px 3px 1px 3px;
|
||||
|
||||
.run-editor {
|
||||
@include ContextMenu.context-menu;
|
||||
@include Markdown.markdown;
|
||||
@include Table.table;
|
||||
@include Toggle.toggle;
|
||||
@@ -205,18 +203,10 @@ $small-button-padding: 1px 3px 1px 3px;
|
||||
}
|
||||
|
||||
&.segment-icon-container {
|
||||
width: $segment-icon-size;
|
||||
height: $segment-icon-size;
|
||||
cursor: pointer;
|
||||
|
||||
div {
|
||||
height: $segment-icon-size;
|
||||
|
||||
.segment-icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@use 'sass:math';
|
||||
|
||||
@use 'Toggle';
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
$h1-font-size: 24px;
|
||||
$h2-font-size: 22px;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
@use 'sass:math';
|
||||
|
||||
@use 'mobile';
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
@use 'Table';
|
||||
@use 'ContextMenu';
|
||||
|
||||
$loading-text-font-size: 40px;
|
||||
$splits-row-width: 500px;
|
||||
@@ -86,8 +85,6 @@ $splits-row-height: 40px;
|
||||
flex-shrink: 0;
|
||||
margin-left: variables.$ui-large-margin;
|
||||
|
||||
@include ContextMenu.context-menu;
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
.label {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
width: 35px;
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
@use 'sass:math';
|
||||
|
||||
@use 'mobile';
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
@mixin table {
|
||||
.table {
|
||||
@@ -74,14 +74,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
border-bottom: 1px solid hsla(0, 0%, 100%, 0.25);
|
||||
input.text-box {
|
||||
font-family: inherit;
|
||||
background: transparent;
|
||||
color: white;
|
||||
text-overflow: ellipsis;
|
||||
font-family: "fira", sans-serif;
|
||||
font-size: 15px;
|
||||
border: none;
|
||||
border-bottom: 1px solid hsla(0, 0%, 100%, 0.25);
|
||||
}
|
||||
|
||||
input[Type="text"] {
|
||||
@@ -94,7 +94,7 @@
|
||||
border: 1px solid variables.$border-color;
|
||||
color: white;
|
||||
text-overflow: ellipsis;
|
||||
font-family: "fira", sans-serif;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.settings-table {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@use 'sass:math';
|
||||
|
||||
@use 'mobile';
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
.buttons {
|
||||
margin-top: variables.$ui-large-margin - math.div(variables.$ui-margin, 2);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
@mixin toggle {
|
||||
.toggle-left {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@use 'mobile';
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
@@ -8,8 +8,9 @@
|
||||
.tooltip .tooltip-text {
|
||||
visibility: hidden;
|
||||
width: 300px;
|
||||
background-color: variables.$sidebar-background-color;
|
||||
border: 1px solid variables.$border-color;
|
||||
background-color: rgba(28, 28, 28, .8);
|
||||
backdrop-filter: blur(5px);
|
||||
border: 1px solid rgba(255, 255, 255, .25);
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
@@ -31,7 +32,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.react-contextmenu-item.tooltip .tooltip-text {
|
||||
.contextmenu-item.tooltip .tooltip-text {
|
||||
bottom: initial;
|
||||
left: calc(100% + variables.$ui-margin);
|
||||
margin-left: 0;
|
||||
|
||||
+4
-3
@@ -1,7 +1,7 @@
|
||||
@use 'sass:math';
|
||||
|
||||
@use 'browser_source';
|
||||
@use 'variables';
|
||||
@use 'variables.icss';
|
||||
|
||||
@font-face {
|
||||
font-family: timer;
|
||||
@@ -25,8 +25,9 @@ body {
|
||||
.Toastify {
|
||||
.toast-class {
|
||||
font-family: "fira", sans-serif;
|
||||
background: variables.$sidebar-background-color !important;
|
||||
border: 2px solid variables.$border-color !important;
|
||||
background: rgba(28, 28, 28, .8) !important;
|
||||
backdrop-filter: blur(5px);
|
||||
border: 2px solid rgba(255, 255, 255, .25) !important;
|
||||
border-radius: 10px !important;
|
||||
min-height: 48px !important;
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export default class Layout extends React.Component<Props, unknown> {
|
||||
<div className="layout" style={{ width: this.props.width, height: this.props.height }}>
|
||||
<div
|
||||
style={{ width: "inherit", height: "inherit" }}
|
||||
ref={(element) => element?.appendChild(this.props.renderer.element())}
|
||||
ref={(element) => { element?.appendChild(this.props.renderer.element()); }}
|
||||
/>
|
||||
{
|
||||
this.props.allowResize && <div className="resizable-layout">
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
declare module "commonmark-react-renderer" {
|
||||
class ReactRenderer {
|
||||
constructor(options?: Options);
|
||||
render(ast: any): JSX.Element;
|
||||
}
|
||||
interface Options {
|
||||
escapeHtml?: boolean,
|
||||
linkTarget?: string,
|
||||
softBreak?: string,
|
||||
}
|
||||
export = ReactRenderer;
|
||||
}
|
||||
Vendored
-10
@@ -1,10 +0,0 @@
|
||||
declare module "emojilib" {
|
||||
export const lib: {
|
||||
[emoji_name: string]: Emoji;
|
||||
}
|
||||
|
||||
export interface Emoji {
|
||||
char: string,
|
||||
keywords: string[],
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
declare module "react-contextmenu" {
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export interface ContextMenuProps {
|
||||
children?: ReactNode,
|
||||
}
|
||||
|
||||
export interface ContextMenuTriggerProps {
|
||||
children?: ReactNode,
|
||||
}
|
||||
|
||||
export interface MenuItemProps {
|
||||
children?: ReactNode,
|
||||
}
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
declare module "react-linkifier" {
|
||||
import { Component, ReactNode } from "react";
|
||||
|
||||
export interface Props {
|
||||
target?: string,
|
||||
children: ReactNode,
|
||||
}
|
||||
|
||||
export default class Linkifier extends Component<Props> { }
|
||||
}
|
||||
+2
-2
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
|
||||
import LiveSplitIcon from "../assets/icon.svg";
|
||||
import { Markdown } from "../util/Markdown";
|
||||
import * as variables from "../css/variables.scss";
|
||||
import * as variables from "../css/variables.icss.scss";
|
||||
|
||||
import "../css/About.scss";
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface Props {
|
||||
}
|
||||
|
||||
interface Callbacks {
|
||||
renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element): JSX.Element,
|
||||
renderViewWithSidebar(renderedView: React.JSX.Element, sidebarContent: React.JSX.Element): React.JSX.Element,
|
||||
openTimerView(): void,
|
||||
}
|
||||
|
||||
|
||||
+88
-176
@@ -1,9 +1,9 @@
|
||||
import * as React from "react";
|
||||
import React, { ChangeEvent, MouseEvent } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { colorToCss } from "../util/ColorUtil";
|
||||
import { Color } from "../livesplit-core";
|
||||
|
||||
import "../css/ColorPicker.scss";
|
||||
import * as classes from "../css/ColorPicker.module.scss";
|
||||
|
||||
const EyeDropper = (window as any).EyeDropper;
|
||||
const hasEyeDropper = !!EyeDropper;
|
||||
@@ -13,14 +13,11 @@ export default function ColorPicker({ color, setColor }: { color: Color, setColo
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="color-picker-button"
|
||||
className={classes.colorPickerButton}
|
||||
style={{ background: colorToCss(color) }}
|
||||
onClick={() => setIsShowing(true)}
|
||||
/>
|
||||
<div style={{
|
||||
margin: "0 auto",
|
||||
width: "0",
|
||||
}}>
|
||||
<div className={classes.colorPickerDialogPositioning}>
|
||||
{isShowing && <ColorPickerDialog color={color} setColor={setColor} close={() => setIsShowing(false)} />}
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,67 +26,48 @@ export default function ColorPicker({ color, setColor }: { color: Color, setColo
|
||||
|
||||
function ColorPickerDialog({ color, setColor, close }: { color: Color, setColor: (color: Color) => void, close: () => void }) {
|
||||
return (
|
||||
<div style={{ margin: "0 auto", width: 0 }} className="color-picker">
|
||||
<div className="overlay" onClick={() => close()}></div>
|
||||
<div
|
||||
className="blur"
|
||||
style={{
|
||||
zIndex: 1,
|
||||
position: "absolute",
|
||||
marginTop: "5px",
|
||||
marginLeft: "-113.5px",
|
||||
border: "1px solid rgba(255, 255, 255, 0.25)",
|
||||
boxShadow: "0 5px 10px 0px rgba(28, 28, 28, 0.8)",
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<div className={classes.overlay} onClick={close} />
|
||||
<div className={classes.glassPanel} >
|
||||
<GradientSelector color={color} setColor={setColor} />
|
||||
<Hr />
|
||||
<ControlPanel color={color} setColor={setColor} />
|
||||
<Hr />
|
||||
<PredefinedColors setColor={setColor} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Hr() {
|
||||
return (
|
||||
<hr
|
||||
style={{
|
||||
margin: 0,
|
||||
height: "1px",
|
||||
borderWidth: "0px",
|
||||
background: "rgba(255, 255, 255, 0.25)",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return <hr className={classes.hr} />;
|
||||
}
|
||||
|
||||
function GradientSelector({ color, setColor }: { color: Color, setColor: (color: Color) => void }) {
|
||||
const [r, g, b, a] = color;
|
||||
const [h, s, v] = rgbToHsv(r, g, b);
|
||||
const [r1, g1, b1] = hsvToRgb(h, 1.0, 1.0);
|
||||
const [r1, g1, b1] = hsvToRgb(h, 1, 1);
|
||||
const [mouseDown, setMouseDown] = useState(false);
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent) => {
|
||||
const handleMouseDown = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
(document.activeElement as any)?.blur();
|
||||
(document.activeElement as any)?.blur?.();
|
||||
setMouseDown(true);
|
||||
updateColor(e);
|
||||
};
|
||||
|
||||
const handleMouseUp = (e: React.MouseEvent) => {
|
||||
const handleMouseUp = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setMouseDown(false);
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: React.MouseEvent) => {
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
if (!mouseDown) return;
|
||||
updateColor(e);
|
||||
};
|
||||
|
||||
const updateColor = (e: React.MouseEvent) => {
|
||||
const updateColor = (e: MouseEvent) => {
|
||||
const pos = e.currentTarget.getBoundingClientRect();
|
||||
const x = Math.min(Math.max(e.clientX - pos.left, 0), 225);
|
||||
const y = Math.min(Math.max(e.clientY - pos.top, 0), 125);
|
||||
@@ -104,41 +82,19 @@ function GradientSelector({ color, setColor }: { color: Color, setColor: (color:
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseMove={handleMouseMove}
|
||||
className={classes.gradientSelector}
|
||||
style={{
|
||||
background: `rgb(${255 * r1}, ${255 * g1}, ${255 * b1})`,
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
cursor: mouseDown ? "grabbing" : "grab",
|
||||
}}
|
||||
>
|
||||
<div style={{ background: "linear-gradient(to right, white, transparent)" }}>
|
||||
<div
|
||||
style={{
|
||||
pointerEvents: "none",
|
||||
position: "absolute",
|
||||
transform: `translate(${s * 225 - 6}px, ${125 - v * 125 - 6}px)`,
|
||||
width: "12px",
|
||||
height: "12px",
|
||||
borderRadius: "6px",
|
||||
boxShadow: "black 0 0 0 2px inset",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: "6px",
|
||||
boxShadow: "white 0 0 0 1px inset",
|
||||
}}
|
||||
/>
|
||||
<div className={classes.whiteGradient}>
|
||||
<div className={classes.cursor} style={{
|
||||
transform: `translate(${s * 225 - 6}px, ${125 - v * 125 - 6}px)`,
|
||||
}}>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: "225px",
|
||||
height: "125px",
|
||||
background: "linear-gradient(to top, black, transparent)",
|
||||
}}
|
||||
/>
|
||||
<div className={classes.blackGradient} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -155,31 +111,23 @@ function ControlPanel({ color, setColor }: { color: Color, setColor: (color: Col
|
||||
const [h, s, v] = rgbToHsv(r, g, b);
|
||||
const [mode, setMode] = useState<Mode>("Hex");
|
||||
|
||||
const handleHueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleHueChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const newH = parseFloat(e.target.value);
|
||||
const [newR, newG, newB] = hsvToRgb(newH, s, v);
|
||||
setColor([newR, newG, newB, a]);
|
||||
};
|
||||
|
||||
const handleAlphaChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleAlphaChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const newA = parseFloat(e.target.value);
|
||||
setColor([r, g, b, newA]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ margin: "10px", display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||
<div style={{ display: "flex", gap: "10px", alignItems: "center" }}>
|
||||
<div className={classes.controlPanel}>
|
||||
<div className={classes.controlPanelTop}>
|
||||
<ColorPreview color={color} setColor={setColor} />
|
||||
<div style={{ flexGrow: 1, display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||
<div
|
||||
style={{
|
||||
background: "linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%)",
|
||||
height: "18px",
|
||||
position: "relative",
|
||||
borderRadius: "9px",
|
||||
}}
|
||||
title="Hue"
|
||||
>
|
||||
<div className={classes.sliders}>
|
||||
<div className={classes.hueSlider} title="Hue">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
@@ -187,27 +135,10 @@ function ControlPanel({ color, setColor }: { color: Color, setColor: (color: Col
|
||||
step="1"
|
||||
value={h}
|
||||
onChange={handleHueChange}
|
||||
style={{
|
||||
margin: 0,
|
||||
background: "none",
|
||||
height: "16px",
|
||||
position: "absolute",
|
||||
top: "1px",
|
||||
left: "1px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
background: "white",
|
||||
borderRadius: "9px",
|
||||
overflow: "hidden",
|
||||
height: "18px",
|
||||
position: "relative",
|
||||
}}
|
||||
title="Alpha"
|
||||
>
|
||||
<div className="checker">
|
||||
<div className={classes.alphaSlider} title="Alpha">
|
||||
<div className={classes.checker}>
|
||||
<div
|
||||
style={{
|
||||
height: "18px",
|
||||
@@ -222,20 +153,12 @@ function ControlPanel({ color, setColor }: { color: Color, setColor: (color: Col
|
||||
step="0.01"
|
||||
value={a}
|
||||
onChange={handleAlphaChange}
|
||||
style={{
|
||||
margin: 0,
|
||||
background: "none",
|
||||
height: "16px",
|
||||
position: "absolute",
|
||||
top: "1px",
|
||||
left: "1px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{ display: "flex", gap: "10px", cursor: "ew-resize" }}
|
||||
className={classes.controlPanelBottom}
|
||||
onClick={() => setMode(nextMode(mode))}
|
||||
>
|
||||
{mode === "Rgb" ? (
|
||||
@@ -296,23 +219,14 @@ function ColorPreview({ color, setColor }: { color: Color, setColor: (color: Col
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid rgba(255, 255, 255, 0.25)",
|
||||
borderRadius: "50%",
|
||||
background: "white",
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className={classes.colorPreview}
|
||||
title="Color Preview"
|
||||
>
|
||||
<div className="checker">
|
||||
<div className={classes.checker}>
|
||||
<div
|
||||
className={classes.colorPreviewInner}
|
||||
style={{
|
||||
width: "35px",
|
||||
height: "35px",
|
||||
backgroundColor: `rgba(${255 * r}, ${255 * g}, ${255 * b}, ${a})`,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -332,7 +246,7 @@ function Hsva({ color, setColor }: { color: Color, setColor: (color: Color) => v
|
||||
short="H"
|
||||
kind="Degree"
|
||||
value={h}
|
||||
setValue={(newH: number) => {
|
||||
setValue={(newH) => {
|
||||
const [newR, newG, newB] = hsvToRgb(newH, s, v);
|
||||
setColor([newR, newG, newB, a]);
|
||||
}}
|
||||
@@ -342,7 +256,7 @@ function Hsva({ color, setColor }: { color: Color, setColor: (color: Color) => v
|
||||
short="S"
|
||||
kind="Percent"
|
||||
value={s}
|
||||
setValue={(newS: number) => {
|
||||
setValue={(newS) => {
|
||||
const [newR, newG, newB] = hsvToRgb(h, newS, v);
|
||||
setColor([newR, newG, newB, a]);
|
||||
}}
|
||||
@@ -352,7 +266,7 @@ function Hsva({ color, setColor }: { color: Color, setColor: (color: Color) => v
|
||||
short="V"
|
||||
kind="Percent"
|
||||
value={v}
|
||||
setValue={(newV: number) => {
|
||||
setValue={(newV) => {
|
||||
const [newR, newG, newB] = hsvToRgb(h, s, newV);
|
||||
setColor([newR, newG, newB, a]);
|
||||
}}
|
||||
@@ -362,7 +276,7 @@ function Hsva({ color, setColor }: { color: Color, setColor: (color: Color) => v
|
||||
short="A"
|
||||
kind="Percent"
|
||||
value={a}
|
||||
setValue={(newA: number) => {
|
||||
setValue={(newA) => {
|
||||
setColor([r, g, b, newA]);
|
||||
}}
|
||||
/>
|
||||
@@ -380,7 +294,7 @@ function Rgba({ color, setColor }: { color: Color, setColor: (color: Color) => v
|
||||
short="R"
|
||||
kind="Byte"
|
||||
value={r}
|
||||
setValue={(newR: number) => {
|
||||
setValue={(newR) => {
|
||||
setColor([newR, g, b, a]);
|
||||
}}
|
||||
/>
|
||||
@@ -389,7 +303,7 @@ function Rgba({ color, setColor }: { color: Color, setColor: (color: Color) => v
|
||||
short="G"
|
||||
kind="Byte"
|
||||
value={g}
|
||||
setValue={(newG: number) => {
|
||||
setValue={(newG) => {
|
||||
setColor([r, newG, b, a]);
|
||||
}}
|
||||
/>
|
||||
@@ -398,7 +312,7 @@ function Rgba({ color, setColor }: { color: Color, setColor: (color: Color) => v
|
||||
short="B"
|
||||
kind="Byte"
|
||||
value={b}
|
||||
setValue={(newB: number) => {
|
||||
setValue={(newB) => {
|
||||
setColor([r, g, newB, a]);
|
||||
}}
|
||||
/>
|
||||
@@ -407,7 +321,7 @@ function Rgba({ color, setColor }: { color: Color, setColor: (color: Color) => v
|
||||
short="A"
|
||||
kind="Percent"
|
||||
value={a}
|
||||
setValue={(newA: number) => {
|
||||
setValue={(newA) => {
|
||||
setColor([r, g, b, newA]);
|
||||
}}
|
||||
/>
|
||||
@@ -420,11 +334,11 @@ function Hex({ color, setColor }: { color: Color, setColor: (color: Color) => vo
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="color-input" title="Hexadecimal">
|
||||
<div className={classes.colorInput} title="Hexadecimal">
|
||||
<FormattedInput
|
||||
value={color}
|
||||
format={([r, g, b, _]: Color) => `#${Math.round(255 * r).toString(16).padStart(2, '0')}${Math.round(255 * g).toString(16).padStart(2, '0')}${Math.round(255 * b).toString(16).padStart(2, '0')}`.toUpperCase()}
|
||||
parse={(value: string) => {
|
||||
format={([r, g, b, _]) => `#${Math.round(255 * r).toString(16).padStart(2, '0')}${Math.round(255 * g).toString(16).padStart(2, '0')}${Math.round(255 * b).toString(16).padStart(2, '0')}`.toUpperCase()}
|
||||
parse={(value) => {
|
||||
const parsed = parseHex(value);
|
||||
if (parsed) {
|
||||
return [...parsed, a];
|
||||
@@ -440,7 +354,7 @@ function Hex({ color, setColor }: { color: Color, setColor: (color: Color) => vo
|
||||
short="A"
|
||||
kind="Percent"
|
||||
value={a}
|
||||
setValue={(newA: number) => {
|
||||
setValue={(newA) => {
|
||||
setColor([r, g, b, newA]);
|
||||
}}
|
||||
/>
|
||||
@@ -465,7 +379,7 @@ function FormattedInput<T>({ value, format, parse, setValue }: {
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const formatted = e.target.value;
|
||||
setFormatted(formatted);
|
||||
const newValue = parse(formatted);
|
||||
@@ -496,7 +410,7 @@ type Format = "Degree" | "Percent" | "Byte";
|
||||
|
||||
function ColorComponent({ title, short, kind, value, setValue }: { title: string, short: string, kind: Format, value: number, setValue: (value: number) => void }) {
|
||||
return (
|
||||
<div className="color-input" title={title}>
|
||||
<div className={classes.colorInput} title={title}>
|
||||
<FormattedInput
|
||||
value={value}
|
||||
format={(value) => kind === "Degree" ? `${value.toFixed(0)}°` : kind === "Percent" ? `${(100 * value).toFixed(0)}%` : `${(255 * value).toFixed(0)}`}
|
||||
@@ -517,40 +431,40 @@ function ColorComponent({ title, short, kind, value, setValue }: { title: string
|
||||
function PredefinedColors({ setColor }: { setColor: (color: Color) => void }) {
|
||||
const predefinedColors = [
|
||||
[
|
||||
[4.0, 0.79, 0.96],
|
||||
[340.0, 0.86, 0.91],
|
||||
[291.0, 0.78, 0.69],
|
||||
[262.0, 0.68, 0.71],
|
||||
[231.0, 0.65, 0.71],
|
||||
[207.0, 0.87, 0.95],
|
||||
[199.0, 0.99, 0.95],
|
||||
["Red", 244, 67, 54],
|
||||
["Pink", 233, 30, 99],
|
||||
["Purple", 156, 39, 176],
|
||||
["Deep Purple", 103, 58, 183],
|
||||
["Indigo", 63, 81, 181],
|
||||
["Blue", 33, 150, 243],
|
||||
["Light Blue", 3, 169, 244],
|
||||
],
|
||||
[
|
||||
[187.0, 1.0, 0.84],
|
||||
[174.0, 1.0, 0.58],
|
||||
[122.0, 0.56, 0.68],
|
||||
[88.0, 0.61, 0.77],
|
||||
[66.0, 0.75, 0.86],
|
||||
[54.0, 0.76, 1.0],
|
||||
[45.0, 0.98, 1.0],
|
||||
["Cyan", 0, 188, 212],
|
||||
["Teal", 0, 150, 136],
|
||||
["Green", 76, 175, 80],
|
||||
["Light Green", 139, 195, 74],
|
||||
["Lime", 205, 220, 57],
|
||||
["Yellow", 255, 235, 59],
|
||||
["Amber", 255, 193, 7],
|
||||
],
|
||||
[
|
||||
[36.0, 1.0, 1.0],
|
||||
[14.0, 0.86, 1.0],
|
||||
[16.0, 0.40, 0.48],
|
||||
[0.0, 0.0, 0.62],
|
||||
[200.0, 0.31, 0.54],
|
||||
[0.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 1.0],
|
||||
["Orange", 255, 152, 0],
|
||||
["Deep Orange", 255, 87, 34],
|
||||
["Brown", 121, 85, 72],
|
||||
["Grey", 158, 158, 158],
|
||||
["Blue Grey", 96, 125, 139],
|
||||
["Black", 0, 0, 0],
|
||||
["White", 255, 255, 255],
|
||||
],
|
||||
] as unknown as [[[number, number, number]]];
|
||||
] as unknown as [[[string, number, number, number]]];
|
||||
|
||||
return (
|
||||
<div style={{ margin: "10px", display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||
<div className={classes.predefinedColors}>
|
||||
{predefinedColors.map((hsv, index) => (
|
||||
<div key={index} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<div key={index} className={classes.predefinedColorsRow}>
|
||||
{hsv.map((color, i) => (
|
||||
<PredefinedColor key={i} hsv={color} setColor={setColor} />
|
||||
<PredefinedColor key={i} color={color} setColor={setColor} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
@@ -558,36 +472,34 @@ function PredefinedColors({ setColor }: { setColor: (color: Color) => void }) {
|
||||
);
|
||||
}
|
||||
|
||||
function PredefinedColor({ hsv, setColor }: { hsv: [number, number, number], setColor: (color: Color) => void }) {
|
||||
const [h, s, v] = hsv;
|
||||
const [r, g, b] = hsvToRgb(h, s, v);
|
||||
|
||||
function PredefinedColor({ color: [title, r, g, b], setColor }: { color: [string, number, number, number], setColor: (color: Color) => void }) {
|
||||
return (
|
||||
<button
|
||||
className="predefined-color"
|
||||
style={{ backgroundColor: `rgb(${255 * r}, ${255 * g}, ${255 * b})` }}
|
||||
onClick={() => setColor([r, g, b, 1.0])}
|
||||
className={classes.predefinedColor}
|
||||
style={{ backgroundColor: `rgb(${r}, ${g}, ${b})` }}
|
||||
onClick={() => setColor([r / 255, g / 255, b / 255, 1])}
|
||||
title={title}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function hsvToRgb(h: number, s: number, v: number) {
|
||||
const RECIP_60 = 1 / 60;
|
||||
const x_div_c = 1 - Math.abs((h * RECIP_60) % 2 - 1);
|
||||
const xDivC = 1 - Math.abs((h / 60) % 2 - 1);
|
||||
|
||||
const [rc, rx, gc, gx, bc, bx] = h < 60 ? [1, 0, 0, 1, 0, 0] :
|
||||
h < 120 ? [0, 1, 1, 0, 0, 0] :
|
||||
h < 180 ? [0, 0, 1, 0, 0, 1] :
|
||||
h < 240 ? [0, 0, 0, 1, 1, 0] :
|
||||
h < 300 ? [0, 1, 0, 0, 1, 0] :
|
||||
[1, 0, 0, 0, 0, 1];
|
||||
const value_times_255 = v;
|
||||
const c_times_255 = value_times_255 * s;
|
||||
const x_times_255 = x_div_c * c_times_255;
|
||||
const m_times_255 = value_times_255 - c_times_255;
|
||||
|
||||
const r = rc * c_times_255 + rx * x_times_255 + m_times_255;
|
||||
const g = gc * c_times_255 + gx * x_times_255 + m_times_255;
|
||||
const b = bc * c_times_255 + bx * x_times_255 + m_times_255;
|
||||
const c = v * s;
|
||||
const x = xDivC * c;
|
||||
const m = v - c;
|
||||
|
||||
const r = rc * c + rx * x + m;
|
||||
const g = gc * c + gx * x + m;
|
||||
const b = bc * c + bx * x + m;
|
||||
|
||||
return [r, g, b];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import React, { createContext, useContext, ReactNode } from "react";
|
||||
import { expect } from "../util/OptionUtil";
|
||||
|
||||
import * as classes from "../css/ContextMenu.module.css";
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
const InfoContext = createContext<{ onClose: () => void } | undefined>(undefined);
|
||||
|
||||
export function ContextMenu({
|
||||
position,
|
||||
onClose,
|
||||
children,
|
||||
}: {
|
||||
position: Position;
|
||||
onClose: () => void;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<InfoContext.Provider value={{ onClose }}>
|
||||
<nav
|
||||
role="menu"
|
||||
className={classes.contextMenu}
|
||||
style={{
|
||||
top: `${position.y}px`,
|
||||
left: `${position.x}px`,
|
||||
}}
|
||||
>
|
||||
<div className={classes.overlay} onClick={onClose} />
|
||||
<div className={classes.panel}>
|
||||
{children}
|
||||
</div>
|
||||
</nav>
|
||||
</InfoContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function MenuItem({
|
||||
children,
|
||||
onClick,
|
||||
className,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
}) {
|
||||
const { onClose } = expect(useContext(InfoContext), "MenuItem must be used within a ContextMenu");
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${classes.entry} ${className}`}
|
||||
role="menuitem"
|
||||
onClick={() => {
|
||||
onClick();
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Separator() {
|
||||
return <hr className={classes.hr} />;
|
||||
}
|
||||
+3
-3
@@ -8,8 +8,8 @@ export interface Props {
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
title: string | JSX.Element,
|
||||
description: string | JSX.Element,
|
||||
title: string | React.JSX.Element,
|
||||
description: string | React.JSX.Element,
|
||||
textInput?: boolean,
|
||||
defaultText?: string,
|
||||
buttons: string[],
|
||||
@@ -71,7 +71,7 @@ export default class DialogContainer extends React.Component<Props, State> {
|
||||
|
||||
public render() {
|
||||
return <dialog
|
||||
ref={(element) => dialogElement = element}
|
||||
ref={(element) => { dialogElement = element; }}
|
||||
onKeyDown={(e) => {
|
||||
if (e?.key === "ArrowLeft") {
|
||||
e.preventDefault();
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { Option } from "../util/OptionUtil";
|
||||
|
||||
export function resolveEmbed(uri: string): Option<JSX.Element> {
|
||||
export function resolveEmbed(uri: string): Option<React.JSX.Element> {
|
||||
const youtube = tryYoutubeFromUri(uri);
|
||||
if (youtube != null) {
|
||||
return youtube;
|
||||
@@ -13,7 +13,7 @@ export function resolveEmbed(uri: string): Option<JSX.Element> {
|
||||
return <p><a href={uri} target="_blank">{uri}</a></p>;
|
||||
}
|
||||
|
||||
function tryYoutubeFromUri(uri: string): Option<JSX.Element> {
|
||||
function tryYoutubeFromUri(uri: string): Option<React.JSX.Element> {
|
||||
const youtubeBase = /https?:\/\/www\.youtube\.com\/watch\?v=([A-z0-9-]+)&?/;
|
||||
let result = youtubeBase.exec(uri);
|
||||
if (result === null) {
|
||||
@@ -26,7 +26,7 @@ function tryYoutubeFromUri(uri: string): Option<JSX.Element> {
|
||||
return null;
|
||||
}
|
||||
|
||||
function tryTwitchFromUri(uri: string): Option<JSX.Element> {
|
||||
function tryTwitchFromUri(uri: string): Option<React.JSX.Element> {
|
||||
const twitchBase = /https?:\/\/(www\.)?(go\.)?twitch\.tv\/.*\/v\/(\w+)&?/;
|
||||
const twitchBase2 = /https?:\/\/(www\.)?(go\.)?twitch\.tv\/videos\/(\w+)&?/;
|
||||
let result = twitchBase.exec(uri);
|
||||
@@ -39,7 +39,7 @@ function tryTwitchFromUri(uri: string): Option<JSX.Element> {
|
||||
return null;
|
||||
}
|
||||
|
||||
function videoIframe(videoSource: string): JSX.Element {
|
||||
function videoIframe(videoSource: string): React.JSX.Element {
|
||||
return (
|
||||
<div className="video-outer-container">
|
||||
<div className="video-inner-container">
|
||||
@@ -55,11 +55,11 @@ function videoIframe(videoSource: string): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
function resolveYoutube(videoId: string): JSX.Element {
|
||||
function resolveYoutube(videoId: string): React.JSX.Element {
|
||||
return videoIframe(`https://www.youtube.com/embed/${videoId}?wmode=transparent`);
|
||||
}
|
||||
|
||||
function resolveTwitch(videoId: string): JSX.Element {
|
||||
function resolveTwitch(videoId: string): React.JSX.Element {
|
||||
const domain = window.location.hostname;
|
||||
return videoIframe(`https://player.twitch.tv/?video=${videoId}&parent=${domain}&autoplay=false`);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export default class HotkeyButton extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
public render() {
|
||||
let buttonText: Option<string> | JSX.Element = null;
|
||||
let buttonText: Option<string> | React.JSX.Element = null;
|
||||
if (this.props.value != null) {
|
||||
buttonText = this.state.resolvedKey;
|
||||
} else if (this.state.listener != null) {
|
||||
|
||||
+155
-140
@@ -1,5 +1,4 @@
|
||||
import * as React from "react";
|
||||
import { ContextMenu, ContextMenuTrigger, MenuItem } from "react-contextmenu";
|
||||
import React, { useState } from "react";
|
||||
import * as LiveSplit from "../livesplit-core";
|
||||
import { SettingsComponent } from "./Settings";
|
||||
import { UrlCache } from "../util/UrlCache";
|
||||
@@ -7,6 +6,7 @@ import Layout from "../layout/Layout";
|
||||
import { WebRenderer } from "../livesplit-core/livesplit_core";
|
||||
import { GeneralSettings } from "./MainSettings";
|
||||
import { LSOCommandSink } from "./LSOCommandSink";
|
||||
import { ContextMenu, MenuItem, Separator, Position } from "./ContextMenu";
|
||||
|
||||
import "../css/LayoutEditor.scss";
|
||||
|
||||
@@ -32,7 +32,7 @@ export interface State {
|
||||
|
||||
interface Callbacks {
|
||||
onResize(width: number, height: number): void,
|
||||
renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element): JSX.Element,
|
||||
renderViewWithSidebar(renderedView: React.JSX.Element, sidebarContent: React.JSX.Element): React.JSX.Element,
|
||||
closeLayoutEditor(save: boolean): void,
|
||||
}
|
||||
|
||||
@@ -91,13 +91,6 @@ export class LayoutEditor extends React.Component<Props, State> {
|
||||
);
|
||||
});
|
||||
|
||||
let contextTrigger: any = null;
|
||||
const toggleMenu = (e: any) => {
|
||||
if (contextTrigger) {
|
||||
contextTrigger.handleContextClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
const settings = this.state.showComponentSettings
|
||||
? (
|
||||
<SettingsComponent
|
||||
@@ -136,136 +129,11 @@ export class LayoutEditor extends React.Component<Props, State> {
|
||||
<div className="layout-editor-inner-container">
|
||||
<div className="layout-editor-inner">
|
||||
<div className="btn-group">
|
||||
<ContextMenuTrigger id="add-button-context-menu" ref={(c) => contextTrigger = c}>
|
||||
<button
|
||||
aria-label="Add Component"
|
||||
onClick={toggleMenu}
|
||||
>
|
||||
<i className="fa fa-plus" aria-hidden="true"></i>
|
||||
</button>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenu id="add-button-context-menu">
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.TitleComponent)}>
|
||||
Title
|
||||
<span className="tooltip-text">
|
||||
Shows the name of the game and the category that is being run. Additionally, the game icon, the attempt count, and the total number of successfully finished runs can be shown.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.GraphComponent)}>
|
||||
Graph
|
||||
<span className="tooltip-text">
|
||||
Visualizes how far the current run has been ahead or behind the chosen comparison throughout the whole run. All the individual deltas are shown as points on the graph.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.SplitsComponent)}>
|
||||
Splits
|
||||
<span className="tooltip-text">
|
||||
The main component for visualizing all the split times. Each segment is shown in a tabular fashion showing the segment icon, segment name, the delta compared to the chosen comparison, and the split time. The list provides scrolling functionality, so not every segment needs to be shown all the time.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem divider />
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.DetailedTimerComponent)}>
|
||||
Detailed Timer
|
||||
<span className="tooltip-text">
|
||||
Shows two timers, one for the total time of the current run and one showing the time of just the current segment. Other information, like segment times of up to two comparisons, the segment icon, and the segment name, can also be shown.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.TimerComponent)}>
|
||||
Timer
|
||||
<span className="tooltip-text">
|
||||
Shows the total time of the current run as a digital clock. The color of the time shown is based on a how well the current run is doing compared to the chosen comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem divider />
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.CurrentComparisonComponent)}>
|
||||
Current Comparison
|
||||
<span className="tooltip-text">
|
||||
Shows the name of the comparison that the timer is currently comparing against.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.CurrentPaceComponent)}>
|
||||
Current Pace
|
||||
<span className="tooltip-text">
|
||||
Shows a prediction for the current run's final time. The remainder of the run is predicted based on the chosen comparison for the component. For example, the "Best Segments" comparison can be chosen to show the best possible final time for the current run based on the Sum of Best Segments.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.DeltaComponent)}>
|
||||
Delta
|
||||
<span className="tooltip-text">
|
||||
Shows how far ahead or behind the current run is compared to the chosen comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.PbChanceComponent)}>
|
||||
PB Chance
|
||||
<span className="tooltip-text">
|
||||
Shows how likely it is for the active run to beat the personal best. If there is no active run, it shows the general chance of beating the personal best. During a run, it actively changes based on how well the run is going.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.PossibleTimeSaveComponent)}>
|
||||
Possible Time Save
|
||||
<span className="tooltip-text">
|
||||
Shows how much time you can save on the current segment compared to the chosen comparison, based on the best segment time of the segment. This component also allows showing the "Total Possible Time Save" for the remainder of the current run.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.PreviousSegmentComponent)}>
|
||||
Previous Segment
|
||||
<span className="tooltip-text">
|
||||
Shows how much time was saved or lost during the previous segment based on the chosen comparison. Additionally, the potential time save for the previous segment can be displayed. This component switches to a "Live Segment" view that shows the active time loss whenever you are losing time on the current segment.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.SegmentTimeComponent)}>
|
||||
Segment Time
|
||||
<span className="tooltip-text">
|
||||
Shows the time for the current segment for the chosen comparison. If no comparison is specified it uses the timer's current comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.SumOfBestComponent)}>
|
||||
Sum of Best Segments
|
||||
<span className="tooltip-text">
|
||||
Shows the fastest possible time to complete a run of the current category, based on information collected from all the previous runs. This often matches up with the sum of the best segment times of all the segments, but that may not always be the case, as skipped segments may introduce combined segments that may be faster than the actual sum of their best segment times. The name is therefore a bit misleading, but sticks around for historical reasons.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.TextComponent)}>
|
||||
Text
|
||||
<span className="tooltip-text">
|
||||
Shows the text that you specify. This can either be a single centered text, or split up into a left and right text, which is suitable for a situation where you have a label and a value. There is also the option of showing a custom variable that you specify in the splits editor.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.TotalPlaytimeComponent)}>
|
||||
Total Playtime
|
||||
<span className="tooltip-text">
|
||||
Shows the total amount of time that the current category has been played for.
|
||||
</span>
|
||||
</MenuItem>
|
||||
{
|
||||
this.props.allVariables.size > 0 && <MenuItem divider />
|
||||
}
|
||||
{
|
||||
this.props.allVariables.size > 0 && Array.from(this.props.allVariables).map((name) => {
|
||||
return (
|
||||
<MenuItem className="tooltip" key={name} onClick={(_) => this.addVariable(name)}>
|
||||
{name}
|
||||
<span className="tooltip-text">
|
||||
Creates a text component that shows the value of the custom variable "{name}".
|
||||
</span>
|
||||
</MenuItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
<MenuItem divider />
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.BlankSpaceComponent)}>
|
||||
Blank Space
|
||||
<span className="tooltip-text">
|
||||
An empty component that doesn't show anything other than a background. It mostly serves as padding between other components.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComponent(LiveSplit.SeparatorComponent)}>
|
||||
Separator
|
||||
<span className="tooltip-text">
|
||||
A simple component that just renders a separator between components.
|
||||
</span>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
<AddComponentButton
|
||||
allVariables={this.props.allVariables}
|
||||
addVariable={(v) => this.addVariable(v)}
|
||||
addComponent={(v) => this.addComponent(v)}
|
||||
/>
|
||||
<button
|
||||
aria-label="Remove Component"
|
||||
onClick={(_) => this.removeComponent()}
|
||||
@@ -426,3 +294,150 @@ export class LayoutEditor extends React.Component<Props, State> {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
function AddComponentButton({
|
||||
allVariables,
|
||||
addVariable,
|
||||
addComponent,
|
||||
}: {
|
||||
allVariables: Set<string>;
|
||||
addVariable: (name: string) => void;
|
||||
addComponent: (componentClass: any) => void;
|
||||
}) {
|
||||
const [position, setPosition] = useState<Position | null>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
aria-label="Add Component"
|
||||
onClick={(e) => setPosition({ x: e.clientX, y: e.clientY })}
|
||||
>
|
||||
<i className="fa fa-plus" aria-hidden="true"></i>
|
||||
</button>
|
||||
{position && (
|
||||
<ContextMenu position={position} onClose={() => setPosition(null)}>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.TitleComponent)}>
|
||||
Title
|
||||
<span className="tooltip-text">
|
||||
Shows the name of the game and the category that is being run. Additionally, the game icon, the attempt count, and the total number of successfully finished runs can be shown.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.GraphComponent)}>
|
||||
Graph
|
||||
<span className="tooltip-text">
|
||||
Visualizes how far the current run has been ahead or behind the chosen comparison throughout the whole run. All the individual deltas are shown as points on the graph.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.SplitsComponent)}>
|
||||
Splits
|
||||
<span className="tooltip-text">
|
||||
The main component for visualizing all the split times. Each segment is shown in a tabular fashion showing the segment icon, segment name, the delta compared to the chosen comparison, and the split time. The list provides scrolling functionality, so not every segment needs to be shown all the time.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.DetailedTimerComponent)}>
|
||||
Detailed Timer
|
||||
<span className="tooltip-text">
|
||||
Shows two timers, one for the total time of the current run and one showing the time of just the current segment. Other information, like segment times of up to two comparisons, the segment icon, and the segment name, can also be shown.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.TimerComponent)}>
|
||||
Timer
|
||||
<span className="tooltip-text">
|
||||
Shows the total time of the current run as a digital clock. The color of the time shown is based on a how well the current run is doing compared to the chosen comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.CurrentComparisonComponent)}>
|
||||
Current Comparison
|
||||
<span className="tooltip-text">
|
||||
Shows the name of the comparison that the timer is currently comparing against.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.CurrentPaceComponent)}>
|
||||
Current Pace
|
||||
<span className="tooltip-text">
|
||||
Shows a prediction for the current run's final time. The remainder of the run is predicted based on the chosen comparison for the component. For example, the "Best Segments" comparison can be chosen to show the best possible final time for the current run based on the Sum of Best Segments.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.DeltaComponent)}>
|
||||
Delta
|
||||
<span className="tooltip-text">
|
||||
Shows how far ahead or behind the current run is compared to the chosen comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.PbChanceComponent)}>
|
||||
PB Chance
|
||||
<span className="tooltip-text">
|
||||
Shows how likely it is for the active run to beat the personal best. If there is no active run, it shows the general chance of beating the personal best. During a run, it actively changes based on how well the run is going.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.PossibleTimeSaveComponent)}>
|
||||
Possible Time Save
|
||||
<span className="tooltip-text">
|
||||
Shows how much time you can save on the current segment compared to the chosen comparison, based on the best segment time of the segment. This component also allows showing the "Total Possible Time Save" for the remainder of the current run.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.PreviousSegmentComponent)}>
|
||||
Previous Segment
|
||||
<span className="tooltip-text">
|
||||
Shows how much time was saved or lost during the previous segment based on the chosen comparison. Additionally, the potential time save for the previous segment can be displayed. This component switches to a "Live Segment" view that shows the active time loss whenever you are losing time on the current segment.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.SegmentTimeComponent)}>
|
||||
Segment Time
|
||||
<span className="tooltip-text">
|
||||
Shows the time for the current segment for the chosen comparison. If no comparison is specified it uses the timer's current comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.SumOfBestComponent)}>
|
||||
Sum of Best Segments
|
||||
<span className="tooltip-text">
|
||||
Shows the fastest possible time to complete a run of the current category, based on information collected from all the previous runs. This often matches up with the sum of the best segment times of all the segments, but that may not always be the case, as skipped segments may introduce combined segments that may be faster than the actual sum of their best segment times. The name is therefore a bit misleading, but sticks around for historical reasons.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.TextComponent)}>
|
||||
Text
|
||||
<span className="tooltip-text">
|
||||
Shows the text that you specify. This can either be a single centered text, or split up into a left and right text, which is suitable for a situation where you have a label and a value. There is also the option of showing a custom variable that you specify in the splits editor.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.TotalPlaytimeComponent)}>
|
||||
Total Playtime
|
||||
<span className="tooltip-text">
|
||||
Shows the total amount of time that the current category has been played for.
|
||||
</span>
|
||||
</MenuItem>
|
||||
{
|
||||
allVariables.size > 0 && <Separator />
|
||||
}
|
||||
{
|
||||
allVariables.size > 0 && Array.from(allVariables).map((name) => {
|
||||
return (
|
||||
<MenuItem className="contextmenu-item tooltip" key={name} onClick={() => addVariable(name)}>
|
||||
{name}
|
||||
<span className="tooltip-text">
|
||||
Creates a text component that shows the value of the custom variable "{name}".
|
||||
</span>
|
||||
</MenuItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
<Separator />
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.BlankSpaceComponent)}>
|
||||
Blank Space
|
||||
<span className="tooltip-text">
|
||||
An empty component that doesn't show anything other than a background. It mostly serves as padding between other components.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={() => addComponent(LiveSplit.SeparatorComponent)}>
|
||||
Separator
|
||||
<span className="tooltip-text">
|
||||
A simple component that just renders a separator between components.
|
||||
</span>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ interface Callbacks {
|
||||
openSplitsView(): void,
|
||||
openMainSettings(): void,
|
||||
openTimerView(): void,
|
||||
renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element): JSX.Element,
|
||||
renderViewWithSidebar(renderedView: React.JSX.Element, sidebarContent: React.JSX.Element): React.JSX.Element,
|
||||
saveLayout(): void,
|
||||
onServerConnectionOpened(serverConnection: LiveSplitServer): void,
|
||||
onServerConnectionClosed(): void,
|
||||
|
||||
@@ -25,7 +25,7 @@ import { LSOCommandSink } from "./LSOCommandSink";
|
||||
import DialogContainer from "./Dialog";
|
||||
import { createHotkeys, HotkeyImplementation } from "../platform/Hotkeys";
|
||||
|
||||
import * as variables from "../css/variables.scss";
|
||||
import * as variables from "../css/variables.icss.scss";
|
||||
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import "../css/LiveSplit.scss";
|
||||
@@ -426,7 +426,7 @@ export class LiveSplit extends React.Component<Props, State> {
|
||||
</>;
|
||||
}
|
||||
|
||||
public renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element) {
|
||||
public renderViewWithSidebar(renderedView: React.JSX.Element, sidebarContent: React.JSX.Element) {
|
||||
return (
|
||||
<div className={this.state.isDesktop ? "" : "is-mobile"}>
|
||||
<Sidebar
|
||||
|
||||
@@ -49,7 +49,7 @@ export interface State {
|
||||
}
|
||||
|
||||
interface Callbacks {
|
||||
renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element): JSX.Element,
|
||||
renderViewWithSidebar(renderedView: React.JSX.Element, sidebarContent: React.JSX.Element): React.JSX.Element,
|
||||
closeMainSettings(save: boolean, newGeneralSettings: GeneralSettings): void,
|
||||
onServerConnectionOpened(serverConnection: LiveSplitServer): void,
|
||||
onServerConnectionClosed(): void,
|
||||
|
||||
+328
-253
@@ -1,5 +1,4 @@
|
||||
import * as React from "react";
|
||||
import { ContextMenu, ContextMenuTrigger, MenuItem } from "react-contextmenu";
|
||||
import * as LiveSplit from "../livesplit-core";
|
||||
import { FILE_EXT_IMAGES, FILE_EXT_SPLITS, openFileAsArrayBuffer } from "../util/FileUtil";
|
||||
import { TextBox } from "./TextBox";
|
||||
@@ -26,6 +25,7 @@ import { UrlCache } from "../util/UrlCache";
|
||||
import { GeneralSettings } from "./MainSettings";
|
||||
import { showDialog } from "./Dialog";
|
||||
import { corsBustingFetch } from "../platform/CORS";
|
||||
import { ContextMenu, MenuItem, Position } from "./ContextMenu";
|
||||
|
||||
import "../css/RunEditor.scss";
|
||||
|
||||
@@ -48,7 +48,7 @@ export interface State {
|
||||
}
|
||||
|
||||
interface Callbacks {
|
||||
renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element): JSX.Element,
|
||||
renderViewWithSidebar(renderedView: React.JSX.Element, sidebarContent: React.JSX.Element): React.JSX.Element,
|
||||
closeRunEditor(save: boolean): void,
|
||||
}
|
||||
|
||||
@@ -131,13 +131,6 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
private renderView() {
|
||||
const gameIcon = this.getGameIcon();
|
||||
|
||||
let gameIconContextTrigger: any = null;
|
||||
const gameIconToggleMenu = (e: any) => {
|
||||
if (gameIconContextTrigger) {
|
||||
gameIconContextTrigger.handleContextClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
const tab = this.getTab();
|
||||
|
||||
const { category, categoryNames } = this.getCurrentCategoriesInfo();
|
||||
@@ -145,58 +138,14 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
return (
|
||||
<div className="run-editor">
|
||||
<div className="run-editor-info">
|
||||
<div
|
||||
className="game-icon-container"
|
||||
onClick={(e) => {
|
||||
gameIconToggleMenu(e);
|
||||
}}
|
||||
>
|
||||
<ContextMenuTrigger
|
||||
id="game-icon-context-menu"
|
||||
ref={(c) => gameIconContextTrigger = c}
|
||||
>
|
||||
{
|
||||
gameIcon !== undefined &&
|
||||
<img
|
||||
src={gameIcon}
|
||||
className="game-icon-image"
|
||||
/>
|
||||
}
|
||||
</ContextMenuTrigger>
|
||||
</div>
|
||||
<ContextMenu id="game-icon-context-menu">
|
||||
<MenuItem className="tooltip" onClick={(_) => this.changeGameIcon()}>
|
||||
Set Icon
|
||||
<span className="tooltip-text">
|
||||
Allows you to choose an image file to set as the game's icon. Certain file formats may not work everywhere.
|
||||
</span>
|
||||
</MenuItem>
|
||||
{
|
||||
this.props.generalSettings.speedrunComIntegration && <>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.downloadBoxArt()}>
|
||||
Download Box Art
|
||||
<span className="tooltip-text">
|
||||
Attempts to download the box art of the game from speedrun.com, to set as the game's icon.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.downloadIcon()}>
|
||||
Download Icon
|
||||
<span className="tooltip-text">
|
||||
Attempts to download the icon of the game from speedrun.com.
|
||||
</span>
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
{
|
||||
gameIcon !== undefined &&
|
||||
<MenuItem className="tooltip" onClick={(_) => this.removeGameIcon()}>
|
||||
Remove Icon
|
||||
<span className="tooltip-text">
|
||||
Removes the icon of the game.
|
||||
</span>
|
||||
</MenuItem>
|
||||
}
|
||||
</ContextMenu>
|
||||
<GameIcon
|
||||
gameIcon={gameIcon}
|
||||
speedrunComIntegration={this.props.generalSettings.speedrunComIntegration}
|
||||
changeGameIcon={() => this.changeGameIcon()}
|
||||
downloadBoxArt={() => this.downloadBoxArt()}
|
||||
downloadIcon={() => this.downloadIcon()}
|
||||
removeGameIcon={() => this.removeGameIcon()}
|
||||
/>
|
||||
<div className="run-editor-info-table">
|
||||
<div className="info-table-row">
|
||||
<div className="info-table-cell">
|
||||
@@ -322,21 +271,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
private renderSegmentListButtons(): JSX.Element {
|
||||
let otherButtonContextTrigger: any = null;
|
||||
const otherButtonToggleMenu = (e: any) => {
|
||||
if (otherButtonContextTrigger) {
|
||||
otherButtonContextTrigger.handleContextClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
let comparisonsButtonContextTrigger: any = null;
|
||||
const comparisonsButtonToggleMenu = (e: any) => {
|
||||
if (comparisonsButtonContextTrigger) {
|
||||
comparisonsButtonContextTrigger.handleContextClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
private renderSegmentListButtons(): React.JSX.Element {
|
||||
return (
|
||||
<div className="btn-group">
|
||||
<button onClick={(_) => this.insertSegmentAbove()}>
|
||||
@@ -363,73 +298,22 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
>
|
||||
Move Down
|
||||
</button>
|
||||
<button onClick={(e) => comparisonsButtonToggleMenu(e)}>
|
||||
<ContextMenuTrigger
|
||||
id="comparisons-button-context-menu"
|
||||
ref={(c) => comparisonsButtonContextTrigger = c}
|
||||
>
|
||||
Comparisons…
|
||||
</ContextMenuTrigger>
|
||||
</button>
|
||||
<button onClick={(e) => otherButtonToggleMenu(e)}>
|
||||
<ContextMenuTrigger
|
||||
id="other-button-context-menu"
|
||||
ref={(c) => otherButtonContextTrigger = c}
|
||||
>
|
||||
Cleaning…
|
||||
</ContextMenuTrigger>
|
||||
</button>
|
||||
<ContextMenu id="comparisons-button-context-menu">
|
||||
<MenuItem className="tooltip" onClick={(_) => this.addComparison()}>
|
||||
Add Comparison
|
||||
<span className="tooltip-text">
|
||||
Adds a new custom comparison where you can store any times that you would like.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.importComparison()}>
|
||||
Import Comparison
|
||||
<span className="tooltip-text">
|
||||
Imports the Personal Best of a splits file you provide as a comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.generateGoalComparison()}>
|
||||
Generate Goal Comparison
|
||||
<span className="tooltip-text">
|
||||
Generates a custom goal comparison based on a goal time that you can specify. The comparison's times are automatically balanced based on the segment history such that it roughly represents what the split times for the goal time would look like. Since it is populated by the segment history, the goal times are capped to a range between the sum of the best segments and the sum of the worst segments. The comparison is only populated for the selected timing method. The other timing method's comparison times are not modified by this, so you can generate it again with the other timing method to generate the comparison times for both timing methods.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.copyComparison()}>
|
||||
Copy Comparison
|
||||
<span className="tooltip-text">
|
||||
Copies any existing comparison, including the Personal Best or even any other automatically provided comparison as a new custom comparison. You could for example use this to keep the Latest Run around as a comparison that exists for as long as you want it to.
|
||||
</span>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
<ContextMenu id="other-button-context-menu">
|
||||
<MenuItem className="tooltip" onClick={(_) => this.clearHistory()}>
|
||||
Clear Only History
|
||||
<span className="tooltip-text">
|
||||
Splits store the entire history of all runs, including every segment time. This information is used by various components. You can clear the history with this. The personal best, the best segment times, and the comparisons will not be affected.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.clearTimes()}>
|
||||
Clear All Times
|
||||
<span className="tooltip-text">
|
||||
This removes all the times from the splits, including all the history, such that the splits are completely empty, as if they were just created.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.cleanSumOfBest()}>
|
||||
Clean Sum of Best
|
||||
<span className="tooltip-text">
|
||||
Allows you to interactively remove potential issues in the segment history that lead to an inaccurate Sum of Best. If you skip a split, whenever you will do the next split, the combined segment time might be faster than the sum of the individual best segments. This will point out all such occurrences and allow you to delete them individually if any of them seem wrong.
|
||||
</span>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
<ComparisonsButton
|
||||
addComparison={() => this.addComparison()}
|
||||
importComparison={() => this.importComparison()}
|
||||
generateGoalComparison={() => this.generateGoalComparison()}
|
||||
copyComparison={() => this.copyComparison()}
|
||||
/>
|
||||
<CleaningButton
|
||||
clearHistory={() => this.clearHistory()}
|
||||
clearTimes={() => this.clearTimes()}
|
||||
cleanSumOfBest={() => this.cleanSumOfBest()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderAssociateRunButton(): JSX.Element {
|
||||
private renderAssociateRunButton(): React.JSX.Element {
|
||||
if (this.props.generalSettings.speedrunComIntegration) {
|
||||
return (
|
||||
<button onClick={(_) => this.interactiveAssociateRunOrOpenPage()}>
|
||||
@@ -441,7 +325,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
private renderRulesButtons(): JSX.Element {
|
||||
private renderRulesButtons(): React.JSX.Element {
|
||||
return (
|
||||
<div className="btn-group">
|
||||
{this.renderAssociateRunButton()}
|
||||
@@ -449,7 +333,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private renderVariablesButtons(): JSX.Element {
|
||||
private renderVariablesButtons(): React.JSX.Element {
|
||||
return (
|
||||
<div className="btn-group">
|
||||
{this.renderAssociateRunButton()}
|
||||
@@ -473,7 +357,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
private renderSideButtons(tab: Tab, category: Option<Category>): JSX.Element {
|
||||
private renderSideButtons(tab: Tab, category: Option<Category>): React.JSX.Element {
|
||||
switch (tab) {
|
||||
case Tab.RealTime:
|
||||
case Tab.GameTime:
|
||||
@@ -487,7 +371,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
private renderTab(tab: Tab, category: Option<Category>): JSX.Element {
|
||||
private renderTab(tab: Tab, category: Option<Category>): React.JSX.Element {
|
||||
switch (tab) {
|
||||
case Tab.RealTime:
|
||||
case Tab.GameTime:
|
||||
@@ -501,7 +385,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
private renderLeaderboardButtons(category: Option<Category>): JSX.Element {
|
||||
private renderLeaderboardButtons(category: Option<Category>): React.JSX.Element {
|
||||
const gameInfo = getGameInfo(this.state.editor.game);
|
||||
if (gameInfo === undefined) {
|
||||
return this.renderRulesButtons();
|
||||
@@ -745,7 +629,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
this.update();
|
||||
}
|
||||
|
||||
private renderVariablesTab(category: Option<Category>): JSX.Element {
|
||||
private renderVariablesTab(category: Option<Category>): React.JSX.Element {
|
||||
const metadata = this.state.editor.metadata;
|
||||
const gameInfo = getGameInfo(this.state.editor.game);
|
||||
const fields: ExtendedSettingsDescriptionFieldJson[] = [];
|
||||
@@ -927,7 +811,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private renderLeaderboard(category: Option<Category>): JSX.Element {
|
||||
private renderLeaderboard(category: Option<Category>): React.JSX.Element {
|
||||
const leaderboard = this.getCurrentLeaderboard(category);
|
||||
if (leaderboard === undefined) {
|
||||
return <div />;
|
||||
@@ -1177,7 +1061,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private renderRulesTab(category: Option<Category>): JSX.Element {
|
||||
private renderRulesTab(category: Option<Category>): React.JSX.Element {
|
||||
let rules = null;
|
||||
if (category != null && category.rules != null) {
|
||||
rules = <Markdown markdown={category.rules} />;
|
||||
@@ -1223,7 +1107,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private renderSegmentsTable(): JSX.Element {
|
||||
private renderSegmentsTable(): React.JSX.Element {
|
||||
return (
|
||||
<table className="table run-editor-tab run-editor-table">
|
||||
<thead className="table-header">
|
||||
@@ -1235,30 +1119,13 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
<th>Best Segment</th>
|
||||
{
|
||||
this.state.editor.comparison_names.map((comparison, comparisonIndex) => {
|
||||
let contextTrigger: any = null;
|
||||
const toggleMenu = (e: any) => {
|
||||
if (contextTrigger) {
|
||||
contextTrigger.handleContextClick(e);
|
||||
}
|
||||
};
|
||||
const id = `comparison-${comparisonIndex}-context-menu`;
|
||||
return (
|
||||
<th
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={(e) => toggleMenu(e)}
|
||||
draggable
|
||||
<CustomComparison
|
||||
comparison={comparison}
|
||||
onDragStart={(e) => {
|
||||
e.dataTransfer.setData("text/plain", "");
|
||||
this.dragIndex = comparisonIndex;
|
||||
}}
|
||||
onDragOver={(e) => {
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
e.dataTransfer.dropEffect = "move";
|
||||
}}
|
||||
onDragEnd={(_) => this.update()}
|
||||
onDrop={(e) => {
|
||||
if (e.stopPropagation) {
|
||||
@@ -1267,40 +1134,10 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
this.props.editor.moveComparison(this.dragIndex, comparisonIndex);
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
<ContextMenuTrigger
|
||||
id={id}
|
||||
ref={(c) => contextTrigger = c}
|
||||
>
|
||||
{comparison}
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenu id={id}>
|
||||
<MenuItem className="tooltip" onClick={(_) =>
|
||||
this.renameComparison(comparison)
|
||||
}>
|
||||
Rename
|
||||
<span className="tooltip-text">
|
||||
Choose a new name for the custom comparison. There are reserved names that can't be used. You also can't have duplicate names.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) =>
|
||||
this.copyComparison(comparison)
|
||||
}>
|
||||
Copy
|
||||
<span className="tooltip-text">
|
||||
Creates a copy of the custom comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) =>
|
||||
this.removeComparison(comparison)
|
||||
}>
|
||||
Remove
|
||||
<span className="tooltip-text">
|
||||
Removes the custom comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</th>
|
||||
renameComparison={() => this.renameComparison(comparison)}
|
||||
copyComparison={() => this.copyComparison(comparison)}
|
||||
removeComparison={() => this.removeComparison(comparison)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
@@ -1310,15 +1147,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
{
|
||||
this.state.editor.segments.map((s, segmentIndex) => {
|
||||
const segmentIcon = this.getSegmentIconUrl(segmentIndex);
|
||||
const segmentIconContextMenuId = `segment-icon-${segmentIndex}-context-menu`;
|
||||
let segmentIconContextTrigger: any = null;
|
||||
const segmentIconToggleMenu = (e: any) => {
|
||||
if (segmentIconContextTrigger) {
|
||||
segmentIconContextTrigger.handleContextClick(e);
|
||||
this.props.editor.selectOnly(segmentIndex);
|
||||
this.update();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={segmentIndex.toString()}
|
||||
@@ -1329,46 +1158,14 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
}
|
||||
onClick={(e) => this.changeSegmentSelection(e, segmentIndex)}
|
||||
>
|
||||
<td
|
||||
className="segment-icon-container"
|
||||
onClick={(e) => {
|
||||
if (segmentIcon !== undefined) {
|
||||
segmentIconToggleMenu(e);
|
||||
} else {
|
||||
this.changeSegmentIcon(segmentIndex);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ContextMenuTrigger
|
||||
id={segmentIconContextMenuId}
|
||||
ref={(c) => segmentIconContextTrigger = c}
|
||||
>
|
||||
{
|
||||
segmentIcon !== undefined &&
|
||||
<img
|
||||
className="segment-icon"
|
||||
src={segmentIcon}
|
||||
/>
|
||||
}
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenu id={segmentIconContextMenuId}>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.changeSegmentIcon(segmentIndex)}>
|
||||
Set Icon
|
||||
<span className="tooltip-text">
|
||||
Allows you to choose an image file to set as the segment's icon. Certain file formats may not work everywhere.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.removeSegmentIcon(segmentIndex)}>
|
||||
Remove Icon
|
||||
<span className="tooltip-text">
|
||||
Removes the segment's icon.
|
||||
</span>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</td>
|
||||
<SegmentIcon
|
||||
segmentIcon={segmentIcon}
|
||||
changeSegmentIcon={() => this.changeSegmentIcon(segmentIndex)}
|
||||
removeSegmentIcon={() => this.removeSegmentIcon(segmentIndex)}
|
||||
/>
|
||||
<td>
|
||||
<input
|
||||
className="name"
|
||||
className="name text-box"
|
||||
type="text"
|
||||
value={s.name}
|
||||
onFocus={(_) => this.focusSegment(segmentIndex)}
|
||||
@@ -1377,7 +1174,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
className="number"
|
||||
className="number text-box"
|
||||
type="text"
|
||||
value={segmentIndex === this.state.rowState.index &&
|
||||
this.state.rowState.splitTimeChanged
|
||||
@@ -1394,8 +1191,8 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
((segmentIndex !== this.state.rowState.index ||
|
||||
!this.state.rowState.segmentTimeChanged) &&
|
||||
s.segment_time === s.best_segment_time)
|
||||
? "number best-segment-time"
|
||||
: "number"
|
||||
? "number text-box best-segment-time"
|
||||
: "number text-box"
|
||||
}
|
||||
type="text"
|
||||
value={segmentIndex === this.state.rowState.index &&
|
||||
@@ -1409,7 +1206,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
className="number"
|
||||
className="number text-box"
|
||||
type="text"
|
||||
value={segmentIndex === this.state.rowState.index &&
|
||||
this.state.rowState.bestSegmentTimeChanged
|
||||
@@ -1429,7 +1226,7 @@ export class RunEditor extends React.Component<Props, State> {
|
||||
.map((comparisonTime, comparisonIndex) => (
|
||||
<td>
|
||||
<input
|
||||
className="number"
|
||||
className="number text-box"
|
||||
type="text"
|
||||
value={segmentIndex === this.state.rowState.index &&
|
||||
this.state.rowState.comparisonTimesChanged[comparisonIndex]
|
||||
@@ -2286,3 +2083,281 @@ function associateRun<T>(
|
||||
// Needs to be set last in order for it not to dissociate again
|
||||
editor.setRunId(apiRun.id);
|
||||
}
|
||||
|
||||
function GameIcon({
|
||||
gameIcon,
|
||||
speedrunComIntegration,
|
||||
changeGameIcon,
|
||||
downloadBoxArt,
|
||||
downloadIcon,
|
||||
removeGameIcon,
|
||||
}: {
|
||||
gameIcon: string | undefined;
|
||||
speedrunComIntegration: boolean;
|
||||
changeGameIcon: () => void;
|
||||
downloadBoxArt: () => void;
|
||||
downloadIcon: () => void;
|
||||
removeGameIcon: () => void;
|
||||
}) {
|
||||
const [position, setPosition] = React.useState<Position | null>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="game-icon-container"
|
||||
onClick={(e) => setPosition({ x: e.clientX, y: e.clientY })}
|
||||
>
|
||||
{
|
||||
gameIcon !== undefined &&
|
||||
<img
|
||||
src={gameIcon}
|
||||
className="game-icon-image"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
{position && (
|
||||
<ContextMenu position={position} onClose={() => setPosition(null)}>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={changeGameIcon}>
|
||||
Set Icon
|
||||
<span className="tooltip-text">
|
||||
Allows you to choose an image file to set as the game's icon. Certain file formats may not work everywhere.
|
||||
</span>
|
||||
</MenuItem>
|
||||
{
|
||||
speedrunComIntegration && <>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={downloadBoxArt}>
|
||||
Download Box Art
|
||||
<span className="tooltip-text">
|
||||
Attempts to download the box art of the game from speedrun.com, to set as the game's icon.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={downloadIcon}>
|
||||
Download Icon
|
||||
<span className="tooltip-text">
|
||||
Attempts to download the icon of the game from speedrun.com.
|
||||
</span>
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
{
|
||||
gameIcon !== undefined &&
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={removeGameIcon}>
|
||||
Remove Icon
|
||||
<span className="tooltip-text">
|
||||
Removes the icon of the game.
|
||||
</span>
|
||||
</MenuItem>
|
||||
}
|
||||
</ContextMenu>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function CleaningButton({
|
||||
clearHistory,
|
||||
clearTimes,
|
||||
cleanSumOfBest,
|
||||
}: {
|
||||
clearHistory: () => void;
|
||||
clearTimes: () => void;
|
||||
cleanSumOfBest: () => void;
|
||||
}) {
|
||||
const [position, setPosition] = React.useState<Position | null>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={(e) => setPosition({ x: e.clientX, y: e.clientY })}>
|
||||
Cleaning…
|
||||
</button>
|
||||
{position && (
|
||||
<ContextMenu position={position} onClose={() => setPosition(null)}>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={clearHistory}>
|
||||
Clear Only History
|
||||
<span className="tooltip-text">
|
||||
Splits store the entire history of all runs, including every segment time. This information is used by various components. You can clear the history with this. The personal best, the best segment times, and the comparisons will not be affected.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={clearTimes}>
|
||||
Clear All Times
|
||||
<span className="tooltip-text">
|
||||
This removes all the times from the splits, including all the history, such that the splits are completely empty, as if they were just created.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={cleanSumOfBest}>
|
||||
Clean Sum of Best
|
||||
<span className="tooltip-text">
|
||||
Allows you to interactively remove potential issues in the segment history that lead to an inaccurate Sum of Best. If you skip a split, whenever you will do the next split, the combined segment time might be faster than the sum of the individual best segments. This will point out all such occurrences and allow you to delete them individually if any of them seem wrong.
|
||||
</span>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ComparisonsButton({
|
||||
addComparison,
|
||||
importComparison,
|
||||
generateGoalComparison,
|
||||
copyComparison,
|
||||
}: {
|
||||
addComparison: () => void;
|
||||
importComparison: () => void;
|
||||
generateGoalComparison: () => void;
|
||||
copyComparison: () => void;
|
||||
}) {
|
||||
const [position, setPosition] = React.useState<Position | null>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={(e) => setPosition({ x: e.clientX, y: e.clientY })}>
|
||||
Comparisons…
|
||||
</button>
|
||||
{position && (
|
||||
<ContextMenu position={position} onClose={() => setPosition(null)}>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={addComparison}>
|
||||
Add Comparison
|
||||
<span className="tooltip-text">
|
||||
Adds a new custom comparison where you can store any times that you would like.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={importComparison}>
|
||||
Import Comparison
|
||||
<span className="tooltip-text">
|
||||
Imports the Personal Best of a splits file you provide as a comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={generateGoalComparison}>
|
||||
Generate Goal Comparison
|
||||
<span className="tooltip-text">
|
||||
Generates a custom goal comparison based on a goal time that you can specify. The comparison's times are automatically balanced based on the segment history such that it roughly represents what the split times for the goal time would look like. Since it is populated by the segment history, the goal times are capped to a range between the sum of the best segments and the sum of the worst segments. The comparison is only populated for the selected timing method. The other timing method's comparison times are not modified by this, so you can generate it again with the other timing method to generate the comparison times for both timing methods.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={copyComparison}>
|
||||
Copy Comparison
|
||||
<span className="tooltip-text">
|
||||
Copies any existing comparison, including the Personal Best or even any other automatically provided comparison as a new custom comparison. You could for example use this to keep the Latest Run around as a comparison that exists for as long as you want it to.
|
||||
</span>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SegmentIcon({
|
||||
segmentIcon,
|
||||
changeSegmentIcon,
|
||||
removeSegmentIcon,
|
||||
}: {
|
||||
segmentIcon: string | undefined;
|
||||
changeSegmentIcon: () => void;
|
||||
removeSegmentIcon: () => void;
|
||||
}) {
|
||||
const [position, setPosition] = React.useState<Position | null>(null);
|
||||
|
||||
return (
|
||||
<td
|
||||
className="segment-icon-container"
|
||||
onClick={(e) => {
|
||||
if (position !== null) {
|
||||
return;
|
||||
}
|
||||
if (segmentIcon !== undefined) {
|
||||
setPosition({ x: e.clientX, y: e.clientY });
|
||||
} else {
|
||||
changeSegmentIcon();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{
|
||||
segmentIcon !== undefined &&
|
||||
<img src={segmentIcon} />
|
||||
}
|
||||
{position && (
|
||||
<ContextMenu position={position} onClose={() => setPosition(null)}>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={changeSegmentIcon}>
|
||||
Set Icon
|
||||
<span className="tooltip-text">
|
||||
Allows you to choose an image file to set as the segment's icon. Certain file formats may not work everywhere.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={removeSegmentIcon}>
|
||||
Remove Icon
|
||||
<span className="tooltip-text">
|
||||
Removes the segment's icon.
|
||||
</span>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
)}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
function CustomComparison({
|
||||
comparison,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
onDrop,
|
||||
renameComparison,
|
||||
copyComparison,
|
||||
removeComparison,
|
||||
}: {
|
||||
comparison: string;
|
||||
onDragStart: (e: React.DragEvent) => void;
|
||||
onDragEnd: (e: React.DragEvent) => void;
|
||||
onDrop: (e: React.DragEvent) => void;
|
||||
renameComparison: () => void;
|
||||
copyComparison: () => void;
|
||||
removeComparison: () => void;
|
||||
}) {
|
||||
const [position, setPosition] = React.useState<Position | null>(null);
|
||||
|
||||
return (
|
||||
<th
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (position === null) {
|
||||
setPosition({ x: e.clientX, y: e.clientY });
|
||||
}
|
||||
}}
|
||||
draggable
|
||||
onDragStart={onDragStart}
|
||||
onDragOver={(e) => {
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
e.dataTransfer.dropEffect = "move";
|
||||
}}
|
||||
onDragEnd={onDragEnd}
|
||||
onDrop={onDrop}
|
||||
>
|
||||
{comparison}
|
||||
{position && (
|
||||
<ContextMenu position={position} onClose={() => setPosition(null)}>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={renameComparison}>
|
||||
Rename
|
||||
<span className="tooltip-text">
|
||||
Choose a new name for the custom comparison. There are reserved names that can't be used. You also can't have duplicate names.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={copyComparison}>
|
||||
Copy
|
||||
<span className="tooltip-text">
|
||||
Creates a copy of the custom comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={removeComparison}>
|
||||
Remove
|
||||
<span className="tooltip-text">
|
||||
Removes the custom comparison.
|
||||
</span>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
)}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
+13
-9
@@ -29,8 +29,8 @@ export interface ExtendedSettingsDescriptionJson {
|
||||
}
|
||||
|
||||
export interface ExtendedSettingsDescriptionFieldJson {
|
||||
text: string | JSX.Element,
|
||||
tooltip: string | JSX.Element,
|
||||
text: string | React.JSX.Element,
|
||||
tooltip: string | React.JSX.Element,
|
||||
value: ExtendedSettingsDescriptionValueJson,
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ export class JsonSettingValueFactory implements SettingValueFactory<ExtendedSett
|
||||
|
||||
export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
public render() {
|
||||
const settingsRows: JSX.Element[] = [];
|
||||
const settingsRows: React.JSX.Element[] = [];
|
||||
const { factory } = this.props;
|
||||
|
||||
this.props.state.fields.forEach((field, valueIndex) => {
|
||||
@@ -218,7 +218,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
<div className="settings-value-box">
|
||||
<input
|
||||
type="number"
|
||||
className="number"
|
||||
className="number text-box"
|
||||
value={value.UInt}
|
||||
min="0"
|
||||
onChange={(e) => {
|
||||
@@ -235,7 +235,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
<div className="settings-value-box">
|
||||
<input
|
||||
type="number"
|
||||
className="number"
|
||||
className="number text-box"
|
||||
value={value.Int}
|
||||
onChange={(e) => {
|
||||
this.props.setValue(
|
||||
@@ -283,6 +283,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
component = (
|
||||
<div className="settings-value-box">
|
||||
<input
|
||||
className="text-box"
|
||||
value={value.String}
|
||||
onChange={(e) => {
|
||||
this.props.setValue(
|
||||
@@ -344,6 +345,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
if (value.OptionalString !== null) {
|
||||
children.push(
|
||||
<input
|
||||
className="text-box"
|
||||
value={value.OptionalString}
|
||||
onChange={(e) => {
|
||||
this.props.setValue(
|
||||
@@ -365,6 +367,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
component = (
|
||||
<div className="settings-value-box removable-string">
|
||||
<input
|
||||
className="text-box"
|
||||
value={value.RemovableString || ""}
|
||||
onChange={(e) => {
|
||||
if (factory.fromRemovableString) {
|
||||
@@ -551,7 +554,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
}
|
||||
};
|
||||
|
||||
const children: JSX.Element[] = [
|
||||
const children: React.JSX.Element[] = [
|
||||
<select
|
||||
value={type}
|
||||
onChange={(e) => {
|
||||
@@ -676,7 +679,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
}
|
||||
};
|
||||
|
||||
const children: JSX.Element[] = [
|
||||
const children: React.JSX.Element[] = [
|
||||
<select
|
||||
value={type}
|
||||
onChange={(e) => {
|
||||
@@ -1020,6 +1023,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
} else {
|
||||
children.push(
|
||||
<input
|
||||
className="text-box"
|
||||
value={family}
|
||||
onChange={(e) => {
|
||||
this.props.setValue(
|
||||
@@ -1154,7 +1158,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
}
|
||||
};
|
||||
|
||||
const children: JSX.Element[] = [
|
||||
const children: React.JSX.Element[] = [
|
||||
<select
|
||||
value={type}
|
||||
onChange={(e) => {
|
||||
@@ -1231,7 +1235,7 @@ export class SettingsComponent<T> extends React.Component<Props<T>> {
|
||||
let blur = 0;
|
||||
const gradient = value.LayoutBackground;
|
||||
|
||||
const children: JSX.Element[] = [];
|
||||
const children: React.JSX.Element[] = [];
|
||||
|
||||
const colorsToValue = (
|
||||
type: string,
|
||||
|
||||
+47
-35
@@ -1,4 +1,4 @@
|
||||
import * as React from "react";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
getSplitsInfos, SplitsInfo, deleteSplits, copySplits, loadSplits,
|
||||
storeRunWithoutDisposing, storeSplitsKey,
|
||||
@@ -9,10 +9,10 @@ import { toast } from "react-toastify";
|
||||
import { openFileAsArrayBuffer, exportFile, convertFileToArrayBuffer, FILE_EXT_SPLITS } from "../util/FileUtil";
|
||||
import { Option, bug, maybeDisposeAndThen } from "../util/OptionUtil";
|
||||
import DragUpload from "./DragUpload";
|
||||
import { ContextMenuTrigger, ContextMenu, MenuItem } from "react-contextmenu";
|
||||
import { GeneralSettings } from "./MainSettings";
|
||||
import { LSOCommandSink } from "./LSOCommandSink";
|
||||
import { showDialog } from "./Dialog";
|
||||
import { ContextMenu, MenuItem, Position } from "./ContextMenu";
|
||||
|
||||
import "../css/SplitsSelection.scss";
|
||||
|
||||
@@ -37,7 +37,7 @@ interface Callbacks {
|
||||
openRunEditor(editingInfo: EditingInfo): void,
|
||||
setSplitsKey(newKey?: number): void,
|
||||
openTimerView(): void,
|
||||
renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element): JSX.Element,
|
||||
renderViewWithSidebar(renderedView: React.JSX.Element, sidebarContent: React.JSX.Element): React.JSX.Element,
|
||||
saveSplits(): Promise<void>,
|
||||
}
|
||||
|
||||
@@ -112,14 +112,6 @@ export class SplitsSelection extends React.Component<Props, State> {
|
||||
private renderSavedSplitsRow(key: number, info: SplitsInfo) {
|
||||
const isOpened = key === this.props.openedSplitsKey;
|
||||
|
||||
const segmentIconContextMenuId = `splits-${key}-context-menu`;
|
||||
let exportContextTrigger: any = null;
|
||||
const exportButtonToggleMenu = (e: any) => {
|
||||
if (exportContextTrigger) {
|
||||
exportContextTrigger.handleContextClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={isOpened ? "splits-row selected" : "splits-row"} key={key}>
|
||||
{this.splitsTitle(info)}
|
||||
@@ -134,30 +126,11 @@ export class SplitsSelection extends React.Component<Props, State> {
|
||||
<button aria-label="Edit Splits" onClick={() => this.editSplits(key)}>
|
||||
<i className="fa fa-edit" aria-hidden="true" />
|
||||
</button>
|
||||
<button aria-label="Export Splits" onClick={(e) => exportButtonToggleMenu(e)}>
|
||||
<ContextMenuTrigger
|
||||
id={segmentIconContextMenuId}
|
||||
ref={(c) => exportContextTrigger = c}
|
||||
>
|
||||
<i className="fa fa-upload" aria-hidden="true" />
|
||||
</ContextMenuTrigger>
|
||||
</button>
|
||||
<ContextMenu id={segmentIconContextMenuId}>
|
||||
<MenuItem className="tooltip" onClick={(_) => this.exportSplits(key, info)}>
|
||||
Export to File
|
||||
<span className="tooltip-text">
|
||||
Export the splits to a file on your computer.
|
||||
</span>
|
||||
</MenuItem>
|
||||
{
|
||||
this.props.generalSettings.splitsIoIntegration && <MenuItem className="tooltip" onClick={(_) => this.uploadSplitsToSplitsIO(key)}>
|
||||
Upload to Splits.io
|
||||
<span className="tooltip-text">
|
||||
Upload the splits to splits.io.
|
||||
</span>
|
||||
</MenuItem>
|
||||
}
|
||||
</ContextMenu>
|
||||
<ExportButton
|
||||
splitsIoIntegration={this.props.generalSettings.splitsIoIntegration}
|
||||
exportSplits={() => this.exportSplits(key, info)}
|
||||
uploadSplitsToSplitsIO={() => this.uploadSplitsToSplitsIO(key)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
<button aria-label="Copy Splits" onClick={() => this.copySplits(key)}>
|
||||
@@ -428,3 +401,42 @@ export class SplitsSelection extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ExportButton({
|
||||
splitsIoIntegration,
|
||||
exportSplits,
|
||||
uploadSplitsToSplitsIO,
|
||||
}: {
|
||||
splitsIoIntegration: boolean;
|
||||
exportSplits: () => void;
|
||||
uploadSplitsToSplitsIO: () => void;
|
||||
}) {
|
||||
const [position, setPosition] = useState<Position | null>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button aria-label="Export Splits" onClick={(e) => setPosition({ x: e.clientX, y: e.clientY })}>
|
||||
<i className="fa fa-upload" aria-hidden="true" />
|
||||
</button>
|
||||
{position && (
|
||||
<ContextMenu position={position} onClose={() => setPosition(null)}>
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={exportSplits}>
|
||||
Export to File
|
||||
<span className="tooltip-text">
|
||||
Export the splits to a file on your computer.
|
||||
</span>
|
||||
</MenuItem>
|
||||
{splitsIoIntegration && (
|
||||
<MenuItem className="contextmenu-item tooltip" onClick={uploadSplitsToSplitsIO}>
|
||||
Upload to Splits.io
|
||||
<span className="tooltip-text">
|
||||
Upload the splits to splits.io.
|
||||
</span>
|
||||
</MenuItem>
|
||||
)}
|
||||
</ContextMenu>
|
||||
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,14 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
import "../css/Switch.scss";
|
||||
import * as classes from "../css/Switch.module.scss";
|
||||
|
||||
export default function Switch({ checked, setIsChecked }: {
|
||||
checked: boolean,
|
||||
setIsChecked: (checked: boolean) => void,
|
||||
}) {
|
||||
return (
|
||||
<label style={{ cursor: "pointer", display: "flex", justifyContent: "center", alignItems: "center" }}>
|
||||
<div className="switch">
|
||||
<label className={classes.label}>
|
||||
<div className={classes.switch}>
|
||||
<input type="checkbox" checked={checked} onChange={(e) => setIsChecked(e.target.checked)} />
|
||||
<span />
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ export class TextBox extends React.Component<Props> {
|
||||
<div className={className}>
|
||||
<input
|
||||
list={name}
|
||||
type="text"
|
||||
type="text text-box"
|
||||
required
|
||||
className={this.props.className}
|
||||
value={this.props.value}
|
||||
|
||||
@@ -48,7 +48,7 @@ interface Callbacks {
|
||||
openLayoutView(): void,
|
||||
openSplitsView(): void,
|
||||
openMainSettings(): void,
|
||||
renderViewWithSidebar(renderedView: JSX.Element, sidebarContent: JSX.Element): JSX.Element,
|
||||
renderViewWithSidebar(renderedView: React.JSX.Element, sidebarContent: React.JSX.Element): React.JSX.Element,
|
||||
onServerConnectionClosed(): void,
|
||||
onServerConnectionOpened(serverConnection: LiveSplitServer): void,
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ function replaceTwitchEmotes(text: string): string {
|
||||
});
|
||||
}
|
||||
|
||||
export function replaceFlag(countryCode: string): JSX.Element {
|
||||
export function replaceFlag(countryCode: string): React.JSX.Element {
|
||||
const url = `https://www.speedrun.com/images/flags/${countryCode}.png`;
|
||||
|
||||
return <img className="flag" src={url} alt={countryCode} />;
|
||||
@@ -43,7 +43,7 @@ export const Markdown = React.memo(renderMarkdown);
|
||||
|
||||
export function renderMarkdown({ markdown, unsafe }: { markdown: string,
|
||||
unsafe?: boolean,
|
||||
}): JSX.Element {
|
||||
}): React.JSX.Element {
|
||||
const markdownWithEmotes = replaceTwitchEmotes(markdown);
|
||||
const html = (unsafe ? UNSAFE : SAFE).render(markdownWithEmotes);
|
||||
|
||||
|
||||
+14
-1
@@ -9,6 +9,7 @@ import webpack from "webpack";
|
||||
import { execSync } from "child_process";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from 'url';
|
||||
import { defineReactCompilerLoaderOption, reactCompilerLoader } from "react-compiler-webpack";
|
||||
|
||||
function parseChangelog() {
|
||||
return execSync("git log --grep \"^Changelog: \" -10")
|
||||
@@ -182,6 +183,16 @@ export default async (env, argv) => {
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: [
|
||||
...(isProduction ? [{
|
||||
loader: reactCompilerLoader,
|
||||
options: defineReactCompilerLoaderOption({
|
||||
babelTransFormOpt: {
|
||||
plugins: [
|
||||
"@babel/plugin-proposal-explicit-resource-management",
|
||||
],
|
||||
},
|
||||
}),
|
||||
}] : []),
|
||||
{
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
@@ -202,7 +213,9 @@ export default async (env, argv) => {
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: "icss",
|
||||
modules: {
|
||||
auto: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"sass-loader",
|
||||
|
||||
Reference in New Issue
Block a user