Merge remote-tracking branch 'iTNTPiston/main' into main
This commit is contained in:
commit
84cfd7f62e
14 changed files with 359 additions and 43 deletions
|
@ -297,9 +297,9 @@ module.exports = function (webpackEnv) {
|
|||
],
|
||||
},
|
||||
resolve: {
|
||||
// fallback: {
|
||||
// buffer: require.resolve("buffer/")
|
||||
// },
|
||||
fallback: {
|
||||
buffer: require.resolve("buffer/")
|
||||
},
|
||||
// This allows you to set a fallback for where webpack should look for modules.
|
||||
// We placed these paths second because we want `node_modules` to "win"
|
||||
// if there are any conflicts. This matches Node resolution mechanism.
|
||||
|
@ -579,6 +579,11 @@ module.exports = function (webpackEnv) {
|
|||
].filter(Boolean),
|
||||
},
|
||||
plugins: [
|
||||
// Work around for Buffer is undefined:
|
||||
// https://github.com/webpack/changelog-v5/issues/10
|
||||
new webpack.ProvidePlugin({
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
}),
|
||||
// Generates an `index.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin(
|
||||
Object.assign(
|
||||
|
|
108
package-lock.json
generated
108
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "botw-hundo-dupl",
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.5",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "botw-hundo-dupl",
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.5",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.16.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.3",
|
||||
|
@ -43,12 +43,14 @@
|
|||
"jest-resolve": "^27.4.2",
|
||||
"jest-watch-typeahead": "^1.0.0",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"pako": "^2.0.4",
|
||||
"postcss": "^8.4.4",
|
||||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-loader": "^6.2.1",
|
||||
"postcss-normalize": "^10.0.1",
|
||||
"postcss-preset-env": "^7.0.1",
|
||||
"prompts": "^2.4.2",
|
||||
"query-string": "^7.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-dev-utils": "^12.0.1",
|
||||
|
@ -72,6 +74,7 @@
|
|||
"yaml-loader": "^0.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pako": "^2.0.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
},
|
||||
|
@ -3827,6 +3830,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.41.tgz",
|
||||
"integrity": "sha512-mqoYK2TnVjdkGk8qXAVGc/x9nSaTpSrFaGFm43BUH3IdoBV0nta6hYaGmdOvIMlbHJbUEVen3gvwpwovAZKNdQ=="
|
||||
},
|
||||
"node_modules/@types/pako": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.0.tgz",
|
||||
"integrity": "sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
|
@ -7652,6 +7661,14 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/filter-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||
|
@ -11962,6 +11979,11 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz",
|
||||
"integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg=="
|
||||
},
|
||||
"node_modules/param-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
|
||||
|
@ -13455,6 +13477,23 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/query-string": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
|
||||
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
|
||||
"dependencies": {
|
||||
"decode-uri-component": "^0.2.0",
|
||||
"filter-obj": "^1.1.0",
|
||||
"split-on-first": "^1.0.0",
|
||||
"strict-uri-encode": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
@ -14580,6 +14619,14 @@
|
|||
"wbuf": "^1.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/split-on-first": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
|
@ -14622,6 +14669,14 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/strict-uri-encode": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
|
@ -15023,9 +15078,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.14.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.1.tgz",
|
||||
"integrity": "sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
|
||||
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.2",
|
||||
"acorn": "^8.5.0",
|
||||
|
@ -19048,6 +19103,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.41.tgz",
|
||||
"integrity": "sha512-mqoYK2TnVjdkGk8qXAVGc/x9nSaTpSrFaGFm43BUH3IdoBV0nta6hYaGmdOvIMlbHJbUEVen3gvwpwovAZKNdQ=="
|
||||
},
|
||||
"@types/pako": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.0.tgz",
|
||||
"integrity": "sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
|
@ -21850,6 +21911,11 @@
|
|||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"filter-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||
|
@ -24929,6 +24995,11 @@
|
|||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
|
||||
},
|
||||
"pako": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz",
|
||||
"integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg=="
|
||||
},
|
||||
"param-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
|
||||
|
@ -25847,6 +25918,17 @@
|
|||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"query-string": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
|
||||
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
|
||||
"requires": {
|
||||
"decode-uri-component": "^0.2.0",
|
||||
"filter-obj": "^1.1.0",
|
||||
"split-on-first": "^1.0.0",
|
||||
"strict-uri-encode": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
@ -26679,6 +26761,11 @@
|
|||
"wbuf": "^1.7.3"
|
||||
}
|
||||
},
|
||||
"split-on-first": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
|
@ -26714,6 +26801,11 @@
|
|||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
|
||||
},
|
||||
"strict-uri-encode": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
|
@ -26998,9 +27090,9 @@
|
|||
}
|
||||
},
|
||||
"terser": {
|
||||
"version": "5.14.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.1.tgz",
|
||||
"integrity": "sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==",
|
||||
"version": "5.15.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
|
||||
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
|
||||
"requires": {
|
||||
"@jridgewell/source-map": "^0.3.2",
|
||||
"acorn": "^8.5.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "botw-hundo-dupl",
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.5",
|
||||
"homepage": "https://dupl.itntpiston.app/",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
@ -39,12 +39,14 @@
|
|||
"jest-resolve": "^27.4.2",
|
||||
"jest-watch-typeahead": "^1.0.0",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"pako": "^2.0.4",
|
||||
"postcss": "^8.4.4",
|
||||
"postcss-flexbugs-fixes": "^5.0.2",
|
||||
"postcss-loader": "^6.2.1",
|
||||
"postcss-normalize": "^10.0.1",
|
||||
"postcss-preset-env": "^7.0.1",
|
||||
"prompts": "^2.4.2",
|
||||
"query-string": "^7.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-dev-utils": "^12.0.1",
|
||||
|
@ -152,6 +154,7 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pako": "^2.0.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<meta property="og:url" content="https://dupl.itntpiston.app/#/">
|
||||
<meta property="og:description" content="for Breath of the Wild">
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>IST Sim</title>
|
||||
<title>IST Simulator</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
|
|
@ -176,6 +176,13 @@ button.MainButton:active {
|
|||
background-color: #888888;
|
||||
}
|
||||
|
||||
button.MainButton:disabled{
|
||||
color: #888888;
|
||||
background-color: #333333;
|
||||
border-color: #888888;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.FullWidth {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
@ -188,6 +195,7 @@ div.OtherPage {
|
|||
|
||||
div.OtherPageContent{
|
||||
padding: 10px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.MainInput {
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { PropsWithChildren } from "react";
|
||||
|
||||
type Props = {
|
||||
multiLine?: boolean,
|
||||
hasError?: boolean,
|
||||
}
|
||||
|
||||
// an over-engineered loading screen
|
||||
export const LoadingScreen: React.FC<PropsWithChildren> = ({children})=>{
|
||||
export const LoadingScreen: React.FC<PropsWithChildren<Props>> = ({multiLine, hasError, children})=>{
|
||||
return (
|
||||
<div style={{
|
||||
textAlign: "center",
|
||||
|
@ -13,9 +18,9 @@ export const LoadingScreen: React.FC<PropsWithChildren> = ({children})=>{
|
|||
backgroundColor: "#262626"
|
||||
}}>
|
||||
<span style={{
|
||||
color: "#00ffcc",
|
||||
color: hasError? "#ee7777":"#00ffcc",
|
||||
|
||||
lineHeight: "100vh",
|
||||
lineHeight: multiLine?"default":"100vh",
|
||||
height: "100vh",
|
||||
}}>
|
||||
{children}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { ItemStack, ItemType, createMaterialStack, createEquipmentStack } from "
|
|||
import { Slots } from "./Slots";
|
||||
import { createArrowMockItem, createEquipmentMockItem, createFoodMockItem, createKeyMockItem, createMaterialMockItem, equalsExceptEquip } from "./SlotsTestHelpers";
|
||||
|
||||
describe("Slots.add", ()=>{
|
||||
describe("core/Slots.add", ()=>{
|
||||
describe("sorted", ()=>{
|
||||
describe("reloading = true", ()=>{
|
||||
it("should add new stack when empty", ()=>{
|
||||
|
|
|
@ -2,7 +2,7 @@ import { createEquipmentStack, createMaterialStack, ItemStack, ItemType } from "
|
|||
import { Slots } from "./Slots";
|
||||
import { createEquipmentMockItem, createMaterialMockItem } from "./SlotsTestHelpers";
|
||||
|
||||
describe("Slots.remove", ()=>{
|
||||
describe("core/Slots.remove", ()=>{
|
||||
it("Does nothing if item doesn't exist", ()=>{
|
||||
const mockItem1 = createMaterialMockItem("MaterialA");
|
||||
const stackToRemove = createMaterialStack(mockItem1, 1);
|
||||
|
|
|
@ -2,7 +2,7 @@ import { createEquipmentStack, createMaterialStack, ItemStack, ItemType } from "
|
|||
import { Slots } from "./Slots";
|
||||
import { createArrowMockItem, createEquipmentMockItem, createFoodMockItem, createKeyMockItemStackable, createMaterialMockItem } from "./SlotsTestHelpers";
|
||||
|
||||
describe.only("Slots.updateLife", ()=>{
|
||||
describe.only("core/Slots.updateLife", ()=>{
|
||||
it("should update life", ()=>{
|
||||
const mockItem1 = createMaterialMockItem("MaterialA");
|
||||
const slot = createMaterialStack(mockItem1, 1);
|
||||
|
|
33
src/data/serialize.test.ts
Normal file
33
src/data/serialize.test.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { compressString, decompressString } from "./serialize";
|
||||
|
||||
const runCompressDecompressTest = (input: string) => {
|
||||
const compressed = compressString(input);
|
||||
expect(compressed).not.toEqual(input);
|
||||
expect(decompressString(compressed)).toEqual(input);
|
||||
};
|
||||
|
||||
describe.only("data/serialize.compress", ()=>{
|
||||
it("Should compress and decompress empty string", ()=>{
|
||||
runCompressDecompressTest("");
|
||||
});
|
||||
it("Should compress and decompress one character", ()=>{
|
||||
runCompressDecompressTest("a");
|
||||
});
|
||||
it("Should compress and decompress single command", ()=>{
|
||||
const input = "Break 5 Slots";
|
||||
runCompressDecompressTest(input);
|
||||
});
|
||||
it("Should compress and decompress large command", ()=>{
|
||||
const input = "Initialize 1 Tree Branch[equip] 1 Hammer 1 Travel Bow[Equip] 3 NormalArrow[Equip] 1 potlid 1 potlid[equip] 1 Fairy 1 SpeedFood 3 EnduraFood 1 Slate 9 SpiritOrb 1 Glider";
|
||||
runCompressDecompressTest(input);
|
||||
});
|
||||
it("Should compress and decompress multiple commands", ()=>{
|
||||
const inputArray = [
|
||||
"Break 5 Slots",
|
||||
"Save",
|
||||
"Eat 3 EnduraFood",
|
||||
"Eat SpeedFood"
|
||||
];
|
||||
runCompressDecompressTest(inputArray.join("\n"));
|
||||
});
|
||||
});
|
40
src/data/serialize.ts
Normal file
40
src/data/serialize.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { gzip, ungzip } from "pako";
|
||||
|
||||
const ZLIB_OPTIONS = {
|
||||
level: 9
|
||||
} as const;
|
||||
|
||||
type SerializedCommands = { r: string } | { c: string }; // r for raw and c for compressed
|
||||
|
||||
export const serialize = (commandsString: string): SerializedCommands => {
|
||||
const compressed = compressString(commandsString);
|
||||
if (commandsString.length < compressed.length){
|
||||
return { r: commandsString };
|
||||
}
|
||||
return { c: compressed };
|
||||
};
|
||||
|
||||
export const deserialize = (serializedCommands: Partial<SerializedCommands>): string | null => {
|
||||
if ( "r" in serializedCommands && serializedCommands.r){
|
||||
return serializedCommands.r;
|
||||
}
|
||||
if ( "c" in serializedCommands && serializedCommands.c){
|
||||
return decompressString(serializedCommands.c);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const compressString = (uncompressedString: string): string => {
|
||||
const uncompressedBytes = Buffer.from(uncompressedString, "utf8");
|
||||
const compressedBytes = gzip(uncompressedBytes, ZLIB_OPTIONS);
|
||||
return Buffer.from(compressedBytes).toString("base64");
|
||||
};
|
||||
|
||||
export const decompressString = (decompressedString: string): string => {
|
||||
const compressedBytes = Buffer.from(decompressedString, "base64");
|
||||
const uncompressedBytes = ungzip(compressedBytes, {
|
||||
to: "string",
|
||||
...ZLIB_OPTIONS
|
||||
});
|
||||
return Buffer.from(uncompressedBytes).toString("utf8");
|
||||
};
|
|
@ -5,6 +5,7 @@ import {App} from "./App";
|
|||
import reportWebVitals from "./reportWebVitals";
|
||||
import { LanguageProvider } from "data/i18n";
|
||||
import { ItemProvider } from "data/item";
|
||||
import { DirectLoadPage } from "surfaces/DirectLoadPage";
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById("root") as HTMLElement
|
||||
|
@ -12,12 +13,12 @@ const root = ReactDOM.createRoot(
|
|||
root.render(
|
||||
<React.StrictMode>
|
||||
<LanguageProvider>
|
||||
<ItemProvider>
|
||||
<App />
|
||||
</ItemProvider>
|
||||
|
||||
<DirectLoadPage>
|
||||
<ItemProvider>
|
||||
<App />
|
||||
</ItemProvider>
|
||||
</DirectLoadPage>
|
||||
</LanguageProvider>
|
||||
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
|
|
72
src/surfaces/DirectLoadPage.tsx
Normal file
72
src/surfaces/DirectLoadPage.tsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { PropsWithChildren, useMemo } from "react";
|
||||
import { parse } from "query-string";
|
||||
import { deserialize } from "data/serialize";
|
||||
import { LoadingScreen } from "components/LoadingScreen";
|
||||
import { BodyText, Header, SubHeader } from "components/Text";
|
||||
|
||||
const redirectToMainApp = ()=>{
|
||||
window.location.href = window.location.origin;
|
||||
};
|
||||
|
||||
export const DirectLoadPage: React.FC<PropsWithChildren> = ({children}) => {
|
||||
const query = parse(window.location.search);
|
||||
const [commandTextToLoad, errorText] = useMemo(()=>{
|
||||
try {
|
||||
return [deserialize(query), false];
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return [null, "Fail to deserialize. The URL may be corrupted."];
|
||||
}
|
||||
}, [query]);
|
||||
|
||||
if(errorText){
|
||||
return (
|
||||
<LoadingScreen hasError multiLine>
|
||||
<Header>Error loading direct URL</Header>
|
||||
<SubHeader>{errorText}</SubHeader>
|
||||
<BodyText>The browser console may have useful information for debugging</BodyText>
|
||||
<BodyText emphasized>Press Continue to load existing data in the simulator instead</BodyText>
|
||||
<button className="MainButton" onClick={()=>{
|
||||
redirectToMainApp();
|
||||
}}>Continue</button>
|
||||
</LoadingScreen>
|
||||
);
|
||||
}
|
||||
|
||||
if (!commandTextToLoad){
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
if (!localStorage.getItem("HDS.CurrentCommandsText")){
|
||||
// If no data is in simulator (i.e. first time use), load data without warning
|
||||
localStorage.setItem("HDS.CurrentCommandsText", commandTextToLoad);
|
||||
redirectToMainApp();
|
||||
}
|
||||
|
||||
return <LoadingScreen multiLine>
|
||||
<div className="OtherPageContent">
|
||||
<Header>Open Direct URL?</Header>
|
||||
<SubHeader>You are trying to open a direct URL. This will automatically load data into the simulator.</SubHeader>
|
||||
<BodyText emphasized>This will override existing data and cannot be reversed</BodyText>
|
||||
<button className="MainButton" onClick={()=>{
|
||||
localStorage.setItem("HDS.CurrentCommandsText", commandTextToLoad);
|
||||
redirectToMainApp();
|
||||
}}>Yes</button>
|
||||
<button className="MainButton" onClick={()=>{
|
||||
|
||||
redirectToMainApp();
|
||||
}}>No</button>
|
||||
|
||||
<div style={{marginTop: "50px", marginLeft: "10%", marginRight: "10%"}}>
|
||||
<BodyText>(Below is what the incoming data looks like)</BodyText>
|
||||
<textarea
|
||||
className="MainInput"
|
||||
spellCheck={false}
|
||||
value={commandTextToLoad}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</LoadingScreen>;
|
||||
};
|
|
@ -1,6 +1,10 @@
|
|||
import { BodyText, SubHeader, SubTitle } from "components/Text";
|
||||
import { TitledList } from "components/TitledList";
|
||||
import { saveAs } from "data/FileSaver";
|
||||
import { useRef, useState } from "react";
|
||||
import { serialize } from "data/serialize";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
const URL_MAX = 2048;
|
||||
|
||||
type OptionPageProps = {
|
||||
interlaceInventory: boolean,
|
||||
|
@ -21,8 +25,23 @@ export const OptionPage: React.FC<OptionPageProps> = ({
|
|||
}) => {
|
||||
const [currentText, setCurrentText] = useState<string>(commandText);
|
||||
const [fileName, setFileName] = useState<string>("");
|
||||
const [showDirectUrl, setShowDirectUrl] = useState<boolean>(false);
|
||||
const [showCopiedMessage, setShowCopiedMessage] = useState<boolean>(false);
|
||||
|
||||
const uploadRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const directUrl = useMemo(()=>{
|
||||
const serializedCommands = serialize(commandText);
|
||||
const query = new URLSearchParams(serializedCommands).toString();
|
||||
return `${window.location.origin}/?${query}`;
|
||||
}, [commandText]);
|
||||
|
||||
const directUrlLength = directUrl.length;
|
||||
|
||||
useEffect(()=>{
|
||||
setShowCopiedMessage(false);
|
||||
}, [currentText]);
|
||||
|
||||
return (
|
||||
<div className="OtherPage">
|
||||
|
||||
|
@ -41,36 +60,29 @@ export const OptionPage: React.FC<OptionPageProps> = ({
|
|||
|
||||
<TitledList title="Options">
|
||||
<div className="OtherPageContent">
|
||||
|
||||
<h3 className="Reference">
|
||||
<SubHeader>
|
||||
Interlace Inventory with GameData
|
||||
<button className="MainButton" onClick={()=>{
|
||||
setInterlaceInventory(!interlaceInventory);
|
||||
}}>
|
||||
{interlaceInventory ? "ON" : "OFF"}
|
||||
</button>
|
||||
</h3>
|
||||
<h4 className="Reference">
|
||||
Toggle whether Visible Inventory should be displayed separetely from Game Data or interlaced.
|
||||
</h4>
|
||||
</SubHeader>
|
||||
<SubTitle>Toggle whether Visible Inventory should be displayed separetely from Game Data or interlaced.</SubTitle>
|
||||
|
||||
<h3 className="Reference">
|
||||
<SubHeader>
|
||||
Enable Animated Item Icons
|
||||
<button className="MainButton" onClick={()=>{
|
||||
setIsIconAnimated(!isIconAnimated);
|
||||
}}>
|
||||
{isIconAnimated ? "ON" : "OFF"}
|
||||
</button>
|
||||
</h3>
|
||||
<h4 className="Reference">
|
||||
Toggle whether items such as the champion abilities or Travel Medallion use animated or still icons.
|
||||
</h4>
|
||||
</SubHeader>
|
||||
<SubTitle>Toggle whether items such as the champion abilities or Travel Medallion use animated or still icons.</SubTitle>
|
||||
|
||||
<h3 className="Reference">Import / Export</h3>
|
||||
<h4 className="Reference">
|
||||
You can also directly copy, paste, or edit the commands here
|
||||
</h4>
|
||||
<p className="Reference">
|
||||
<SubHeader>Text Import / Export</SubHeader>
|
||||
<SubTitle>You can also directly copy, paste, or edit the commands here</SubTitle>
|
||||
<BodyText>
|
||||
<button className="MainButton" onClick={()=>{
|
||||
if(uploadRef.current){
|
||||
uploadRef.current.click();
|
||||
|
@ -112,9 +124,54 @@ export const OptionPage: React.FC<OptionPageProps> = ({
|
|||
<span className="Example">Don't forget to save changes</span>
|
||||
</>
|
||||
}
|
||||
|
||||
</p>
|
||||
</BodyText>
|
||||
|
||||
<SubHeader>Direct URL</SubHeader>
|
||||
<SubTitle>Use this to open the simulator with the steps automatically loaded.</SubTitle>
|
||||
<div>
|
||||
{
|
||||
currentText !== commandText ?
|
||||
<BodyText emphasized>
|
||||
You must save the changes above to access the updated URL
|
||||
</BodyText>
|
||||
:
|
||||
<>
|
||||
{
|
||||
directUrlLength > URL_MAX && <BodyText emphasized>
|
||||
Warning: The URL is too long ({directUrlLength} characters) and may not work in certain browsers. Export as file instead if you encounter any problems.
|
||||
</BodyText>
|
||||
}
|
||||
<p className="Reference" style={{
|
||||
fontSize: "10pt",
|
||||
color: "#aaaaaa",
|
||||
|
||||
...!showDirectUrl && {
|
||||
textOverflow: "ellipsis",
|
||||
overflowX: "hidden",
|
||||
whiteSpace: "nowrap",
|
||||
}
|
||||
}}>
|
||||
{directUrl}
|
||||
</p>
|
||||
|
||||
<BodyText>
|
||||
<button className="MainButton" onClick={()=>{
|
||||
setShowDirectUrl(!showDirectUrl);
|
||||
}}>{showDirectUrl ? "Hide" : "Expand"}</button>
|
||||
<button className="MainButton" disabled={currentText !== commandText} onClick={()=>{
|
||||
window.navigator.clipboard.writeText(directUrl);
|
||||
setShowCopiedMessage(true);
|
||||
}}>
|
||||
Copy
|
||||
</button>
|
||||
{
|
||||
showCopiedMessage && <span className="Example">Link copied!</span>
|
||||
}
|
||||
|
||||
</BodyText>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</TitledList>
|
||||
</div>
|
||||
|
|
Reference in a new issue