diff --git a/.gitignore b/.gitignore index d150aeb..dacbd13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /node_modules/ -/build/ +/.build/ /library/ \ No newline at end of file diff --git a/build-ghpages.bat b/build-ghpages.bat deleted file mode 100644 index 411c58b..0000000 --- a/build-ghpages.bat +++ /dev/null @@ -1,10 +0,0 @@ -git branch -D ghpages -git checkout -b ghpages -del .gitignore -move .gitignore.ghpages .gitignore -cmd /C npm run build -git describe --tags --match v* > build/build_version.txt -git add -A -git commit -m "build GitHub Pages" -git push -f origin ghpages -git checkout main \ No newline at end of file diff --git a/index.html b/index.html index a891ab1..46e4cc7 100644 --- a/index.html +++ b/index.html @@ -5,46 +5,46 @@ - TheoryTracker - + TheoryTracker2 + + + + -
+
- + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 8490349..a64b3cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,35 +1,24 @@ { - "name": "theorytracker", + "name": "theorytracker2", "version": "0.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "theorytracker", + "name": "theorytracker2", "version": "0.1.0", "dependencies": { - "@babel/core": "^7.22.5", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/preset-env": "^7.22.5", - "@babel/preset-react": "^7.22.5", - "@types/node": "^20.2.5", - "@types/react": "^18.2.9", - "@types/react-dom": "^18.2.4", - "@types/styled-components": "^5.1.26", - "@types/wicg-file-system-access": "^2020.9.6", - "babel-loader": "^9.1.2", - "core-js": "^3.30.2", - "immutable": "^4.3.0", - "pako": "^2.1.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "regenerator-runtime": "^0.13.11", - "source-map-loader": "^4.0.1", - "styled-components": "^5.3.11", - "ts-loader": "^9.4.3", - "typescript": "^5.1.3", - "webpack": "^5.86.0", - "webpack-cli": "^5.1.4" + "@babel/preset-typescript": "^7.28.5", + "@types/wicg-file-system-access": "^2023.10.7", + "babel-loader": "^10.0.0", + "babel-preset-solid": "^1.9.10", + "immutable": "^5.1.4", + "solid-js": "^1.9.10", + "solid-styled-components": "^0.28.5", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "webpack": "^5.103.0", + "webpack-cli": "^6.0.1" } }, "node_modules/@ampproject/remapping": { @@ -45,11 +34,14 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -67,6 +59,7 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.5", @@ -93,49 +86,38 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", - "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", - "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -173,35 +155,18 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz", - "integrity": "sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", + "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz", - "integrity": "sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", - "semver": "^6.3.0" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.5", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -210,121 +175,88 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz", - "integrity": "sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", - "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dependencies": { - "@babel/types": "^7.22.5" - }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", - "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", - "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz", - "integrity": "sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g==", + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-wrap-function": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -333,123 +265,67 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", - "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz", - "integrity": "sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==", - "dependencies": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" - }, + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", - "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "license": "MIT", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/types": "^7.28.5" }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", "bin": { "parser": "bin/babel-parser.js" }, @@ -457,43 +333,44 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", - "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", - "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" }, "peerDependencies": { - "@babel/core": "^7.13.0" + "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -502,10 +379,18 @@ "@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", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", + "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, "engines": { "node": ">=6.9.0" }, @@ -513,1752 +398,1646 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "node_modules/@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" }, "engines": { - "node": ">=4" + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" + "@jridgewell/trace-mapping": "0.3.9" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=12" } }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "license": "MIT", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=14.17.0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/estree": "*", + "@types/json-schema": "*" } }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@types/eslint": "*", + "@types/estree": "*" } }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.2.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", + "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==", + "peer": true + }, + "node_modules/@types/wicg-file-system-access": { + "version": "2023.10.7", + "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.7.tgz", + "integrity": "sha512-g49ijasEJvCd7ifmAY2D0wdEtt1xRjBbA33PJTiv8mKBr7DoMsPeISoJ8oQOTopSRi+FBWPpPW5ouDj2QPKtGA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" } }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@xtuc/ieee754": "^1.2.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", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "@xtuc/long": "4.2.2" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz", - "integrity": "sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==", + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", - "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" } }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=18.12.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" } }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", - "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=18.12.0" }, "peerDependencies": { - "@babel/core": "^7.12.0" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz", - "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "globals": "^11.1.0" - }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=18.12.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=0.4.0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", - "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=10.13.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "acorn": "^8.14.0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", + "node_modules/acorn-walk": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "acorn": "^8.11.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=0.4.0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", - "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" + "ajv": "^8.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" + "fast-deep-equal": "^3.1.3" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "ajv": "^8.8.2" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", - "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "node_modules/babel-loader": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.0.0.tgz", + "integrity": "sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + "find-up": "^5.0.0" }, "engines": { - "node": ">=6.9.0" + "node": "^18.20.0 || ^20.10.0 || >=22.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.12.0", + "webpack": ">=5.61.0" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", - "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", + "node_modules/babel-loader/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=10" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", + "node_modules/babel-loader/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=10" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", - "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", + "node_modules/babel-loader/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=10" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", + "node_modules/babel-loader/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=10" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", - "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", + "node_modules/babel-plugin-jsx-dom-expressions": { + "version": "0.40.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.3.tgz", + "integrity": "sha512-5HOwwt0BYiv/zxl7j8Pf2bGL6rDXfV6nUhLs8ygBX+EFJXzBPHM/euj9j/6deMZ6wa52Wb2PBaAV5U/jKwIY1w==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" + "@babel/helper-module-imports": "7.18.6", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.20.7", + "html-entities": "2.3.3", + "parse5": "^7.1.2" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.20.12" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", + "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/types": "^7.18.6" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "node_modules/babel-preset-solid": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.10.tgz", + "integrity": "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ==", + "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" + "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0", + "solid-js": "^1.9.10" + }, + "peerDependenciesMeta": { + "solid-js": { + "optional": true + } } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", - "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", - "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node_modules/baseline-browser-mapping": { + "version": "2.9.7", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.7.tgz", + "integrity": "sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", - "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, - "engines": { - "node": ">=6.9.0" + "bin": { + "browserslist": "cli.js" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", - "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, + "node_modules/caniuse-lite": { + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=6.0" } }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=6" } }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", - "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "safe-buffer": "~5.1.1" } }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", - "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 8" } }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", - "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "peer": true + }, + "node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.5" + "ms": "2.1.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=6.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" - }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=0.3.1" } }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", - "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=10.13.0" } }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz", - "integrity": "sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", "engines": { - "node": ">=6.9.0" + "node": ">=0.12" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", - "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=4" } }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, + "node_modules/es-module-lexer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=6" } }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", - "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=8.0.0" } }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=4.0" } }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", - "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=4.0" } }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz", - "integrity": "sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/types": "^7.22.5" - }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=4.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", - "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.22.5" - }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=0.8.x" } }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz", - "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==" + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=8" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", - "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" } }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "license": "BSD-2-Clause" + }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", "peerDependencies": { - "@babel/core": "^7.0.0-0" + "csstype": "^3.0.10" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "function-bind": "^1.1.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 0.4.0" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=8" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "license": "MIT" + }, + "node_modules/immutable": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "license": "MIT" + }, + "node_modules/import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" }, - "engines": { - "node": ">=6.9.0" + "bin": { + "import-local-fixture": "fixtures/cli.js" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "engines": { + "node": ">=8" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", - "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=10.13.0" } }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" + "has": "^1.0.3" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "isobject": "^3.0.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=0.10.0" } }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=0.10.0" } }, - "node_modules/@babel/preset-env": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.5.tgz", - "integrity": "sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A==", + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.5", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.5", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.5", - "@babel/plugin-transform-classes": "^7.22.5", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.5", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.5", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.5", - "@babel/plugin-transform-for-of": "^7.22.5", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.5", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.5", - "@babel/plugin-transform-modules-systemjs": "^7.22.5", - "@babel/plugin-transform-modules-umd": "^7.22.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", - "@babel/plugin-transform-numeric-separator": "^7.22.5", - "@babel/plugin-transform-object-rest-spread": "^7.22.5", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5", - "@babel/plugin-transform-parameters": "^7.22.5", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.5", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.5", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.5", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.3", - "babel-plugin-polyfill-corejs3": "^0.8.1", - "babel-plugin-polyfill-regenerator": "^0.5.0", - "core-js-compat": "^3.30.2", - "semver": "^6.3.0" + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 10.13.0" } }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, - "node_modules/@babel/preset-react": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.5.tgz", - "integrity": "sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-transform-react-display-name": "^7.22.5", - "@babel/plugin-transform-react-jsx": "^7.22.5", - "@babel/plugin-transform-react-jsx-development": "^7.22.5", - "@babel/plugin-transform-react-pure-annotations": "^7.22.5" + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=6" } }, - "node_modules/@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, - "node_modules/@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", - "dependencies": { - "regenerator-runtime": "^0.13.11" + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" }, "engines": { - "node": ">=6.9.0" + "node": ">=6" } }, - "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", - "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" - }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=0.10.0" } }, - "node_modules/@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", - "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" + "p-locate": "^4.1.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=8" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", - "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", "engines": { - "node": ">=10.0.0" + "node": ">= 0.6" } }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", - "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "node_modules/mime-types": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", "dependencies": { - "@emotion/memoize": "^0.8.0" + "mime-db": "1.48.0" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, - "node_modules/@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT" }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">=8" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "engines": { - "node": ">=6.0.0" + "node": ">=6" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "engines": { - "node": ">=6.0.0" + "node": ">=8" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" } }, - "node_modules/@types/eslint": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", - "integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==", + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "safe-buffer": "^5.1.0" } }, - "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" - }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" } }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" - }, - "node_modules/@types/node": { - "version": "20.2.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", - "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==" - }, - "node_modules/@types/prop-types": { - "version": "15.7.3", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/@types/react": { - "version": "18.2.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.9.tgz", - "integrity": "sha512-pL3JAesUkF7PEQGxh5XOwdXGV907te6m1/Qe1ERJLgomojS6Ne790QiA7GUl434JEkFA2aAaB6qJ5z4e1zJn/w==", + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@types/react-dom": { - "version": "18.2.4", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", - "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dependencies": { - "@types/react": "*" + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@types/scheduler": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz", - "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==" - }, - "node_modules/@types/styled-components": { - "version": "5.1.26", - "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", - "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", - "dependencies": { - "@types/hoist-non-react-statics": "*", - "@types/react": "*", - "csstype": "^3.0.2" + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" } }, - "node_modules/@types/wicg-file-system-access": { - "version": "2020.9.6", - "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.6.tgz", - "integrity": "sha512-6hogE75Hl2Ov/jgp8ZhDaGmIF/q3J07GtXf8nCJCwKTHq7971po5+DId7grft09zG7plBwpF6ZU0yx9Du4/e1A==" + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" + "randombytes": "^2.1.0" } }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + "node_modules/seroval": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", + "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "node_modules/seroval-plugins": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz", + "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "license": "MIT", "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dependencies": { - "@xtuc/long": "4.2.2" + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" } }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "node_modules/solid-js": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz", + "integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==", + "license": "MIT", + "peer": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "csstype": "^3.1.0", + "seroval": "~1.3.0", + "seroval-plugins": "~1.3.0" } }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "node_modules/solid-styled-components": { + "version": "0.28.5", + "resolved": "https://registry.npmjs.org/solid-styled-components/-/solid-styled-components-0.28.5.tgz", + "integrity": "sha512-vwTcdp76wZNnESIzB6rRZ3U55NgcSAQXCiiRIiEFhxTFqT0bEh/warNT1qaRZu4OkAzrBkViOngF35ktI8sc4A==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "csstype": "^3.1.0", + "goober": "^2.1.10" + }, + "peerDependencies": { + "solid-js": "^1.4.4" } }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=14.15.0" + "node": ">=10" }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "engines": { - "node": ">=14.15.0" + "node": ">= 0.4" }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "node": ">=6" }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" - }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "node_modules/terser": { + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, "bin": { - "acorn": "bin/acorn" + "terser": "bin/terser" }, "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "peerDependencies": { - "acorn": "^8" + "node": ">=10" } }, - "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "node_modules/terser-webpack-plugin": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dependencies": { - "ajv": "^8.0.0" + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "ajv": "^8.0.0" + "webpack": "^5.1.0" }, "peerDependenciesMeta": { - "ajv": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { "optional": true } } }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/babel-loader": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", - "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", - "dependencies": { - "find-cache-dir": "^3.3.2", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz", - "integrity": "sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==", - "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.4.0", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz", - "integrity": "sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.0", - "core-js-compat": "^3.30.1" + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz", - "integrity": "sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.0" + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-styled-components": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", - "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-module-imports": "^7.16.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "lodash": "^4.17.11", - "picomatch": "^2.3.0" + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" }, - "peerDependencies": { - "styled-components": ">= 2" + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } } }, - "node_modules/babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "^7.0.1" + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=8" + "node": ">=14.17" } }, - "node_modules/browserslist": { - "version": "4.21.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz", - "integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==", + "node_modules/update-browserslist-db": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", "funding": [ { "type": "opencollective", @@ -2273,2803 +2052,523 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001489", - "electron-to-chromium": "^1.4.411", - "node-releases": "^2.0.12", - "update-browserslist-db": "^1.0.11" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { - "browserslist": "cli.js" + "update-browserslist-db": "cli.js" }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/camelize": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", - "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001497", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001497.tgz", - "integrity": "sha512-I4/duVK4wL6rAK/aKZl3HXB4g+lIZvaT4VLAn2rCgJ38jVLb0lv2Xug6QuqmxXFVRJMF74SPPWPJ/1Sdm3vCzw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" }, "engines": { - "node": ">=4" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "engines": { - "node": ">=6.0" + "node": ">=10.13.0" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" + "node_modules/webpack": { + "version": "5.103.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.103.0.tgz", + "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.26.3", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.3", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" }, "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==" - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, - "node_modules/convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/core-js": { - "version": "3.30.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.2.tgz", - "integrity": "sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.30.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz", - "integrity": "sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==", - "dependencies": { - "browserslist": "^4.21.5" + "node": ">=10.13.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=", - "engines": { - "node": ">=4" - } - }, - "node_modules/css-to-react-native": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", - "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, - "node_modules/csstype": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", - "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" - }, - "node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "url": "https://opencollective.com/webpack" }, "peerDependenciesMeta": { - "supports-color": { + "webpack-cli": { "optional": true } } }, - "node_modules/electron-to-chromium": { - "version": "1.4.427", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.427.tgz", - "integrity": "sha512-HK3r9l+Jm8dYAm1ctXEWIC+hV60zfcjS9UA5BDlYvnI5S7PU/yytjpvSrTNrSSRRkuu3tDyZhdkwIczh+0DWaw==" - }, - "node_modules/enhanced-resolve": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz", - "integrity": "sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", - "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==" - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", - "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==" - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/immutable": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", - "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==" - }, - "node_modules/import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", - "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.31", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", - "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", - "dependencies": { - "mime-db": "1.48.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "node_modules/node-releases": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", - "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==" - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "peer": true - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "node_modules/regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "dependencies": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dependencies": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/schema-utils": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.1.0.tgz", - "integrity": "sha512-Jw+GZVbP5IggB2WAn6UHI02LBwGmsIeYN/lNbSMZyDziQ7jmtAUrqKqDja+W89YHVs+KL/3IkIMltAklqB1vAw==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", - "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", - "dependencies": { - "abab": "^2.0.6", - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.72.1" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/styled-components": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", - "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^1.1.0", - "@emotion/stylis": "^0.8.4", - "@emotion/unitless": "^0.7.4", - "babel-plugin-styled-components": ">= 1.12.0", - "css-to-react-native": "^3.0.0", - "hoist-non-react-statics": "^3.0.0", - "shallowequal": "^1.1.0", - "supports-color": "^5.5.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0", - "react-is": ">= 16.8.0" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.17.7", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.7.tgz", - "integrity": "sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", - "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-loader": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.3.tgz", - "integrity": "sha512-n3hBnm6ozJYzwiwt5YRiJZkzktftRpMiBApHaJPoWLA+qetQBAXkHqCLM6nwSdRDimqVtA5ocIkcTRLMTt7yzA==", - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-loader/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ts-loader/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ts-loader/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ts-loader/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/ts-loader/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-loader/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-loader/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "engines": { - "node": ">=4" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack": { - "version": "5.86.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.86.0.tgz", - "integrity": "sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg==", - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.14.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "engines": { - "node": ">=14" - } - }, - "node_modules/webpack-merge": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", - "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", - "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", - "requires": { - "@babel/highlight": "^7.22.5" - } - }, - "@babel/compat-data": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", - "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==" - }, - "@babel/core": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", - "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helpers": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" - } - }, - "@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", - "requires": { - "@babel/types": "^7.22.5", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz", - "integrity": "sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", - "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", - "requires": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz", - "integrity": "sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz", - "integrity": "sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", - "semver": "^6.3.0" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz", - "integrity": "sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==", - "requires": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==" - }, - "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", - "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz", - "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-module-imports": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-module-transforms": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", - "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", - "requires": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==" - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz", - "integrity": "sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-wrap-function": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-replace-supers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz", - "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==", - "requires": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", - "requires": { - "@babel/types": "^7.22.5" - } - }, - "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" - }, - "@babel/helper-validator-option": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==" - }, - "@babel/helper-wrap-function": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz", - "integrity": "sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==", - "requires": { - "@babel/helper-function-name": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/helpers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", - "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", - "requires": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" - } - }, - "@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", - "requires": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==" - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz", - "integrity": "sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz", - "integrity": "sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@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", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "requires": {} - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz", - "integrity": "sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-import-attributes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz", - "integrity": "sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz", - "integrity": "sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-async-generator-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz", - "integrity": "sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==", - "requires": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", - "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", - "requires": { - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-remap-async-to-generator": "^7.22.5" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz", - "integrity": "sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz", - "integrity": "sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-class-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz", - "integrity": "sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-class-static-block": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz", - "integrity": "sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz", - "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz", - "integrity": "sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/template": "^7.22.5" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz", - "integrity": "sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz", - "integrity": "sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz", - "integrity": "sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-dynamic-import": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz", - "integrity": "sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz", - "integrity": "sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==", - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-export-namespace-from": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz", - "integrity": "sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz", - "integrity": "sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz", - "integrity": "sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==", - "requires": { - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-json-strings": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz", - "integrity": "sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz", - "integrity": "sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz", - "integrity": "sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz", - "integrity": "sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", - "requires": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz", - "integrity": "sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==", - "requires": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz", - "integrity": "sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==", - "requires": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } } }, - "@babel/plugin-transform-modules-umd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz", - "integrity": "sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==", - "requires": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" } }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" } }, - "@babel/plugin-transform-new-target": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz", - "integrity": "sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" } }, - "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz", - "integrity": "sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "@babel/plugin-transform-numeric-separator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz", - "integrity": "sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "license": "MIT" }, - "@babel/plugin-transform-object-rest-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz", - "integrity": "sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==", - "requires": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.22.5" + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" } }, - "@babel/plugin-transform-object-super": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz", - "integrity": "sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5" + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } - }, - "@babel/plugin-transform-optional-catch-binding": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz", - "integrity": "sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==", + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" } }, - "@babel/plugin-transform-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz", - "integrity": "sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ==", + "@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" } }, - "@babel/plugin-transform-parameters": { + "@babel/compat-data": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz", - "integrity": "sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", + "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==" }, - "@babel/plugin-transform-private-methods": { + "@babel/core": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz", - "integrity": "sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", + "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", + "peer": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" } }, - "@babel/plugin-transform-private-property-in-object": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz", - "integrity": "sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==", + "@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + } } }, - "@babel/plugin-transform-property-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz", - "integrity": "sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==", + "@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "requires": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/types": "^7.27.3" } }, - "@babel/plugin-transform-react-display-name": { + "@babel/helper-compilation-targets": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz", - "integrity": "sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", + "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", "requires": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/compat-data": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } } }, - "@babel/plugin-transform-react-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz", - "integrity": "sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==", + "@babel/helper-create-class-features-plugin": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", + "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.5", + "semver": "^6.3.1" } }, - "@babel/plugin-transform-react-jsx-development": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz", - "integrity": "sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==", - "requires": { - "@babel/plugin-transform-react-jsx": "^7.22.5" - } + "@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==" }, - "@babel/plugin-transform-react-pure-annotations": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz", - "integrity": "sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==", + "@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" } }, - "@babel/plugin-transform-regenerator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz", - "integrity": "sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==", + "@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "regenerator-transform": "^0.15.1" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" } }, - "@babel/plugin-transform-reserved-words": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz", - "integrity": "sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==", + "@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "requires": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" } }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz", - "integrity": "sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==", + "@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "requires": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/types": "^7.27.1" } }, - "@babel/plugin-transform-spread": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz", - "integrity": "sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" - } + "@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==" }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz", - "integrity": "sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==", + "@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", "requires": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" } }, - "@babel/plugin-transform-template-literals": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz", - "integrity": "sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==", + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "requires": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" } }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz", - "integrity": "sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } + "@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz", - "integrity": "sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==", - "requires": { - "@babel/helper-plugin-utils": "^7.22.5" - } + "@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" }, - "@babel/plugin-transform-unicode-property-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz", - "integrity": "sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==", + "@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==" + }, + "@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" } }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz", - "integrity": "sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==", + "@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/types": "^7.28.5" } }, - "@babel/plugin-transform-unicode-sets-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz", - "integrity": "sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==", + "@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.27.1" } }, - "@babel/preset-env": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.5.tgz", - "integrity": "sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A==", + "@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "requires": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.5", - "@babel/plugin-transform-async-to-generator": "^7.22.5", - "@babel/plugin-transform-block-scoped-functions": "^7.22.5", - "@babel/plugin-transform-block-scoping": "^7.22.5", - "@babel/plugin-transform-class-properties": "^7.22.5", - "@babel/plugin-transform-class-static-block": "^7.22.5", - "@babel/plugin-transform-classes": "^7.22.5", - "@babel/plugin-transform-computed-properties": "^7.22.5", - "@babel/plugin-transform-destructuring": "^7.22.5", - "@babel/plugin-transform-dotall-regex": "^7.22.5", - "@babel/plugin-transform-duplicate-keys": "^7.22.5", - "@babel/plugin-transform-dynamic-import": "^7.22.5", - "@babel/plugin-transform-exponentiation-operator": "^7.22.5", - "@babel/plugin-transform-export-namespace-from": "^7.22.5", - "@babel/plugin-transform-for-of": "^7.22.5", - "@babel/plugin-transform-function-name": "^7.22.5", - "@babel/plugin-transform-json-strings": "^7.22.5", - "@babel/plugin-transform-literals": "^7.22.5", - "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", - "@babel/plugin-transform-member-expression-literals": "^7.22.5", - "@babel/plugin-transform-modules-amd": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.5", - "@babel/plugin-transform-modules-systemjs": "^7.22.5", - "@babel/plugin-transform-modules-umd": "^7.22.5", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.22.5", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", - "@babel/plugin-transform-numeric-separator": "^7.22.5", - "@babel/plugin-transform-object-rest-spread": "^7.22.5", - "@babel/plugin-transform-object-super": "^7.22.5", - "@babel/plugin-transform-optional-catch-binding": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5", - "@babel/plugin-transform-parameters": "^7.22.5", - "@babel/plugin-transform-private-methods": "^7.22.5", - "@babel/plugin-transform-private-property-in-object": "^7.22.5", - "@babel/plugin-transform-property-literals": "^7.22.5", - "@babel/plugin-transform-regenerator": "^7.22.5", - "@babel/plugin-transform-reserved-words": "^7.22.5", - "@babel/plugin-transform-shorthand-properties": "^7.22.5", - "@babel/plugin-transform-spread": "^7.22.5", - "@babel/plugin-transform-sticky-regex": "^7.22.5", - "@babel/plugin-transform-template-literals": "^7.22.5", - "@babel/plugin-transform-typeof-symbol": "^7.22.5", - "@babel/plugin-transform-unicode-escapes": "^7.22.5", - "@babel/plugin-transform-unicode-property-regex": "^7.22.5", - "@babel/plugin-transform-unicode-regex": "^7.22.5", - "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.3", - "babel-plugin-polyfill-corejs3": "^0.8.1", - "babel-plugin-polyfill-regenerator": "^0.5.0", - "core-js-compat": "^3.30.2", - "semver": "^6.3.0" + "@babel/helper-plugin-utils": "^7.27.1" } }, - "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", + "@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" } }, - "@babel/preset-react": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.22.5.tgz", - "integrity": "sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==", + "@babel/plugin-transform-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", + "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==", "requires": { - "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "@babel/plugin-transform-react-display-name": "^7.22.5", - "@babel/plugin-transform-react-jsx": "^7.22.5", - "@babel/plugin-transform-react-jsx-development": "^7.22.5", - "@babel/plugin-transform-react-pure-annotations": "^7.22.5" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" } }, - "@babel/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" - }, - "@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", + "@babel/preset-typescript": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", "requires": { - "regenerator-runtime": "^0.13.11" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.28.5" } }, "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" } }, "@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", - "debug": "^4.1.0", - "globals": "^11.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" } }, "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" } }, - "@discoveryjs/json-ext": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", - "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==" - }, - "@emotion/is-prop-valid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", - "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "requires": { - "@emotion/memoize": "^0.8.0" + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } } }, - "@emotion/memoize": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", - "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" - }, - "@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" - }, - "@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + "@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==" }, "@jridgewell/gen-mapping": { "version": "0.1.1", @@ -5086,277 +2585,250 @@ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" }, "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" }, "@jridgewell/source-map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" }, "dependencies": { "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } } } }, "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" }, "@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==" + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, "@types/eslint": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", - "integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "requires": { "@types/estree": "*", "@types/json-schema": "*" } }, "@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "requires": { "@types/eslint": "*", "@types/estree": "*" } }, "@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" - }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "@types/node": { "version": "20.2.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", - "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==" - }, - "@types/prop-types": { - "version": "15.7.3", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", - "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" - }, - "@types/react": { - "version": "18.2.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.9.tgz", - "integrity": "sha512-pL3JAesUkF7PEQGxh5XOwdXGV907te6m1/Qe1ERJLgomojS6Ne790QiA7GUl434JEkFA2aAaB6qJ5z4e1zJn/w==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-dom": { - "version": "18.2.4", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", - "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", - "requires": { - "@types/react": "*" - } - }, - "@types/scheduler": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz", - "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==" - }, - "@types/styled-components": { - "version": "5.1.26", - "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", - "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", - "requires": { - "@types/hoist-non-react-statics": "*", - "@types/react": "*", - "csstype": "^3.0.2" - } + "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==", + "peer": true }, "@types/wicg-file-system-access": { - "version": "2020.9.6", - "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.6.tgz", - "integrity": "sha512-6hogE75Hl2Ov/jgp8ZhDaGmIF/q3J07GtXf8nCJCwKTHq7971po5+DId7grft09zG7plBwpF6ZU0yx9Du4/e1A==" + "version": "2023.10.7", + "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.7.tgz", + "integrity": "sha512-g49ijasEJvCd7ifmAY2D0wdEtt1xRjBbA33PJTiv8mKBr7DoMsPeISoJ8oQOTopSRi+FBWPpPW5ouDj2QPKtGA==" }, "@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "requires": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" }, "@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" }, "@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" }, "@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" }, "@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" }, "@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "requires": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", "requires": {} }, "@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", "requires": {} }, "@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", "requires": {} }, "@xtuc/ieee754": { @@ -5369,31 +2841,36 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==" - }, "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==" - }, - "acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "peer": true + }, + "acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "requires": {} }, + "acorn-walk": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "requires": { + "acorn": "^8.11.0" + } + }, "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "peer": true, "requires": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" } }, "ajv-formats": { @@ -5412,84 +2889,100 @@ "fast-deep-equal": "^3.1.3" } }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, "babel-loader": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", - "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", - "requires": { - "find-cache-dir": "^3.3.2", - "schema-utils": "^4.0.0" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz", - "integrity": "sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.0.0.tgz", + "integrity": "sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==", "requires": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.4.0", - "semver": "^6.1.1" - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz", - "integrity": "sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==", - "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.0", - "core-js-compat": "^3.30.1" + "find-up": "^5.0.0" + }, + "dependencies": { + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + } } }, - "babel-plugin-polyfill-regenerator": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz", - "integrity": "sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==", + "babel-plugin-jsx-dom-expressions": { + "version": "0.40.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.3.tgz", + "integrity": "sha512-5HOwwt0BYiv/zxl7j8Pf2bGL6rDXfV6nUhLs8ygBX+EFJXzBPHM/euj9j/6deMZ6wa52Wb2PBaAV5U/jKwIY1w==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.0" + "@babel/helper-module-imports": "7.18.6", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.20.7", + "html-entities": "2.3.3", + "parse5": "^7.1.2" + }, + "dependencies": { + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "requires": { + "@babel/types": "^7.18.6" + } + } } }, - "babel-plugin-styled-components": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", - "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "babel-preset-solid": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.10.tgz", + "integrity": "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ==", "requires": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-module-imports": "^7.16.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "lodash": "^4.17.11", - "picomatch": "^2.3.0" + "babel-plugin-jsx-dom-expressions": "^0.40.3" } }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { - "fill-range": "^7.0.1" - } + "baseline-browser-mapping": { + "version": "2.9.7", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.7.tgz", + "integrity": "sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==" }, "browserslist": { - "version": "4.21.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz", - "integrity": "sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "peer": true, "requires": { - "caniuse-lite": "^1.0.30001489", - "electron-to-chromium": "^1.4.411", - "node-releases": "^2.0.12", - "update-browserslist-db": "^1.0.11" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" } }, "buffer-from": { @@ -5497,25 +2990,10 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, - "camelize": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", - "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" - }, "caniuse-lite": { - "version": "1.0.30001497", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001497.tgz", - "integrity": "sha512-I4/duVK4wL6rAK/aKZl3HXB4g+lIZvaT4VLAn2rCgJ38jVLb0lv2Xug6QuqmxXFVRJMF74SPPWPJ/1Sdm3vCzw==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==" }, "chrome-trace-event": { "version": "1.0.3", @@ -5532,19 +3010,6 @@ "shallow-clone": "^3.0.0" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, "colorette": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", @@ -5555,11 +3020,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" - }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -5568,48 +3028,26 @@ "safe-buffer": "~5.1.1" } }, - "core-js": { - "version": "3.30.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.2.tgz", - "integrity": "sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg==" - }, - "core-js-compat": { - "version": "3.30.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.2.tgz", - "integrity": "sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==", - "requires": { - "browserslist": "^4.21.5" - } + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, - "css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" - }, - "css-to-react-native": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", - "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", - "requires": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, "csstype": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", - "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "peer": true }, "debug": { "version": "4.3.1", @@ -5619,24 +3057,34 @@ "ms": "2.1.2" } }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + }, "electron-to-chromium": { - "version": "1.4.427", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.427.tgz", - "integrity": "sha512-HK3r9l+Jm8dYAm1ctXEWIC+hV60zfcjS9UA5BDlYvnI5S7PU/yytjpvSrTNrSSRRkuu3tDyZhdkwIczh+0DWaw==" + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==" }, "enhanced-resolve": { - "version": "5.14.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz", - "integrity": "sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==", + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, + "entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==" + }, "envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==" + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==" }, "es-module-lexer": { "version": "1.2.1", @@ -5644,14 +3092,9 @@ "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==" }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, "eslint-scope": { "version": "5.1.1", @@ -5682,11 +3125,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -5697,34 +3135,16 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==" }, "fastest-levenshtein": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==" }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -5734,10 +3154,15 @@ "path-exists": "^4.0.0" } }, + "flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" + }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "gensync": { "version": "1.0.0-beta.2", @@ -5749,15 +3174,16 @@ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + "goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "requires": {} }, "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "has": { "version": "1.0.3", @@ -5768,37 +3194,19 @@ } }, "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } + "html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" }, "immutable": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", - "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==" + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==" }, "import-local": { "version": "3.0.2", @@ -5822,11 +3230,6 @@ "has": "^1.0.3" } }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -5843,7 +3246,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" }, "jest-worker": { "version": "27.5.1", @@ -5853,21 +3256,6 @@ "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "requires": { - "has-flag": "^4.0.0" - } - } } }, "js-tokens": { @@ -5876,9 +3264,9 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==" }, "json-parse-even-better-errors": { "version": "2.3.1", @@ -5901,9 +3289,9 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "loader-runner": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", - "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==" }, "locate-path": { "version": "5.0.0", @@ -5913,54 +3301,16 @@ "p-locate": "^4.1.0" } }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - } + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, "mime-db": { "version": "1.48.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", @@ -5985,9 +3335,9 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node-releases": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", - "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==" + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==" }, "p-limit": { "version": "2.3.0", @@ -6010,10 +3360,13 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, - "pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + "parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "requires": { + "entities": "^6.0.0" + } }, "path-exists": { "version": "4.0.0", @@ -6031,14 +3384,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "pkg-dir": { "version": "4.2.0", @@ -6048,16 +3396,6 @@ "find-up": "^4.0.0" } }, - "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" - }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -6066,29 +3404,6 @@ "safe-buffer": "^5.1.0" } }, - "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "requires": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "peer": true - }, "rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -6097,60 +3412,6 @@ "resolve": "^1.20.0" } }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "regenerator-transform": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", - "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regexpu-core": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", - "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", - "requires": { - "@babel/regjsgen": "^0.8.0", - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - } - }, - "regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" - } - } - }, "require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -6184,23 +3445,10 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "requires": { - "loose-envify": "^1.1.0" - } - }, "schema-utils": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.1.0.tgz", - "integrity": "sha512-Jw+GZVbP5IggB2WAn6UHI02LBwGmsIeYN/lNbSMZyDziQ7jmtAUrqKqDja+W89YHVs+KL/3IkIMltAklqB1vAw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "requires": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -6209,18 +3457,30 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" }, "serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "requires": { "randombytes": "^2.1.0" } }, + "seroval": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", + "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", + "peer": true + }, + "seroval-plugins": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz", + "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==", + "requires": {} + }, "shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -6229,11 +3489,6 @@ "kind-of": "^6.0.2" } }, - "shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6247,26 +3502,31 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, + "solid-js": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz", + "integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==", + "peer": true, + "requires": { + "csstype": "^3.1.0", + "seroval": "~1.3.0", + "seroval-plugins": "~1.3.0" + } + }, + "solid-styled-components": { + "version": "0.28.5", + "resolved": "https://registry.npmjs.org/solid-styled-components/-/solid-styled-components-0.28.5.tgz", + "integrity": "sha512-vwTcdp76wZNnESIzB6rRZ3U55NgcSAQXCiiRIiEFhxTFqT0bEh/warNT1qaRZu4OkAzrBkViOngF35ktI8sc4A==", + "requires": { + "csstype": "^3.1.0", + "goober": "^2.1.10" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" - }, - "source-map-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", - "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", - "requires": { - "abab": "^2.0.6", - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.2" - } - }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -6276,29 +3536,12 @@ "source-map": "^0.6.0" } }, - "styled-components": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", - "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^1.1.0", - "@emotion/stylis": "^0.8.4", - "@emotion/unitless": "^0.7.4", - "babel-plugin-styled-components": ">= 1.12.0", - "css-to-react-native": "^3.0.0", - "hoist-non-react-statics": "^3.0.0", - "shallowequal": "^1.1.0", - "supports-color": "^5.5.0" - } - }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } }, "supports-preserve-symlinks-flag": { @@ -6307,304 +3550,157 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==" }, "terser": { - "version": "5.17.7", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.7.tgz", - "integrity": "sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==", + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "requires": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" } }, "terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "requires": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "requires": {} - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "schema-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", - "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-loader": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.3.tgz", - "integrity": "sha512-n3hBnm6ozJYzwiwt5YRiJZkzktftRpMiBApHaJPoWLA+qetQBAXkHqCLM6nwSdRDimqVtA5ocIkcTRLMTt7yzA==", - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + } + }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" } }, "typescript": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", - "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==" - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==" - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", - "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==" - }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "peer": true }, "update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", + "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" } }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "requires": { - "punycode": "^2.1.0" - } + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "webpack": { - "version": "5.86.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.86.0.tgz", - "integrity": "sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg==", - "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "version": "5.103.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.103.0.tgz", + "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", + "peer": true, + "requires": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.26.3", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.14.1", + "enhanced-resolve": "^5.17.3", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.1.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "requires": {} - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "schema-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", - "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - } + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" } }, "webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "peer": true, + "requires": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", "colorette": "^2.0.14", - "commander": "^10.0.1", + "commander": "^12.1.0", "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", + "envinfo": "^7.14.0", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^3.1.1", "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" + "webpack-merge": "^6.0.1" }, "dependencies": { "commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==" + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==" } } }, "webpack-merge": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", - "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "requires": { "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" + "flat": "^5.0.2", + "wildcard": "^2.0.1" } }, "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==" }, "which": { "version": "2.0.2", @@ -6615,14 +3711,19 @@ } }, "wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==" }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" } } } diff --git a/package.json b/package.json index 074a16f..444a341 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "theorytracker", + "name": "theorytracker2", "version": "0.1.0", "description": "", "author": "hlorenzi", @@ -7,38 +7,24 @@ "main": "index.js", "type": "module", "scripts": { + "start": "http-server -p 80", "build": "webpack --config webpack.config.cjs", + "build-profile": "webpack --config webpack.config.cjs --profile --json > webpack.stats.json && npx webpack-bundle-analyzer webpack.stats.json ./.build/", "dev": "webpack --config webpack.config.cjs --watch --mode development", - "start": "http-server -p 80", - "test": "node ./build/test.js" + "test": "echo \"Error: no test specified\" && exit 1" }, - "browserslist": [ - "> 2%", - "not dead", - "not ie 11" - ], + "browserslist": "> 2%, not dead", "dependencies": { - "@babel/core": "^7.22.5", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/preset-env": "^7.22.5", - "@babel/preset-react": "^7.22.5", - "@types/node": "^20.2.5", - "@types/react": "^18.2.9", - "@types/react-dom": "^18.2.4", - "@types/styled-components": "^5.1.26", - "@types/wicg-file-system-access": "^2020.9.6", - "babel-loader": "^9.1.2", - "core-js": "^3.30.2", - "immutable": "^4.3.0", - "pako": "^2.1.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "regenerator-runtime": "^0.13.11", - "source-map-loader": "^4.0.1", - "styled-components": "^5.3.11", - "ts-loader": "^9.4.3", - "typescript": "^5.1.3", - "webpack": "^5.86.0", - "webpack-cli": "^5.1.4" + "@babel/preset-typescript": "^7.28.5", + "@types/wicg-file-system-access": "^2023.10.7", + "babel-loader": "^10.0.0", + "babel-preset-solid": "^1.9.10", + "immutable": "^5.1.4", + "solid-js": "^1.9.10", + "solid-styled-components": "^0.28.5", + "ts-node": "^10.9.2", + "typescript": "^5.9.3", + "webpack": "^5.103.0", + "webpack-cli": "^6.0.1" } } diff --git a/run_local_server.bat b/run_local_server.bat deleted file mode 100644 index ebb7225..0000000 --- a/run_local_server.bat +++ /dev/null @@ -1,5 +0,0 @@ -where /q http-server - -IF ERRORLEVEL 1 (npm i -g http-server) - -http-server -p 80 \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index d5a96c4..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import React from "react" -import * as Dockable from "./dockable" -import * as Command from "./command" -import * as Project from "./project" -import * as Playback from "./playback" -import * as Prefs from "./prefs" -import * as Popup from "./popup" -import * as Menubar from "./menubar" -import * as UI from "./ui" -import { useRefState } from "./util/refState" -import PlaybackToolbar from "./PlaybackToolbar" -import MenuFile from "./MenuFile" -import MenuEdit from "./MenuEdit" -import MenuWindow from "./MenuWindow" -import "./types" - - -let initializedGlobals = false - - -export default function App() -{ - if (!initializedGlobals) - { - initializedGlobals = true - Prefs.initGlobal() - Project.initGlobal() - Playback.initGlobal() - Dockable.initGlobal() - Popup.initGlobal() - } - - Project.useGlobal() - Playback.useGlobal() - Dockable.useGlobal() - Popup.useGlobal() - - const [version, setVersion] = React.useState("") - - React.useEffect(() => - { - ;(async () => - { - const versionFile = await fetch("build/build_version.txt") - const versionTxt = await versionFile.text() - if (versionTxt.startsWith("v0-")) - setVersion("v0." + versionTxt.match(".*?\-(.*?)\-")![1]) - })() - - }, []) - - - React.useEffect(() => - { - window.addEventListener("keydown", (ev: KeyboardEvent) => - { - if (document.activeElement && document.activeElement.tagName == "INPUT") - return - - const key = ev.key.toLowerCase() - - for (const command of Command.allCommands) - { - if (!command.shortcut) - continue - - if (command.isShortcutAvailable && !command.isShortcutAvailable()) - continue - - if (command.isAvailable && !command.isAvailable({})) - continue - - for (const shortcut of command.shortcut) - { - if (!!shortcut.ctrl !== ev.ctrlKey) - continue - - if (!!shortcut.shift !== ev.shiftKey) - continue - - if (key !== shortcut.key) - continue - - //console.log("handled keyboard command: ", command.name) - command.func({}) - ev.preventDefault() - ev.stopPropagation() - return - } - } - }) - - }, []) - - - return <> -
- - - - - - - - How to use the app - - - { version } - - - - { !Playback.global.synthLoading ? null : - - } - - - -
- - { !Popup.global.elem ? null : - - } - -} \ No newline at end of file diff --git a/src/command/file.ts b/src/command/file.ts index 417c71f..c3e86d6 100644 --- a/src/command/file.ts +++ b/src/command/file.ts @@ -1,47 +1,82 @@ -import * as Command from "../command" +import * as Command from "./index.ts" +import * as Global from "../state.ts" import * as Project from "../project" +import * as Timeline from "../timeline" import * as Playback from "../playback" +let fileHandleForSaving: FileSystemFileHandle | undefined = undefined + + +function isProjectSaved() +{ + const state = Global.get() + return state.project.root === state.lastSavedProject +} + + function confirmDiscard() { - if (!Project.isUnsaved()) + if (isProjectSaved()) return true return window.confirm("Discard current song?") } +function newProject() +{ + openProject(Project.makeNew()) +} + + +function openProject(project: Project.ImmutableRoot) +{ + const state = Global.get() + state.playback.stopAll() + state.project.root = project + state.lastSavedProject = project + Timeline.cursorSetTime(state.timeline, project.range.start, project.range.start) + Timeline.cursorSetLaneIndex(state.timeline, 0, 0) + Timeline.scrollTimeIntoView(state.timeline, project.range.start) + Global.refresh() +} + + function isFileSystemAccessApiAvailable(showAlert?: boolean) { - if ("showOpenFilePicker" in window) - return true; + if ("showOpenFilePicker" in (window as any)) + return true if (showAlert) { - (window as Window).alert( + window.alert( "Your browser doesn't support the File System Access API.\n\n" + "Use the other commands labeled as \"Browser\", or " + "switch to a browser with support to that API, like Chrome.") } - return false; + return false } -export function openFromFile(filename: string, fileHandle: FileSystemFileHandle | null, bytes: Uint8Array): boolean +export function openFromFile( + filename: string, + fileHandle: FileSystemFileHandle | undefined, + bytes: Uint8Array) + : boolean { if (filename.endsWith(".ttproj") || filename.endsWith(".json")) { const text = new TextDecoder("utf-8").decode(bytes) const json = JSON.parse(text) - Project.open(Project.jsonImport(json)) - Project.setFileHandleForSave(fileHandle) + openProject(Project.jsonImport(json)) + fileHandleForSaving = fileHandle } else if (filename.endsWith(".mid")) { - Project.open(Project.midiImport(bytes)) - Project.setFileHandleForSave(null) + //openProject(Project.midiImport(bytes)) + fileHandleForSaving = undefined } else { @@ -53,17 +88,17 @@ export function openFromFile(filename: string, fileHandle: FileSystemFileHandle } -export const newProject: Command.Command = +export const commandNewProject: Command.Command = { name: "New Project", icon: "📄", + shortcut: [{ shift: true, key: "n" }], func: async () => { if (!confirmDiscard()) return - Project.setNew() - Playback.setPlaying(false) + newProject() } } @@ -117,12 +152,17 @@ export const saveProjectAs: Command.Command = accept: { "application/json": [".ttproj", ".json"] }, }], }) - const jsonStr = Project.jsonExport(Project.global.project) + + const state = Global.get() + + const jsonStr = Project.jsonExport(state.project.root) const writer = await handle.createWritable() await writer.write(jsonStr) await writer.close() - Project.setFileHandleForSave(handle) - Project.markAsSaved() + + fileHandleForSaving = handle + state.lastSavedProject = state.project.root + Global.refresh() } } @@ -138,17 +178,21 @@ export const saveProject: Command.Command = if (!isFileSystemAccessApiAvailable(true)) return - if (!Project.global.curFileHandleForSave) + if (!fileHandleForSaving) { saveProjectAs.func(args) return } - const jsonStr = Project.jsonExport(Project.global.project) - const writer = await (Project.global.curFileHandleForSave as any).createWritable() + const state = Global.get() + + const jsonStr = Project.jsonExport(state.project.root) + const writer = await fileHandleForSaving.createWritable() await writer.write(jsonStr) await writer.close() - Project.markAsSaved() + + state.lastSavedProject = state.project.root + Global.refresh() } } @@ -177,7 +221,9 @@ export const downloadProjectBrowser: Command.Command = isShortcutAvailable: () => !isFileSystemAccessApiAvailable(), func: async () => { - const jsonStr = Project.jsonExport(Project.global.project) + const state = Global.get() + + const jsonStr = Project.jsonExport(state.project.root) const element = document.createElement("a") element.setAttribute("href", "data:text/json;charset=utf-8," + encodeURIComponent(jsonStr)) @@ -197,7 +243,9 @@ export const previewProjectBrowser: Command.Command = icon: "📥", func: async () => { - const jsonStr = Project.jsonExport(Project.global.project) + const state = Global.get() + + const jsonStr = Project.jsonExport(state.project.root) const newWindow = window.open()! newWindow.document.write("") @@ -207,13 +255,15 @@ export const previewProjectBrowser: Command.Command = } -export const downloadMidiBrowser: Command.Command = +/*export const downloadMidiBrowser: Command.Command = { name: "[Browser] Download MIDI", icon: "📥", func: async () => { - const bytes = Project.midiExport(Project.global.project) + const state = Global.get() + + const bytes = Project.midiExport(state.project.root) const element = document.createElement("a") element.setAttribute("href", "data:audio/midi;base64," + btoa(String.fromCharCode(...bytes))) @@ -224,4 +274,4 @@ export const downloadMidiBrowser: Command.Command = element.click() document.body.removeChild(element) } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/command/index.ts b/src/command/index.ts index d13a27b..fceb3ec 100644 --- a/src/command/index.ts +++ b/src/command/index.ts @@ -1,31 +1,25 @@ export * from "./command" export * from "./file" -export * from "./history" -export * from "./playback" -export * from "./convertNotesToChords" import * as File from "./file" -import * as History from "./history" -import * as Playback from "./playback" -import { convertNotesToChords } from "./convertNotesToChords" export const allCommands = [ - File.newProject, + File.commandNewProject, File.openFile, File.saveProject, File.saveProjectAs, File.openFileBrowser, File.downloadProjectBrowser, File.previewProjectBrowser, - File.downloadMidiBrowser, + //File.downloadMidiBrowser, - History.undo, - History.redo, + //History.undo, + //History.redo, - Playback.togglePlayback, + //Playback.togglePlayback, - convertNotesToChords, + //convertNotesToChords, ] \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..4c64dee --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,46 @@ +import * as Solid from "solid-js" +import * as SolidWeb from "solid-js/web" +import { styled } from "solid-styled-components" +import * as State from "./state.ts" +import * as Timeline from "./timeline" +import * as Ui from "./ui" +import { setupKeyboardShortcuts } from "./shortcuts.ts" + + +function App() +{ + State.get() + setupKeyboardShortcuts() + + return + + + +} + + +const AppRoot = styled.div` + display: grid; + grid-template: 1fr 18em / 1fr; + margin: auto; + width: 100%; + height: 100%; + min-height: 0; + padding: 2em 0; + box-sizing: border-box; + background-color: #101215; + color: #fff; + font-family: Verdana; + + --theme-buttonBkg: #444; + --theme-buttonBkgHover: #555; + --theme-buttonBkgPress: #333; + --theme-buttonBkgSelected: #06a; + --theme-buttonBkgSelected2: #08c; + --theme-buttonBorderRadius: 0.25em; +` + + +SolidWeb.render( + App, + document.getElementById("app")!) \ No newline at end of file diff --git a/src/playback/eventStream.ts b/src/playback/eventStream.ts new file mode 100644 index 0000000..eae46d8 --- /dev/null +++ b/src/playback/eventStream.ts @@ -0,0 +1,294 @@ +import * as Playback from "./index" +import * as Project from "../project" +import * as Theory from "../theory" +import Rational from "../utils/rational.ts" +import Range from "../utils/range.ts" +import * as MathUtils from "../utils/mathUtils.ts" + + +export interface NoteEvent +{ + trackId: Project.ID + + startMs: number + endMs: number + + midiPitchSeq: TimeVariableProperty[] + volumeDbSeq: TimeVariableProperty[] + velocitySeq: TimeVariableProperty[] +} + + +export interface TimeVariableProperty +{ + timeMs: number + value: number +} + + +export function queryNoteEvents( + project: Project.ImmutableRoot, + range: Range, + isStart: boolean) + : NoteEvent[] +{ + const noteEvents: NoteEvent[] = [] + + for (const track of project.tracks) + { + if (track.trackType !== "notes") + continue + + for (const note of iterNotesAtRange(project, track.id, range)) + { + if (range.overlapsPoint(note.range.start) || + (isStart && note.range.overlapsPoint(range.start))) + { + noteEvents.push(makeNoteEvent( + project, + track.id, + note.range, + note.midiPitch, + 0,//note.volumeDb, + 1))//note.velocity) + } + } + } + + const chordTrack = Project.getTrack(project, project.chordTrackId, "chords") + const chordList = project.lists.get(project.chordTrackId) + if (chordTrack && + chordList) + { + for (const chord of chordList.iterAtRange(range)) + { + playChord( + project, + chordTrack, + noteEvents, + range, + isStart, + chord as Project.Chord) + } + } + + return noteEvents +} + + +function makeNoteEvent( + project: Project.ImmutableRoot, + trackId: Project.ID, + noteRange: Range, + midiPitch: number, + volumeDb: number, + velocity: number) + : Playback.NoteEvent +{ + const startMs = + Project.getMillisecondsAt(project, noteRange.start) + + const endMs = + Project.getMillisecondsAt(project, noteRange.end) + + return { + trackId, + + startMs: startMs, + endMs: endMs, + + midiPitchSeq: [{ timeMs: startMs, value: midiPitch }], + volumeDbSeq: [{ timeMs: startMs, value: volumeDb }], + velocitySeq: [{ timeMs: startMs, value: velocity }], + } +} + + +function *iterNotesAtRange( + project: Project.ImmutableRoot, + trackId: Project.ID, + range: Range) + : Generator +{ + const noteList = project.lists.get(trackId) + if (!noteList) + return + + for (const elem of noteList.iterAtRange(range)) + { + if (elem.range.duration.isZero()) + continue + + if (!elem.range.overlapsRange(range)) + continue + + yield elem as Project.Note + } +} + + +function playChord( + project: Project.ImmutableRoot, + chordTrack: Project.TrackChords, + noteEvents: NoteEvent[], + range: Range, + isStart: boolean, + chord: Project.Chord) +{ + const addNoteEvent = (start: Rational, duration: Rational, midiPitch: number, volume: number) => { + const noteRange = Range.fromStartDuration(start, duration) + + if (range.overlapsPoint(noteRange.start) || + (isStart && noteRange.overlapsPoint(range.start))) + { + noteEvents.push(makeNoteEvent( + project, + chordTrack.id, + noteRange, + midiPitch, + MathUtils.linearGainToDb(volume), + 1)) + } + } + + const pitches = chord.chord.strummingPitches + + /*if (!useChordPatterns) + { + for (let j = 0; j < pitches.length; j++) + addNoteEvent(chord.range.start, chord.range.duration, pitches[j], chordVolumeMul) + + continue + }*/ + + const zero = new Rational(0) + + const measures = Project.iterMeasuresAtRange(project, chord.range) + for (const measure of measures) + { + const meterBeatLength = new Rational(1, measure.denominator) + const pattern = getChordStrummingPattern(measure.numerator, measure.denominator) + + let tick = measure.time1 + const endTime = chord.range.end.min(measure.time2) + + let mustPlayFirstBeat = false + let skipTick = zero + let patternIndex = 0 + + while (tick.compare(endTime) < 0) + { + const patternBeat = pattern[patternIndex] + const patternBeatKind = patternBeat[0] + let patternBeatLength = patternBeat[1].multiply(meterBeatLength) + patternIndex = (patternIndex + 1) % pattern.length + + let nextTick = tick.add(patternBeatLength) + if (nextTick.compare(endTime) > 0) + { + nextTick = endTime + patternBeatLength = nextTick.subtract(tick) + } + + // Handle beats after the first one. + if (tick.compare(chord.range.start) > 0 && + skipTick.compare(zero) <= 0) + { + if (mustPlayFirstBeat) + { + mustPlayFirstBeat = false + for (let j = 0; j < pitches.length; j++) + addNoteEvent(chord.range.start, tick.subtract(chord.range.start), pitches[j], 0.75) + } + + switch (patternBeatKind) + { + case 0: + { + for (let j = 0; j < pitches.length; j++) + addNoteEvent(tick, patternBeatLength, pitches[j], 0.75) + break + } + case 1: + { + for (let j = 1; j < pitches.length; j++) + addNoteEvent(tick, patternBeatLength, pitches[j], 0.5) + break + } + case 2: + { + addNoteEvent(tick, patternBeatLength, pitches[0], 0.5) + break + } + } + } + + skipTick = skipTick.subtract(patternBeatLength) + + if (tick.compare(chord.range.start) <= 0 && + nextTick.compare(chord.range.start) > 0) + { + mustPlayFirstBeat = true + + if (tick.compare(chord.range.start) !== 0 || + patternBeatKind !== 0) + skipTick = pattern[0][1].multiply(meterBeatLength) + } + + tick = nextTick + } + + if (mustPlayFirstBeat) + { + mustPlayFirstBeat = false + for (let j = 0; j < pitches.length; j++) + addNoteEvent(chord.range.start, tick.subtract(chord.range.start), pitches[j], 0.75) + } + } +} + + +function getChordStrummingPattern(numerator: number, denominator: number) +{ + // [[beat kind, duration], ...] + // Beat kinds: + // 0: Full chord + // 1: Full chord minus bass + // 2: Only bass + const one: [number, Rational][] = + [[0, new Rational(1)]] + + const two: [number, Rational][] = + [[0, new Rational(1)], [1, new Rational(1, 2)], [2, new Rational(1, 2)]] + + const three: [number, Rational][] = + [[0, new Rational(1)], [1, new Rational(1) ], [1, new Rational(1) ]] + + let pattern: [number, Rational][] = [] + let accumulated = 0 + while (accumulated < numerator) + { + if (numerator % 3 === 0 && + accumulated + 3 <= numerator) + { + pattern = pattern.concat(three) + accumulated += 3 + } + else if (accumulated + 3 === numerator) + { + pattern = pattern.concat(three) + accumulated += 3 + } + else if (accumulated + 2 <= numerator) + { + pattern = pattern.concat(two) + accumulated += 2 + } + else + { + pattern = pattern.concat(one) + accumulated += 1 + } + } + return pattern +} \ No newline at end of file diff --git a/src/playback/index.ts b/src/playback/index.ts index 81bccd9..70c787d 100644 --- a/src/playback/index.ts +++ b/src/playback/index.ts @@ -1,7 +1,4 @@ -export * from "./_global" -export * from "./synthManager" -export * from "./instrument" -export * from "./instrumentBasic" -export * from "./instrumentSflib" -export * from "./projectFeed" -export * from "./sflib" \ No newline at end of file +export * from "./manager.ts" +export * from "./eventStream.ts" +export * from "./instrument.ts" +export * from "./instrumentBasic.ts" \ No newline at end of file diff --git a/src/playback/instrument.ts b/src/playback/instrument.ts index bbbe196..8bf00ef 100644 --- a/src/playback/instrument.ts +++ b/src/playback/instrument.ts @@ -1,73 +1,57 @@ import * as Playback from "./index" import * as Project from "../project" - - -export interface NoteRequest -{ - trackId: Project.ID - noteId: Project.ID - - startMs: number - durationMs: number - - midiPitchSeq: TimeVariableProperty[] - volumeSeq: TimeVariableProperty[] - velocitySeq: TimeVariableProperty[] -} - - -export interface TimeVariableProperty -{ - timeMs: number - value: number -} +import * as MathUtils from "../utils/mathUtils.ts" +import Range from "../utils/range.ts" export class Instrument { - synth: Playback.SynthManager - outputNode: AudioNode + manager: Playback.Manager - constructor(synth: Playback.SynthManager, outputNode: AudioNode) - { - this.synth = synth - this.outputNode = outputNode - } + constructor(manager: Playback.Manager) + { + this.manager = manager + } - async prepare() - { + async prepare(noteEvent: Playback.NoteEvent) + { - } + } - async destroy() - { + async destroy() + { - } + } - isFinished() - { - return true - } + isFinished() + { + return true + } - playNote(request: Playback.NoteRequest) - { - + playNote( + noteEvent: Playback.NoteEvent, + audioCtxTimestampMs: number, + outputNode: AudioNode) + { + } - stopAll() - { + stopAll() + { - } + } - process(deltaTimeMs: number) - { - - } + process( + audioCtxTimestampMs: number, + deltaTimeMs: number) + { + + } } \ No newline at end of file diff --git a/src/playback/instrumentBasic.ts b/src/playback/instrumentBasic.ts index 6ee9149..0ca05e7 100644 --- a/src/playback/instrumentBasic.ts +++ b/src/playback/instrumentBasic.ts @@ -1,6 +1,7 @@ import * as Playback from "./index" import * as Project from "../project" -import * as MathUtils from "../util/mathUtils" +import * as MathUtils from "../utils/mathUtils.ts" +import Range from "../utils/range.ts" interface Sample @@ -12,8 +13,6 @@ interface Sample interface Note { - released: boolean - releasedTimerMs: number voices: Voice[] } @@ -23,238 +22,274 @@ interface Voice nodeSrc: AudioBufferSourceNode nodeEnvelope: GainNode nodeVolume: GainNode + endMs: number } const releaseDurationMs = 100 +const sampleDefs = [ + { frequency: 55.0, src: "audio/piano/a1.mp3" }, + { frequency: 110.0, src: "audio/piano/a2.mp3" }, + { frequency: 220.0, src: "audio/piano/a3.mp3" }, + { frequency: 440.0, src: "audio/piano/a4.mp3" }, + { frequency: 880.0, src: "audio/piano/a5.mp3" }, + { frequency: 1760.0, src: "audio/piano/a6.mp3" }, + //{ frequency: 3520.0, src: "audio/piano/a7.mp3" }, +] + + export class InstrumentBasic extends Playback.Instrument { - samples: Sample[] - notes: Map + samples: (Sample | undefined)[] + notes: Note[] - constructor(synth: Playback.SynthManager, outputNode: AudioNode) - { - super(synth, outputNode) + constructor(manager: Playback.Manager) + { + super(manager) + this.samples = [] - this.notes = new Map() + for (const _ of sampleDefs) + this.samples.push(undefined) + + this.notes = [] } - async prepare() + override async prepare(noteEvent: Playback.NoteEvent) { - if (this.samples.length > 0) - return - - const sampleDefs: [number, string][] = - [ - [ 55.0, "audio/piano/a1.mp3"], - [ 110.0, "audio/piano/a2.mp3"], - [ 220.0, "audio/piano/a3.mp3"], - [ 440.0, "audio/piano/a4.mp3"], - [ 880.0, "audio/piano/a5.mp3"], - [1760.0, "audio/piano/a6.mp3"], - //[3520.0, "audio/piano/a7.mp3"] - ] - - this.samples = [] - this.samples = await Promise.all(sampleDefs.map(s => this.load(s[0], s[1]))) + const neededSamples = this.getNeededSamples(noteEvent) + + for (const neededSample of neededSamples) + { + const index = neededSample.sampleIndex + + if (this.samples[index]) + continue + + this.samples[index] = { frequency: 0, buffer: null } + this.samples[index] = await this.load(sampleDefs[index]) + } } - async load(frequency: number, url: string): Promise - { - const sample: Sample = - { - buffer: null, - frequency + async load(sampleDef: typeof sampleDefs[0]): Promise + { + const sample: Sample = { + buffer: null, + frequency: sampleDef.frequency, } - return new Promise((resolve, reject) => - { - fetch(url) + const src = sampleDef.src + + console.log(`load audio <${ src }>...`) + + return new Promise((resolve, reject) => { + fetch(src) .then(res => res.arrayBuffer()) - .then(arrayBuf => - { - this.synth.audioCtx.decodeAudioData( + .then(arrayBuf => { + this.manager.audioCtx!.decodeAudioData( arrayBuf, - (audioBuf: AudioBuffer) => - { + (audioBuf: AudioBuffer) => { sample.buffer = audioBuf resolve(sample) }, - () => - { - console.log("Error loading audio <" + url + ">") + () => { + console.error(`error loading audio <${ src }>`) reject() }) }) }) - } - - - isFinished() - { - return this.notes.size == 0 - } - - - playNote(request: Playback.NoteRequest) - { - /*if (this.notes.has(request.noteId)) - return - - // Find the primary sample whose frequency is nearest to the desired frequency. - let sample1 = 0 - let sample1Dist = Math.abs(this.samples[0].frequency - desiredFreq) - for (let i = 1; i < this.samples.length; i++) - { - const dist = Math.abs(this.samples[i].frequency - desiredFreq) - if (dist < sample1Dist) - { - sample1 = i - sample1Dist = dist - } - } - - // Find the sample whose frequency is second nearest to the desired frequency, - // unless the primary sample is already exactly a match. - // Then, calculate a cross-fade between the two. - // TODO: Probably should use an equal-power (logarithmic) cross-fade, - // but this seems good enough for now. - let sample2 = null - - let volume1 = 1 - let volume2 = 0 - - if (this.samples[sample1].frequency < desiredFreq) - { - if (sample1 + 1 < this.samples.length) - { - sample2 = sample1 + 1 - - const t = - (desiredFreq - this.samples[sample1].frequency) / - (this.samples[sample2].frequency - this.samples[sample1].frequency) - - volume1 = 1 - t - volume2 = t - } - } - else if (this.samples[sample1].frequency > desiredFreq) - { - if (sample1 - 1 >= 0) - { - sample2 = sample1 - 1 - - const t = - (desiredFreq - this.samples[sample2].frequency) / - (this.samples[sample1].frequency - this.samples[sample2].frequency) - - volume1 = t - volume2 = 1 - t - } - } - - // Play the samples. - const voices = [ this.playVoice(this.samples[sample1], desiredFreq, desiredVolume * volume1) ] - if (sample2 != null) - voices.push(this.playVoice(this.samples[sample2], desiredFreq, desiredVolume * volume2)) - - const note: Note = - { - released: false, - releasedTimerMs: 0, - voices, - } - - this.notes.set(noteId, note)*/ } - playVoice(sample: Sample, midiPitch: number, volume: number): Voice - { - let sourceNode = this.synth.audioCtx.createBufferSource() - sourceNode.playbackRate.value = MathUtils.midiToHertz(midiPitch) / sample.frequency - sourceNode.buffer = sample.buffer - - let envelopeNode = this.synth.audioCtx.createGain() - envelopeNode.gain.value = 1 - - let volumeNode = this.synth.audioCtx.createGain() - volumeNode.gain.value = volume - - sourceNode.connect(envelopeNode) - envelopeNode.connect(volumeNode) - volumeNode.connect(this.outputNode) + override isFinished() + { + return this.notes.length === 0 + } + + + getNeededSamples(noteEvent: Playback.NoteEvent) + { + const desiredFreq = MathUtils.midiToHertz(noteEvent.midiPitchSeq[0].value) + + // Find the primary sample whose frequency is nearest to the desired frequency. + let sampleIndex1 = 0 + let sample1Dist = Math.abs(sampleDefs[0].frequency - desiredFreq) + for (let i = 1; i < sampleDefs.length; i++) + { + const dist = Math.abs(sampleDefs[i].frequency - desiredFreq) + if (dist < sample1Dist) + { + sampleIndex1 = i + sample1Dist = dist + } + } - sourceNode.start(0) - - return { - nodeSrc: sourceNode, - nodeEnvelope: envelopeNode, - nodeVolume: volumeNode, + // Find the sample whose frequency is second nearest to the desired frequency, + // unless the primary sample is already exactly a match. + // Then, calculate a cross-fade between the two. + // TODO: Probably should use an equal-power (logarithmic) cross-fade, + // but this seems good enough for now. + let sampleIndex2 = null + + let volume1 = 1 + let volume2 = 0 + + if (sampleDefs[sampleIndex1].frequency < desiredFreq) + { + if (sampleIndex1 + 1 < sampleDefs.length) + { + sampleIndex2 = sampleIndex1 + 1 + + const t = + (desiredFreq - sampleDefs[sampleIndex1].frequency) / + (sampleDefs[sampleIndex2].frequency - sampleDefs[sampleIndex1].frequency) + + volume1 = 1 - t + volume2 = t + } } - } - + else if (sampleDefs[sampleIndex1].frequency > desiredFreq) + { + if (sampleIndex1 - 1 >= 0) + { + sampleIndex2 = sampleIndex1 - 1 + + const t = + (desiredFreq - sampleDefs[sampleIndex2].frequency) / + (sampleDefs[sampleIndex1].frequency - sampleDefs[sampleIndex2].frequency) + + volume1 = t + volume2 = 1 - t + } + } + + const samples = [{ + sampleIndex: sampleIndex1, + volume: volume1, + }] + + if (sampleIndex2 !== null) + { + samples.push({ + sampleIndex: sampleIndex2, + volume: volume2, + }) + } + + return samples + } + - releaseNote(noteId: Project.ID) + override playNote( + noteEvent: Playback.NoteEvent, + audioCtxTimestamp: number, + outputNode: AudioNode) { - const note = this.notes.get(noteId) - if (!note) - return - - if (!note.released) - { - note.released = true - for (let i = 0; i < note.voices.length; i++) - { - note.voices[i].nodeEnvelope.gain.setValueAtTime( - 1, this.synth.audioCtx.currentTime) - - note.voices[i].nodeEnvelope.gain.linearRampToValueAtTime( - 0, this.synth.audioCtx.currentTime + releaseDurationMs / 1000) - } + const neededSamples = this.getNeededSamples(noteEvent) + + const voices: Voice[] = [] + for (const neededSample of neededSamples) + { + const sample = this.samples[neededSample.sampleIndex] + if (!sample) + continue + + voices.push(this.playVoice( + noteEvent, + audioCtxTimestamp, + outputNode, + sample, + noteEvent.midiPitchSeq[0].value, + neededSample.volume * + MathUtils.dbToLinearGain(noteEvent.volumeDbSeq[0].value) * + noteEvent.velocitySeq[0].value)) } + + this.notes.push({ + voices, + }) } - stopNote(noteId: Project.ID) - { - const note = this.notes.get(noteId) - if (!note) - return + playVoice( + noteEvent: Playback.NoteEvent, + audioCtxTimestamp: number, + outputNode: AudioNode, + sample: Sample, + midiPitch: number, + volume: number) + : Voice + { + const sourceNode = this.manager.audioCtx!.createBufferSource() + sourceNode.buffer = sample.buffer + sourceNode.playbackRate.value = + MathUtils.midiToHertz(midiPitch) / sample.frequency - note.released = true - note.releasedTimerMs = releaseDurationMs * 100 + const startMs = noteEvent.startMs + audioCtxTimestamp + const endMs = noteEvent.endMs + audioCtxTimestamp - for (let i = 0; i < note.voices.length; i++) - note.voices[i].nodeSrc.stop() + const envelopeNode = this.manager.audioCtx!.createGain() + envelopeNode.gain.value = 1 + envelopeNode.gain.setValueAtTime(1, endMs / 1000) + envelopeNode.gain.linearRampToValueAtTime(0, (endMs + 100) / 1000) - this.notes.delete(noteId) + const volumeNode = this.manager.audioCtx!.createGain() + volumeNode.gain.value = volume + + sourceNode.connect(envelopeNode) + envelopeNode.connect(volumeNode) + volumeNode.connect(outputNode) + + sourceNode.start(startMs / 1000) + + return { + nodeSrc: sourceNode, + nodeEnvelope: envelopeNode, + nodeVolume: volumeNode, + endMs: endMs + 100, + } + } + + + stopNote(note: Note) + { + for (const voice of note.voices) + { + voice.nodeEnvelope.gain.cancelScheduledValues(0) + voice.nodeEnvelope.disconnect() + voice.nodeVolume.gain.cancelScheduledValues(0) + voice.nodeVolume.disconnect() + voice.nodeSrc.stop() + voice.nodeSrc.disconnect() + voice.endMs = 0 + } } - stopAll() + override stopAll() { - for (const noteId of [...this.notes.keys()]) - this.stopNote(noteId) + for (const note of this.notes) + this.stopNote(note) + + this.notes = [] } - process(deltaTimeMs: number) - { - for (const [noteId, note] of [...this.notes.entries()]) + override process( + audioCtxTimestampMs: number, + deltaTimeMs: number) + { + for (const note of this.notes) { - if (note.released) - { - note.releasedTimerMs += deltaTimeMs - - if (note.releasedTimerMs > releaseDurationMs * 2) - this.stopNote(noteId) - } + if (note.voices.every(v => audioCtxTimestampMs >= v.endMs)) + this.stopNote(note) } - } + + this.notes = this.notes.filter(note => + note.voices.some(v => audioCtxTimestampMs < v.endMs)) + } } \ No newline at end of file diff --git a/src/playback/instrumentSflib.ts b/src/playback/instrumentSflib.ts deleted file mode 100644 index 758f64a..0000000 --- a/src/playback/instrumentSflib.ts +++ /dev/null @@ -1,256 +0,0 @@ -import * as Playback from "./index" -import * as Project from "../project" -import * as MathUtils from "../util/mathUtils" - - -const notesToDelete = new Set() - - -interface Note -{ - request: Playback.NoteRequest - zone: Playback.SflibInstrumentZone - timePassedMs: number - endTimeMs: number - nodeSrc: AudioBufferSourceNode - nodeEnvelope: GainNode - nodeVolume: GainNode -} - - -export class InstrumentSflib extends Playback.Instrument -{ - collectionId: string - instrumentId: string - sflibInstrument: Playback.SflibInstrument | null - notes: Note[] - - - constructor( - synth: Playback.SynthManager, - outputNode: AudioNode, - collectionId: string, - instrumentId: string) - { - super(synth, outputNode) - this.collectionId = collectionId - this.instrumentId = instrumentId - this.sflibInstrument = null - this.notes = [] - } - - - async prepare() - { - this.sflibInstrument = await Playback.sflibGetInstrument( - this.collectionId, this.instrumentId, this.synth.audioCtx) - - if (!this.sflibInstrument) - { - console.error("could not load sflib " + this.collectionId + "/" + this.instrumentId) - return - } - } - - - async destroy() - { - - } - - - isFinished() - { - return !this.sflibInstrument || this.notes.length == 0 - } - - - playNote(request: Playback.NoteRequest) - { - if (!this.sflibInstrument) - return - - //if (this.notes.has(request.noteId)) - // return - - const midiPitch = request.midiPitchSeq[0].value - - let zone = this.sflibInstrument.zones.find(z => - midiPitch >= z.midiPitchMin && - midiPitch <= z.midiPitchMax) - - if (!zone) - { - let nearestMidiPitchDistance = Infinity - - for (const z of this.sflibInstrument.zones) - { - const distance = Math.min( - Math.abs(z.midiPitchMin - midiPitch), - Math.abs(z.midiPitchMax - midiPitch)) - - if (distance < nearestMidiPitchDistance) - { - nearestMidiPitchDistance = distance - zone = z - } - } - } - - if (!zone) - return - - const freq = MathUtils.midiToHertz(midiPitch) - - const audioBuffer = this.sflibInstrument.audioBuffers[zone.sampleIndex] - - - const sourceNode = this.synth.audioCtx.createBufferSource() - sourceNode.playbackRate.value = freq / MathUtils.midiToHertz(zone.midiPitchBase) - sourceNode.buffer = audioBuffer - sourceNode.loop = (zone.loopMode == "loop") - sourceNode.loopStart = zone.loopStartIndex / audioBuffer.sampleRate - sourceNode.loopEnd = zone.loopEndIndex / audioBuffer.sampleRate - - - const time = 0.05 + Math.max(0, request.startMs / 1000) - const delayTime = time + zone.volEnvDelaySec - const attackTime = delayTime + (zone.volEnvAttackSec || 0.005) - const holdTime = attackTime + zone.volEnvHoldSec - const decayTime = holdTime + zone.volEnvDecaySec - const sustainLevel = zone.volEnvSustain || 0.0001 - const releaseTime = time + request.durationMs / 1000 - const endTime = releaseTime + Math.max(zone.volEnvReleaseSec, 0.05) - - /* - - Envelope parameters: - - | 100% level - | ______ - | /| |\ - | / | | \ sustainLevel - | / | | \____________ - | / | | | |\ - | / | | | | \ - | / | | | | \ - --+------------------------------------------- - ^ ^ ^ ^ ^ ^ ^ endTime - | | | | | + releaseTime - | | | | + decayTime - | | | + holdTime - | | + attackTime - | + delayTime - + time - - */ - - const releaseTimeCap = releaseTime - 0.001 - const cappedDelayTime = Math.min(delayTime, releaseTimeCap) - const cappedAttackTime = Math.min(attackTime, releaseTimeCap) - const cappedHoldTime = Math.min(holdTime, releaseTimeCap) - - const releaseF = (releaseTime - cappedHoldTime) / (decayTime - cappedHoldTime) - const releaseLevel = releaseF >= 1 ? sustainLevel : Math.pow(sustainLevel, releaseF) - - const envelopeNode = this.synth.audioCtx.createGain() - envelopeNode.gain.value = 0 - envelopeNode.gain.setValueAtTime(0.0001, cappedDelayTime) - envelopeNode.gain.exponentialRampToValueAtTime(1, cappedAttackTime) - envelopeNode.gain.setValueAtTime(1, cappedHoldTime) - - if (decayTime < releaseTime) - { - envelopeNode.gain.exponentialRampToValueAtTime(sustainLevel, decayTime) - envelopeNode.gain.setValueAtTime(sustainLevel, releaseTime) - envelopeNode.gain.exponentialRampToValueAtTime(0.0001, endTime) - } - else - { - envelopeNode.gain.exponentialRampToValueAtTime(releaseLevel, releaseTime) - envelopeNode.gain.setValueAtTime(releaseLevel, releaseTime) - envelopeNode.gain.exponentialRampToValueAtTime(0.0001, endTime) - } - - /*console.log("playNote", this.synth.audioCtx.currentTime, request) - console.log("envelope", - "del", (delayTime - time).toFixed(3), - "atk", (attackTime - time).toFixed(3), - "hld", (holdTime - time).toFixed(3), - "dec", (decayTime - time).toFixed(3), - "::", - sustainLevel.toFixed(3), - releaseLevel.toFixed(3), - "::", - "rel", (releaseTimeCap - time).toFixed(3), - "end", (endTime - time).toFixed(3))*/ - - const volumeNode = this.synth.audioCtx.createGain() - volumeNode.gain.value = MathUtils.dbToLinearGain(request.volumeSeq[0].value) - - - sourceNode.connect(envelopeNode) - envelopeNode.connect(volumeNode) - volumeNode.connect(this.outputNode) - - sourceNode.start(time) - - const note: Note = - { - request, - zone, - timePassedMs: -(time - this.synth.audioCtx.currentTime) * 1000, - endTimeMs: (endTime - time) * 1000, - nodeSrc: sourceNode, - nodeEnvelope: envelopeNode, - nodeVolume: volumeNode, - } - - this.notes.push(note) - } - - - stopNote(note: Note) - { - note.nodeSrc.stop() - note.nodeSrc.disconnect() - } - - - stopAll() - { - for (const note of this.notes) - this.stopNote(note) - - this.notes = [] - } - - - process(deltaTimeMs: number) - { - notesToDelete.clear() - - for (const note of this.notes) - { - note.timePassedMs += deltaTimeMs - - if (note.timePassedMs > note.endTimeMs) - { - this.stopNote(note) - notesToDelete.add(note) - } - else if (note.zone.loopMode != "loop") - { - const sample = this.sflibInstrument!.audioBuffers[note.zone.sampleIndex] - if (note.timePassedMs > sample.length / sample.sampleRate * 1000) - { - this.stopNote(note) - notesToDelete.add(note) - } - } - } - - this.notes = this.notes.filter(n => !notesToDelete.has(n)) - notesToDelete.clear() - } -} \ No newline at end of file diff --git a/src/playback/manager.ts b/src/playback/manager.ts new file mode 100644 index 0000000..d91f092 --- /dev/null +++ b/src/playback/manager.ts @@ -0,0 +1,464 @@ +import * as Global from "../state.ts" +import * as Playback from "./index" +import * as Project from "../project" +import * as Timeline from "../timeline" +import * as Theory from "../theory" +import * as MathUtils from "../utils/mathUtils.ts" +import Rational from "../utils/rational.ts" +import Range from "../utils/range.ts" + + +export class Manager +{ + audioCtx?: BaseAudioContext + nodeCompressor?: DynamicsCompressorNode + nodeGlobalVolume?: GainNode + nodeTrackVolumes: Map + + instruments: Playback.Instrument[] + + loading: number + playing: boolean + firstPlayingFrame: boolean + startTime: Rational + startTimeMs: number + nextStartTime: Rational + playTime: Rational + preloadTime: Rational + refreshTimeMs: number + + audioCtxTimestamp: number + requestAnimationFrameId: number + requestAnimationFrameTimestamp: number + requestAnimationFrameDate: Date + setIntervalId: number + + + constructor()//toBuffer?: boolean, bufferLen?: number, bufferSampleRate?: number) + { + this.instruments = [] + this.nodeTrackVolumes = new Map() + + this.loading = 0 + this.playing = false + this.firstPlayingFrame = false + this.startTime = new Rational(0) + this.startTimeMs = 0 + this.nextStartTime = new Rational(0) + this.playTime = new Rational(0) + this.preloadTime = new Rational(0) + this.refreshTimeMs = 0 + + this.requestAnimationFrameId = 0 + this.requestAnimationFrameTimestamp = 0 + this.requestAnimationFrameDate = new Date() + this.setIntervalId = 0 + + /*if (toBuffer) + { + this.audioCtx = new OfflineAudioContext(2, bufferLen!, bufferSampleRate!) + } + else + { + this.audioCtx = new AudioContext() + }*/ + } + + + async start() + { + this.loading += 1 + + try + { + if (this.audioCtx) + return + + this.audioCtx = new AudioContext() + + this.nodeGlobalVolume = this.audioCtx.createGain() + this.nodeGlobalVolume.gain.value = 0.1 + + this.nodeCompressor = this.audioCtx.createDynamicsCompressor() + this.nodeCompressor.threshold.value = -10 + this.nodeCompressor.knee.value = 12 + this.nodeCompressor.ratio.value = 12 + this.nodeCompressor.attack.value = 0 + this.nodeCompressor.release.value = 0.05 + + this.nodeGlobalVolume.connect(this.nodeCompressor) + this.nodeCompressor.connect(this.audioCtx.destination) + } + finally + { + this.loading -= 1 + } + } + + + getTrackOutput(trackId: Project.ID): GainNode + { + let node = this.nodeTrackVolumes.get(trackId) + if (node) + return node + + node = this.audioCtx!.createGain() + node.connect(this.nodeGlobalVolume!) + this.nodeTrackVolumes.set(trackId, node) + return node + } + + + async prepareRange( + project: Project.ImmutableRoot, + range: Range, + isStart: boolean) + { + this.loading += 1 + + try + { + const tasks: Promise[] = [] + + for (const noteEvent of Playback.queryNoteEvents(project, range, isStart)) + { + const instrument = await this.getInstrument(noteEvent) + tasks.push(instrument.prepare(noteEvent)) + } + + await Promise.all(tasks) + } + finally + { + this.loading -= 1 + } + } + + + async getInstrument(noteEvent: Playback.NoteEvent) + { + if (this.instruments.length > 0) + return this.instruments[0] + + const instrument = new Playback.InstrumentBasic(this) + + this.instruments.push(instrument) + return instrument + } + + + async destroy() + { + if (this.audioCtx instanceof AudioContext) + await this.audioCtx.close() + } + + + isFinished() + { + for (const instrument of this.instruments) + { + if (!instrument.isFinished()) + return false + } + + return true + } + + + play() + { + + } + + + stopAll() + { + for (const instrument of this.instruments) + instrument.stopAll() + } + + + process( + audioCtxTimestampMs: number, + deltaTimeMs: number) + { + for (const instrument of this.instruments) + instrument.process(audioCtxTimestampMs, deltaTimeMs) + } + + + async playNote( + noteEvent: Playback.NoteEvent, + audioCtxOffsetMs: number) + { + const instrument = await this.getInstrument(noteEvent) + if (!instrument) + return + + await instrument.prepare(noteEvent) + + const output = this.getTrackOutput(noteEvent.trackId) + instrument.playNote(noteEvent, audioCtxOffsetMs, output) + } + + + async playNotePreview( + project: Project.ImmutableRoot, + trackId: Project.ID, + midiPitch: number) + { + await this.start() + this.stopAll() + + const audioCtxOffsetMs = 15 + this.audioCtx!.currentTime * 1000 + this.updateTracks(project, audioCtxOffsetMs) + + const noteEvent: Playback.NoteEvent = { + trackId, + + startMs: 0, + endMs: 500, + + midiPitchSeq: [{ timeMs: 0, value: midiPitch }], + volumeDbSeq: [{ timeMs: 0, value: 0 }], + velocitySeq: [{ timeMs: 0, value: 1 }], + } + + await this.playNote(noteEvent, audioCtxOffsetMs) + } + + + async playChordPreview( + project: Project.ImmutableRoot, + trackId: Project.ID, + chord: Theory.Chord) + { + await this.start() + this.stopAll() + + const audioCtxOffsetMs = 15 + this.audioCtx!.currentTime * 1000 + this.updateTracks(project, audioCtxOffsetMs) + + for (const midiPitch of chord.strummingPitches) + { + const noteEvent: Playback.NoteEvent = { + trackId, + + startMs: 0, + endMs: 750, + + midiPitchSeq: [{ timeMs: 0, value: midiPitch }], + volumeDbSeq: [{ timeMs: 0, value: 0 }], + velocitySeq: [{ timeMs: 0, value: 1 }], + } + + await this.playNote(noteEvent, audioCtxOffsetMs) + } + } + + + prepareNextRange(isStart: boolean) + { + const preloadTimeNext = this.preloadTime.add(new Rational(4, 4)) + + const range = new Range( + this.preloadTime, + preloadTimeNext) + + this.prepareRange( + Global.getStatic().project.root, + range, + isStart) + + this.preloadTime = preloadTimeNext + } + + + updateTracks( + project: Project.ImmutableRoot, + audioCtxOffsetMs: number) + { + const hasSoloTrack = project.tracks.some(tr => + tr.trackType === "notes" || tr.trackType === "chords" ? tr.solo : false) + + for (const track of project.tracks) + { + const trackWithAttrbs = + track.trackType === "notes" || track.trackType === "chords" ? + track : + undefined + + const trackOutput = this.getTrackOutput(track.id) + const trackVolume = + !trackWithAttrbs?.solo && + (hasSoloTrack || trackWithAttrbs?.mute) ? 0 : + MathUtils.dbToLinearGain(0) + + trackOutput.gain.linearRampToValueAtTime(trackVolume, (audioCtxOffsetMs + 50) / 1000) + } + } + + + processFrame(canRedrawScreen: boolean) + { + if (this.loading > 0) + return + + const audioCtxTimestampPrev = this.audioCtxTimestamp + this.audioCtxTimestamp = this.audioCtx?.currentTime ?? 0 + const deltaTimeMs = (this.audioCtxTimestamp - audioCtxTimestampPrev) * 1000 + + if (deltaTimeMs > 100) + return + + this.prepareNextRange(false) + + const audioCtxOffsetMs = 15 + this.audioCtxTimestamp * 1000 + + const startTimeNext = this.startTimeMs + deltaTimeMs + + const project = Global.getStatic().project.root + + const playTimeNext = + Project.getTimeAtMilliseconds(project, startTimeNext) + + this.process(audioCtxOffsetMs, deltaTimeMs) + + const range = new Range( + this.playTime, + playTimeNext, + true, + false) + + const noteEvents = Playback.queryNoteEvents( + project, + range, + this.firstPlayingFrame) + + this.updateTracks(project, audioCtxOffsetMs) + + this.firstPlayingFrame = false + + for (const noteEvent of noteEvents) + { + noteEvent.startMs = Math.max(0, noteEvent.startMs - this.startTimeMs) + noteEvent.endMs = Math.max(0, noteEvent.endMs - this.startTimeMs) + this.playNote(noteEvent, audioCtxOffsetMs) + } + + //if (noteEvents.length > 0) console.log(noteEvents) + + this.playTime = playTimeNext + this.startTimeMs = startTimeNext + + this.refreshTimeMs += deltaTimeMs + if (canRedrawScreen) + { + this.refreshTimeMs = 0 + if (Timeline.scrollPlayTimeIntoView(Global.getStatic().timeline, this.playTime)) + window.dispatchEvent(new CustomEvent(Timeline.eventTimelineRelayout)) + else + window.dispatchEvent(new CustomEvent(Timeline.eventTimelineRedraw)) + } + + if (this.playTime.compare(project.range.end) > 0 && + this.isFinished()) + { + this.setPlaying(false) + } + } + + + processAnimationFrame(timestamp: number) + { + const prevTimestamp = this.requestAnimationFrameTimestamp + this.requestAnimationFrameTimestamp = timestamp + + this.requestAnimationFrameDate = new Date() + + //const deltaTimeMs = (prevTimestamp < 0 ? 0 : timestamp - prevTimestamp) + + this.processFrame(true) + + if (this.playing) + { + this.requestAnimationFrameId = + requestAnimationFrame(timestamp => this.processAnimationFrame(timestamp)) + } + } + + + processInterval(deltaTimeMs: number) + { + // Take over from requestAnimationFrame and + // process playback on the setInterval callback only if + // requestAnimationFrame was blocked (by e.g. being in the background) + + const msSinceLastRequestAnimationFrame = + (new Date().getTime()) - + this.requestAnimationFrameDate.getTime() + + if (msSinceLastRequestAnimationFrame > 250 && + deltaTimeMs > 0 && deltaTimeMs < 250) + { + this.processFrame(false) + } + } + + + setPlaying( + playing: boolean) + { + if (this.requestAnimationFrameId !== 0) + { + this.stopAll() + + cancelAnimationFrame(this.requestAnimationFrameId) + this.requestAnimationFrameId = 0 + + clearInterval(this.setIntervalId) + this.setIntervalId = 0 + } + + const project = Global.getStatic().project.root + + this.playing = playing + this.firstPlayingFrame = true + this.startTime = this.nextStartTime + this.startTimeMs = Project.getMillisecondsAt(project, this.startTime) + this.playTime = this.nextStartTime + this.preloadTime = this.nextStartTime + this.refreshTimeMs = 0 + this.audioCtxTimestamp = this.audioCtx?.currentTime ?? 0 + + if (playing) + { + this.start() + this.prepareNextRange(true) + + this.requestAnimationFrameId = + requestAnimationFrame(timestamp => this.processAnimationFrame(timestamp)) + + this.requestAnimationFrameTimestamp = 0 + this.requestAnimationFrameDate = new Date() + + this.setIntervalId = + +setInterval(() => this.processInterval(1000 / 60), 1000 / 60) + } + + window.dispatchEvent(new CustomEvent(Timeline.eventTimelineRedraw)) + } + + + setStartTime(startTime: Rational) + { + this.nextStartTime = startTime + } + + + togglePlaying() + { + this.setPlaying(!this.playing) + } +} \ No newline at end of file diff --git a/src/prefs.ts b/src/prefs.ts new file mode 100644 index 0000000..7001221 --- /dev/null +++ b/src/prefs.ts @@ -0,0 +1,121 @@ +export interface Prefs +{ + timeline: + { + bkgColor: string + bkgAlternateMeasureColor: string + bkgInactiveOverlayColor: string + trackVBorderColor: string + trackHBorderColor: string + + outOfBoundsColor1: string + outOfBoundsColor2: string + + selectionCursorColor: string + selectionBkgColor: string + playbackCursorColor: string + trackSeparatorColor: string + + measureColor: string + submeasureColor: string + halfSubmeasureColor: string + + measureLabelColor: string + octaveLabelColor: string + + tempoChangeColor: string + keyChangeColor: string + meterChangeColor: string + noteVelocityMarkerColor: string + noteVelocityMarkerInactiveColor: string + + fontWeightChord: string, + fontNameChord: string, + + fontWeightMarker: string, + fontNameMarker: string, + + keyPan: string + keyPencil: string + keySelectMultiple: string + keyForceCursorSelect: string + keyClone: string + keyDisplaceCursor2: string + keyDisplaceFast: string + keyDisplaceChromatically: string + keyDisplaceStretch: string + + mouseDoubleClickThresholdMs: number + + mouseDragXLockedDistance: number + mouseDragYLockedDistance: number + + mouseEdgeScrollThreshold: number + mouseEdgeScrollSpeed: number + + hoverOuterStretchWidth: number + hoverInnerStretchWidth: number + } +} + + +export function makeNew(): Prefs +{ + return { + timeline: { + bkgColor: "#202225", + bkgAlternateMeasureColor: "#1c1e21", + bkgInactiveOverlayColor: "#0008", + trackVBorderColor: "#888", + trackHBorderColor: "#888", + + outOfBoundsColor1: "#161719", + outOfBoundsColor2: "#141416", + + selectionCursorColor: "#0af", + selectionBkgColor: "#8cf4", + playbackCursorColor: "#0c4", + trackSeparatorColor: "#aaa", + + measureColor: "#040404", + submeasureColor: "#181818", + halfSubmeasureColor: "#181818", + + measureLabelColor: "#aaa", + octaveLabelColor: "#aaa", + + tempoChangeColor: "#d98", + keyChangeColor: "#f0c", + meterChangeColor: "#0cf", + noteVelocityMarkerColor: "#0c4", + noteVelocityMarkerInactiveColor: "#063", + + fontWeightChord: "", + fontNameChord: "Verdana", + + fontWeightMarker: "bold", + fontNameMarker: "Calibri", + + keyPan: "q", + keyPencil: "a", + keySelectMultiple: "control", + keyForceCursorSelect: "shift", + keyClone: "alt", + keyDisplaceCursor2: "shift", + keyDisplaceFast: "control", + keyDisplaceChromatically: "shift", + keyDisplaceStretch: "shift", + + mouseDoubleClickThresholdMs: 300, + + mouseDragXLockedDistance: 10, + mouseDragYLockedDistance: 10, + + mouseEdgeScrollThreshold: 10, + mouseEdgeScrollSpeed: 1, + + hoverOuterStretchWidth: 16, + hoverInnerStretchWidth: 12, + } + } +} \ No newline at end of file diff --git a/src/project/elem.ts b/src/project/elem.ts index 0d46ffb..4b4e6e5 100644 --- a/src/project/elem.ts +++ b/src/project/elem.ts @@ -1,12 +1,11 @@ -import Range from "../util/range" import * as Theory from "../theory" -import * as Playback from "../playback" -import Rational from "../util/rational" -import * as Misc from "../util/misc" +import Range from "../utils/range.ts" +import Rational from "../utils/rational.ts" export type ID = number + export const MinVolumeDb = -30 export const MaxVolumeDb = 0 export const DefaultVolumeDb = 0 @@ -21,67 +20,65 @@ export interface ElementBase } -export interface InstrumentBasic +export interface TrackBase extends ElementBase { - type: "basic" + type: "track" + name: string } -export interface InstrumentSflib +export interface TrackTempoChanges extends TrackBase { - type: "sflib" - collectionId: string - instrumentId: string + trackType: "tempoChanges" } -export type Instrument = - InstrumentBasic | - InstrumentSflib - - -export interface TrackBase extends ElementBase +export interface TrackKeyChanges extends TrackBase { - type: "track" - name: string - mute: boolean - solo: boolean + trackType: "keyChanges" } -export interface TrackNotes extends TrackBase +export interface TrackMeterChanges extends TrackBase { - trackType: "notes" - instrument: Instrument - volumeDb: number + trackType: "meterChanges" } -export interface TrackChords extends TrackBase +export interface TrackAttributes { - trackType: "chords" - instrument: Instrument - volumeDb: number + editable: boolean + visible: boolean + mute: boolean + solo: boolean } -export interface TrackKeyChanges extends TrackBase +export interface TrackChords extends TrackBase, TrackAttributes { - trackType: "keyChanges" + trackType: "chords" } -export interface TrackMeterChanges extends TrackBase +export interface TrackNotes extends TrackBase, TrackAttributes { - trackType: "meterChanges" + trackType: "notes" } export type Track = - TrackNotes | - TrackChords | + TrackTempoChanges | TrackKeyChanges | - TrackMeterChanges + TrackMeterChanges | + TrackChords | + TrackNotes + + +export interface TempoChange extends ElementBase +{ + type: "tempoChange" + bpm: number +} export interface KeyChange extends ElementBase @@ -98,18 +95,10 @@ export interface MeterChange extends ElementBase } -export interface NoteBlock extends ElementBase -{ - type: "noteBlock" -} - - export interface Note extends ElementBase { type: "note" midiPitch: number - volumeDb: number - velocity: number } @@ -122,9 +111,9 @@ export interface Chord extends ElementBase export type Element = Track | + TempoChange | KeyChange | MeterChange | - NoteBlock | Note | Chord @@ -144,11 +133,11 @@ export function makeTrackNotes(): TrackNotes id: -1, parentId: 0, range: Range.dummy(), - name: "", - volumeDb: DefaultVolumeDb, + name: "Notes", + editable: false, + visible: true, mute: false, solo: false, - instrument: makeInstrument(), } } @@ -162,97 +151,16 @@ export function makeTrackChords(): TrackChords parentId: 0, range: Range.dummy(), name: "Chords", - volumeDb: DefaultChordVolumeDb, + editable: false, + visible: true, mute: false, solo: false, - instrument: makeInstrument(), - } -} - - -export function makeInstrument(): Instrument -{ - return makeInstrumentOfKind("sflib") -} - - -export function makeInstrumentOfKind(kind: string): Instrument -{ - switch (kind) - { - case "basic": - default: - { - return { - type: "basic", - } - } - case "sflib": - { - return { - type: "sflib", - collectionId: "gm", - instrumentId: "piano_1", - } - } - } -} - - -export function instrumentName(instrument: Instrument): string -{ - switch (instrument.type) - { - case "basic": return "Basic" - case "sflib": - { - const sflibMeta = Playback.getSflibMeta() - if (!sflibMeta) - return instrument.collectionId + "/" + instrument.instrumentId - - const coll = sflibMeta.collectionsById.get(instrument.collectionId)! - const instr = coll.instrumentsById.get(instrument.instrumentId)! - const emoji = Misc.getMidiPresetEmoji(instr.midiBank, instr.midiPreset) - - return emoji + " " + coll.id + "/" + instr.name - } - - default: return "???" - } -} - - -export function instrumentEmoji(instrument: Instrument): string -{ - switch (instrument.type) - { - case "basic": return Misc.getMidiPresetEmoji(0, 0) - case "sflib": - { - const sflibMeta = Playback.getSflibMeta() - if (!sflibMeta) - return Misc.getMidiPresetEmoji(0, 0) - - const coll = sflibMeta.collectionsById.get(instrument.collectionId)! - const instr = coll.instrumentsById.get(instrument.instrumentId)! - return Misc.getMidiPresetEmoji(instr.midiBank, instr.midiPreset) - } - - default: return Misc.getMidiPresetEmoji(0, 0) } } export function trackDisplayName(track: Track): string { - if (track.trackType == "notes" || track.trackType == "chords") - { - if (track.name) - return instrumentEmoji(track.instrument) + " " + track.name - - return instrumentName(track.instrument) - } - if (track.name) return track.name @@ -260,18 +168,16 @@ export function trackDisplayName(track: Track): string } -export function trackHasInstrument(track: Track): boolean +export function makeTrackTempoChanges(): TrackTempoChanges { - return track.trackType == "notes" || track.trackType == "chords" -} - - -export function trackGetInstrument(track: Track): Instrument | null -{ - if (track.trackType == "notes" || track.trackType == "chords") - return track.instrument - else - return null + return { + type: "track", + trackType: "tempoChanges", + id: -1, + parentId: 0, + range: Range.dummy(), + name: "Tempo Changes", + } } @@ -284,8 +190,6 @@ export function makeTrackKeyChanges(): TrackKeyChanges parentId: 0, range: Range.dummy(), name: "Key Changes", - mute: false, - solo: false, } } @@ -299,20 +203,18 @@ export function makeTrackMeterChanges(): TrackMeterChanges parentId: 0, range: Range.dummy(), name: "Meter Changes", - mute: false, - solo: false, } } -export function makeMeterChange(parentId: ID, time: Rational, meter: Theory.Meter): MeterChange +export function makeTempoChange(parentId: ID, time: Rational, bpm: number): TempoChange { return { - type: "meterChange", + type: "tempoChange", id: -1, parentId, range: Range.fromPoint(time), - meter, + bpm, } } @@ -329,13 +231,14 @@ export function makeKeyChange(parentId: ID, time: Rational, key: Theory.Key): Ke } -export function makeNoteBlock(parentId: ID, range: Range): NoteBlock +export function makeMeterChange(parentId: ID, time: Rational, meter: Theory.Meter): MeterChange { return { - type: "noteBlock", + type: "meterChange", id: -1, parentId, - range, + range: Range.fromPoint(time), + meter, } } @@ -343,9 +246,7 @@ export function makeNoteBlock(parentId: ID, range: Range): NoteBlock export function makeNote( parentId: ID, range: Range, - midiPitch: number, - volumeDb: number, - velocity: number) + midiPitch: number) : Note { return { @@ -354,8 +255,6 @@ export function makeNote( parentId, range, midiPitch, - volumeDb, - velocity, } } diff --git a/src/project/index.ts b/src/project/index.ts index 11d0d8f..7c1a692 100644 --- a/src/project/index.ts +++ b/src/project/index.ts @@ -1,8 +1,9 @@ -export * from "./_global" +//export * from "./_global" export * from "./root" -export * from "./measures" export * from "./elem" -export * from "./midiImport" -export * from "./midiExport" +export * from "./measures" +export * from "./tempo" +//export * from "./midiImport" +//export * from "./midiExport" export * from "./jsonExport" export * from "./jsonImport" \ No newline at end of file diff --git a/src/project/jsonExport.ts b/src/project/jsonExport.ts index 612d2b4..39098f2 100644 --- a/src/project/jsonExport.ts +++ b/src/project/jsonExport.ts @@ -1,16 +1,15 @@ -import * as Project from "./index" +import * as Project from "./index.ts" import * as Theory from "../theory" -import Rational from "../util/rational" -import Range from "../util/range" -import * as MathUtils from "../util/mathUtils" +import Rational from "../utils/rational.ts" +import Range from "../utils/range.ts" +import * as MathUtils from "../utils/mathUtils.ts" -export function jsonExport(project: Project.Root): string +export function jsonExport(project: Project.ImmutableRoot): string { let json = `` json += `{\n` - json += `"version": 1,\n` - json += `"baseBpm": ${ project.baseBpm },\n` + json += `"version": 2,\n` json += `"tracks": [\n` let firstTrack = true @@ -26,13 +25,14 @@ export function jsonExport(project: Project.Root): string json += `\t"name": ${ JSON.stringify(track.name) },\n` json += `\t"type": ${ JSON.stringify(track.type) },\n` json += `\t"trackType": ${ JSON.stringify(track.trackType) },\n` - json += `\t"mute": ${ JSON.stringify(track.mute) },\n` - json += `\t"solo": ${ JSON.stringify(track.solo) },\n` - if (track.trackType == "notes" || track.trackType == "chords") + if (track.trackType === "notes" || + track.trackType === "chords") { - json += `\t"volumeDb": ${ JSON.stringify(track.volumeDb) },\n` - json += `\t"instrument": ${ JSON.stringify(track.instrument) },\n` + json += `\t"mute": ${ JSON.stringify(track.mute) },\n` + json += `\t"solo": ${ JSON.stringify(track.solo) },\n` + json += `\t"volumeDb": 0,\n`// ${ JSON.stringify(track.volumeDb) },\n` + //json += `\t"instrument": ${ JSON.stringify(track.instrument) },\n` } json += `\t"elems": [\n` @@ -60,7 +60,7 @@ export function jsonExport(project: Project.Root): string function exportElem( indent: number, - project: Project.Root, + project: Project.ImmutableRoot, elem: Project.Element, timeOffset: Rational) : string @@ -77,41 +77,47 @@ function exportElem( switch (elem.type) { - case "note": + case "tempoChange": { - json += `, ${ JSON.stringify([ - elem.midiPitch, - MathUtils.quantize(elem.volumeDb, 1000), - MathUtils.quantize(elem.velocity, 1000), - ]) }` + json += `, ${ JSON.stringify({ + bpm: elem.bpm, + }) }` break } - case "chord": + case "keyChange": { - json += `, ${ JSON.stringify([ - elem.chord.rootChroma, - Theory.Chord.kinds[elem.chord.kind].id, - elem.chord.inversion, - ]) }` + json += `, ${ JSON.stringify(elem.key.str) }` break } - case "keyChange": + case "meterChange": { - json += `, ${ JSON.stringify(elem.key.str) }` + json += `, ${ JSON.stringify({ + meter: elem.meter.toJson(), + }) }` break } - case "meterChange": + case "note": + { + json += `, ${ JSON.stringify([ + elem.midiPitch, + 0,//MathUtils.quantize(elem.volumeDb, 1000), + 1,//MathUtils.quantize(elem.velocity, 1000), + ]) }` + break + } + + case "chord": { - json += `, ${ JSON.stringify(elem.meter.str) }` + json += `, ${ JSON.stringify(elem.chord.toJson()) }` break } default: { - json += `, []` + json += `` break } } diff --git a/src/project/jsonImport.ts b/src/project/jsonImport.ts index 878c062..537b05b 100644 --- a/src/project/jsonImport.ts +++ b/src/project/jsonImport.ts @@ -1,31 +1,24 @@ -import * as Project from "./index" +import * as Project from "./index.ts" import * as Theory from "../theory" -import Rational from "../util/rational" -import Range from "../util/range" -import * as MathUtils from "../util/mathUtils" +import Rational from "../utils/rational.ts" +import Range from "../utils/range.ts" +import * as MathUtils from "../utils/mathUtils.ts" -export function jsonImport(json: any): Project.Root +export function jsonImport(json: any): Project.ImmutableRoot { console.log("jsonImport", json) let project = Project.makeEmpty() - project.baseBpm = json.baseBpm + const version = json.version as number for (const jsonTrack of json.tracks) { let track: Project.Track switch (jsonTrack.trackType as Project.Track["trackType"]) { - case "notes": - track = Project.makeTrackNotes() - track.instrument = jsonTrack.instrument - track.volumeDb = jsonTrack.volumeDb - break - case "chords": - track = Project.makeTrackChords() - track.instrument = jsonTrack.instrument - track.volumeDb = jsonTrack.volumeDb + case "tempoChanges": + track = Project.makeTrackTempoChanges() break case "keyChanges": track = Project.makeTrackKeyChanges() @@ -33,37 +26,63 @@ export function jsonImport(json: any): Project.Root case "meterChanges": track = Project.makeTrackMeterChanges() break + case "notes": + track = Project.makeTrackNotes() + //track.instrument = jsonTrack.instrument + //track.volumeDb = jsonTrack.volumeDb + break + case "chords": + track = Project.makeTrackChords() + //track.instrument = jsonTrack.instrument + //track.volumeDb = jsonTrack.volumeDb + break } track.name = jsonTrack.name - track.mute = jsonTrack.mute - track.solo = jsonTrack.solo + + if (track.trackType === "notes" || + track.trackType === "chords") + { + track.mute = jsonTrack.mute + track.solo = jsonTrack.solo + } const trackId = project.nextId project = Project.upsertTrack(project, track) - if (track.trackType == "keyChanges") + if (track.trackType === "tempoChanges") + project.tempoChangeTrackId = trackId + else if (track.trackType === "keyChanges") project.keyChangeTrackId = trackId - else if (track.trackType == "meterChanges") + else if (track.trackType === "meterChanges") project.meterChangeTrackId = trackId - else if (track.trackType == "chords") + else if (track.trackType === "chords") project.chordTrackId = trackId for (const jsonElem of jsonTrack.elems) { - const projectRef = { ref: project } - importElem(projectRef, trackId, jsonElem, new Rational(0)) - project = projectRef.ref + const projectMutable = { root: project } + importElem(version, projectMutable, trackId, jsonElem, new Rational(0)) + project = projectMutable.root } } + if (json.version < 2) + { + project.tempoChangeTrackId = project.nextId + project = Project.upsertTrack(project, Project.makeTrackTempoChanges()) + const tempoCh = Project.makeTempoChange(project.tempoChangeTrackId, new Rational(0), json.baseBpm) + project = Project.upsertElement(project, tempoCh) + } + console.log("project", project) return Project.withRefreshedRange(project) } function importElem( - projectRef: { ref: Project.Root }, + version: number, + project: Project.Mutable, parentId: Project.ID, jsonElem: any, timeOffset: Rational) @@ -73,29 +92,14 @@ function importElem( .quantize(Project.MAX_RATIONAL_DENOMINATOR) let elem: Project.Element - switch (jsonElem[0] as Project.Element["type"]) + switch (jsonElem[0] as Project.Element["type"] | "noteBlock") { - case "note": + case "tempoChange": { - elem = Project.makeNote( + elem = Project.makeTempoChange( parentId, - range, - jsonElem[3][0], - jsonElem[3][1], - jsonElem[3][2]) - break - } - - case "chord": - { - elem = Project.makeChord( - parentId, - range, - new Theory.Chord( - jsonElem[3][0], - Theory.Chord.kindFromId(jsonElem[3][1]), - jsonElem[3][2], - [])) + range.start, + jsonElem[3].bpm) break } @@ -113,30 +117,66 @@ function importElem( elem = Project.makeMeterChange( parentId, range.start, - Theory.Meter.parse(jsonElem[3])) + version < 2 ? + Theory.Meter.parse(jsonElem[3]) : + Theory.Meter.fromJson(jsonElem[3])) break } - case "noteBlock": + case "note": { - elem = Project.makeNoteBlock( + elem = Project.makeNote( parentId, - range) + range, + jsonElem[3][0]) + //jsonElem[3][1], + //jsonElem[3][2]) + break + } + + case "noteBlock": + { + for (const jsonInner of jsonElem[4]) + { + importElem(version, project, parentId, jsonInner, new Rational(0)) + } + return + } + + case "chord": + { + if (version < 2) + { + elem = Project.makeChord( + parentId, + range, + Theory.Chord.fromLegacyKind( + jsonElem[3][0], + jsonElem[3][2], + jsonElem[3][1])) + } + else + { + elem = Project.makeChord( + parentId, + range, + Theory.Chord.fromJson(jsonElem[3])) + } break } - case "track": - throw "invalid json" + default: + throw "invalid project json" } - const id = projectRef.ref.nextId - projectRef.ref = Project.upsertElement(projectRef.ref, elem) + const id = project.root.nextId + project.root = Project.upsertElement(project.root, elem) if (jsonElem[4]) { for (const jsonInner of jsonElem[4]) { - importElem(projectRef, id, jsonInner, range.start) + importElem(version, project, id, jsonInner, range.start) } } } \ No newline at end of file diff --git a/src/project/measures.ts b/src/project/measures.ts index 2767091..c4b93fa 100644 --- a/src/project/measures.ts +++ b/src/project/measures.ts @@ -1,10 +1,10 @@ -import * as Project from "./index" -import * as Theory from "../theory" import Immutable from "immutable" -import BinarySearch from "../util/binarySearch" -import Rational from "../util/rational" -import ListOfRanges from "../util/listOfRanges" -import Range from "../util/range" +import * as Project from "./index.ts" +import * as Theory from "../theory" +import BinarySearch from "../utils/binarySearch.ts" +import Rational from "../utils/rational.ts" +import ListOfRanges from "../utils/listOfRanges.ts" +import Range from "../utils/range.ts" export interface Measure @@ -14,6 +14,8 @@ export interface Measure time1: Rational time2: Rational meterCh: Project.MeterChange + numerator: number + denominator: number } @@ -23,10 +25,11 @@ let firstMeterCh: Project.MeterChange | null = null let lastMeterCh: Project.MeterChange | null = null -export function ensureMeasureCacheRefreshed(project: Project.Root) +export function ensureMeasureCacheRefreshed(project: Project.ImmutableRoot) { - const meterChangeTrackId = Project.meterChangeTrackId(project) - const meterChangeList = Project.global.project.lists.get(meterChangeTrackId)! + const meterChangeList = project.lists.get(project.meterChangeTrackId) + if (!meterChangeList) + return if (meterChangeList === measureCacheKey) return @@ -51,20 +54,23 @@ export function ensureMeasureCacheRefreshed(project: Project.Root) continue const meterCh1 = elem1 as Project.MeterChange - const meterCh2 = elem2 as (Project.MeterChange | null) + const meterCh2 = elem2 as Project.MeterChange let numLocal = 0 - for (const [measureN, measureD, time1, time2] of meterCh1.meter.iterMeasuresPairwise(meterCh1.range.start)) + for (const [numerator, denominator, time1, time2] of meterCh1.meter.iterMeasuresPairwise(meterCh1.range.start)) { if (meterCh2 && time1.compare(meterCh2.range.start) >= 0) break measureCache.push({ - num, numLocal, + num, + numLocal, time1, time2: time2.min(meterCh2 ? meterCh2.range.start : time2), meterCh: meterCh1, + numerator, + denominator, }) num++ @@ -75,7 +81,7 @@ export function ensureMeasureCacheRefreshed(project: Project.Root) export function *iterMeasuresAtRange( - project: Project.Root, + project: Project.ImmutableRoot, range: Range) : Generator { @@ -88,15 +94,21 @@ export function *iterMeasuresAtRange( let time = firstMeterCh.range.start let num = 0 + const fullCycleDuration = firstMeterCh.meter.fullCycleDuration + while (time.compare(range.start) > 0) { - time = time.subtract(firstMeterCh.meter.fullCycleDuration) + time = time.subtract(fullCycleDuration) num-- } + let ratioIndex = 0 + while (time.compare(firstMeterCh.range.start) < 0) { - const time2 = time.add(firstMeterCh.meter.fullCycleDuration) + const ratio = firstMeterCh.meter.ratios[ratioIndex] + const time2 = time.add(new Rational(ratio.numerator, ratio.denominator)) + ratioIndex = (ratioIndex + 1) % firstMeterCh.meter.ratios.length if (time2.compare(range.start) >= 0) { @@ -105,6 +117,8 @@ export function *iterMeasuresAtRange( num, numLocal: num, time1: time, time2, + numerator: ratio.numerator, + denominator: ratio.denominator, } } @@ -114,7 +128,7 @@ export function *iterMeasuresAtRange( } let lastMeasureNum = -1 - let measureIndex = measureStart || 0 + let measureIndex = measureStart ?? 0 while (true) { if (measureIndex >= measureCache.length) @@ -134,9 +148,13 @@ export function *iterMeasuresAtRange( let num = lastMeasureNum + 1 let time = lastMeterCh.range.start + let ratioIndex = 0 + while (time.compare(range.end) < 0) { - const time2 = time.add(lastMeterCh.meter.fullCycleDuration) + const ratio = lastMeterCh.meter.ratios[ratioIndex] + const time2 = time.add(new Rational(ratio.numerator, ratio.denominator)) + ratioIndex = (ratioIndex + 1) % lastMeterCh.meter.ratios.length if (time2.compare(range.start) >= 0) { @@ -145,6 +163,8 @@ export function *iterMeasuresAtRange( num, numLocal: num, time1: time, time2, + numerator: ratio.numerator, + denominator: ratio.denominator, } } diff --git a/src/project/root.ts b/src/project/root.ts index f2fab0a..64c00bc 100644 --- a/src/project/root.ts +++ b/src/project/root.ts @@ -1,38 +1,45 @@ -import * as Project from "./index" +import * as Immutable from "immutable" +import * as Project from "./index.ts" import * as Theory from "../theory" -import Immutable from "immutable" -import Range from "../util/range" -import Rational from "../util/rational" -import ListOfRanges from "../util/listOfRanges" +import Range from "../utils/range.ts" +import Rational from "../utils/rational.ts" +import ListOfRanges from "../utils/listOfRanges.ts" // Least Common Multiple of 2, 3, 4, 5, 6, 7, 8, 9, and 10. export const MAX_RATIONAL_DENOMINATOR = 2520 -export interface Root +export interface Mutable +{ + root: ImmutableRoot +} + + +export interface ImmutableRoot { nextId: Project.ID range: Range - baseBpm: number + tracks: Project.Track[] lists: Immutable.Map> elems: Immutable.Map + tempoChangeTrackId: number keyChangeTrackId: number meterChangeTrackId: number chordTrackId: number } -export function makeEmpty(): Root +export function makeEmpty(): ImmutableRoot { return { nextId: 1, range: new Range(new Rational(0), new Rational(4)), - baseBpm: 120, tracks: [], lists: Immutable.Map>(), elems: Immutable.Map(), + tempoChangeTrackId: -1, keyChangeTrackId: -1, meterChangeTrackId: -1, chordTrackId: -1, @@ -40,87 +47,66 @@ export function makeEmpty(): Root } -export function makeNew(): Root +export function makeNew(): ImmutableRoot { let project = makeEmpty() - const track1Id = project.nextId - project.keyChangeTrackId = track1Id + project.tempoChangeTrackId = project.nextId + project = upsertTrack(project, Project.makeTrackTempoChanges()) + + project.keyChangeTrackId = project.nextId project = upsertTrack(project, Project.makeTrackKeyChanges()) - const track2Id = project.nextId - project.meterChangeTrackId = track2Id + project.meterChangeTrackId = project.nextId project = upsertTrack(project, Project.makeTrackMeterChanges()) + project = upsertElement(project, Project.makeTempoChange( + project.tempoChangeTrackId, new Rational(0), 120)) + project = upsertElement(project, Project.makeKeyChange( - track1Id, new Rational(0), Theory.Key.parse("C Major"))) + project.keyChangeTrackId, new Rational(0), Theory.Key.parse("C Major"))) project = upsertElement(project, Project.makeMeterChange( - track2Id, new Rational(0), new Theory.Meter(4, 4))) + project.meterChangeTrackId, new Rational(0), new Theory.Meter(4, 4))) - const track3Id = project.nextId - project.chordTrackId = track3Id + project.chordTrackId = project.nextId project = upsertTrack(project, Project.makeTrackChords()) - const track4Id = project.nextId - project = upsertTrack(project, Project.makeTrackNotes()) + const trackNotes = Project.makeTrackNotes() + trackNotes.editable = true + project = upsertTrack(project, trackNotes) return project } -export function getDefault(): Root +export function makeTest(): ImmutableRoot { - let project = makeEmpty() - - const track1Id = project.nextId - project.keyChangeTrackId = track1Id - project = upsertTrack(project, Project.makeTrackKeyChanges()) - - const track2Id = project.nextId - project.meterChangeTrackId = track2Id - project = upsertTrack(project, Project.makeTrackMeterChanges()) - - project = upsertElement(project, Project.makeKeyChange( - track1Id, new Rational(0), Theory.Key.parse("C Major"))) + let project = makeNew() - project = upsertElement(project, Project.makeMeterChange( - track2Id, new Rational(0), new Theory.Meter(4, 4))) + const noteTrackId = project.tracks.find(tr => tr.trackType === "notes")!.id - const track3Id = project.nextId - project.chordTrackId = track3Id - project = upsertTrack(project, Project.makeTrackChords()) - - project = upsertElement(project, Project.makeChord( - track3Id, - Range.fromStartDuration(new Rational(0), new Rational(1)), - new Theory.Chord(0, 0, 0, []))) - - const track4Id = project.nextId - project = upsertTrack(project, Project.makeTrackNotes()) - - const noteBlockId = project.nextId - project = upsertElement(project, Project.makeNoteBlock( - track4Id, - Range.fromStartDuration(new Rational(0), new Rational(4)))) - - for (let i = 0; i < 16; i++) + for (let i = 0; i < 24; i++) project = upsertElement(project, Project.makeNote( - noteBlockId, + noteTrackId, Range.fromStartDuration(new Rational(i, 4), new Rational(1, 4)), - 60 + i, - 0, - 1)) + Theory.Utils.midiMiddleC - 12 + i)) - return project + for (let i = 0; i < 7; i++) + project = upsertElement(project, Project.makeChord( + project.chordTrackId, + Range.fromStartDuration(new Rational(i * 4, 4), new Rational(4, 4)), + Theory.Chord.fromDiatonicTriad(Theory.Key.parse("C Major"), i))) + + return withRefreshedRange(project) } export function upsertTrack( - project: Root, + project: ImmutableRoot, track: Project.Track, remove: boolean = false, - insertBefore: number = -1): Root + insertBefore: number = -1): ImmutableRoot { let nextId = project.nextId let tracks = project.tracks @@ -174,7 +160,7 @@ export function upsertTrack( } -export function upsertElement(project: Root, elem: Project.Element): Root +export function upsertElement(project: ImmutableRoot, elem: Project.Element): ImmutableRoot { let nextId = project.nextId @@ -185,11 +171,11 @@ export function upsertElement(project: Root, elem: Project.Element): Root } const prevElem = project.elems.get(elem.id) - const changeParent = !!prevElem && prevElem.parentId != elem.parentId + const changeParent = !!prevElem && prevElem.parentId !== elem.parentId if (!changeParent) { - let list = project.lists.get(elem.parentId) || new ListOfRanges() + let list = project.lists.get(elem.parentId) ?? new ListOfRanges() list = list.upsert(elem) let elems = project.elems.set(elem.id, elem) @@ -198,35 +184,25 @@ export function upsertElement(project: Root, elem: Project.Element): Root } else if (elem.parentId < 0) { - let prevList = project.lists.get(prevElem!.parentId) || new ListOfRanges() - prevList = prevList.removeById(prevElem!.id) + let prevList = project.lists.get(prevElem.parentId) ?? new ListOfRanges() + prevList = prevList.removeById(prevElem.id) let elems = project.elems.delete(elem.id) - let lists = project.lists.set(prevElem!.parentId, prevList) - - /*console.log("delete elem " + elem.id + " from " + prevElem!.parentId) - console.log(prevList.findById(elem.id)) - console.log(elems.get(elem.id))*/ + let lists = project.lists.set(prevElem.parentId, prevList) return { ...project, nextId, elems, lists } } else { - let prevList = project.lists.get(prevElem!.parentId) || new ListOfRanges() - prevList = prevList.removeById(prevElem!.id) + let prevList = project.lists.get(prevElem.parentId) ?? new ListOfRanges() + prevList = prevList.removeById(prevElem.id) - let nextList = project.lists.get(elem.parentId) || new ListOfRanges() + let nextList = project.lists.get(elem.parentId) ?? new ListOfRanges() nextList = nextList.upsert(elem) - /*console.log("change parent from " + prevElem!.parentId + " to " + elem.parentId) - console.log(prevElem!) - console.log(prevList.findById(elem.id)) - console.log(elem) - console.log(nextList.findById(elem.id))*/ - let elems = project.elems.set(elem.id, elem) let lists = project.lists - .set(prevElem!.parentId, prevList) + .set(prevElem.parentId, prevList) .set(elem.parentId, nextList) return { ...project, nextId, elems, lists } @@ -234,22 +210,9 @@ export function upsertElement(project: Root, elem: Project.Element): Root } -export function keyChangeTrackId(project: Root): Project.ID -{ - return project.keyChangeTrackId -} - - -export function meterChangeTrackId(project: Root): Project.ID +export function keyAt(project: ImmutableRoot, time: Rational): Theory.Key { - return project.meterChangeTrackId -} - - -export function keyAt(project: Root, trackId: Project.ID, time: Rational): Theory.Key -{ - const keyChangeTrackId = Project.keyChangeTrackId(project) - const keyChangeTrackTimedElems = project.lists.get(keyChangeTrackId) + const keyChangeTrackTimedElems = project.lists.get(project.keyChangeTrackId) if (!keyChangeTrackTimedElems) return defaultKey() @@ -265,10 +228,9 @@ export function keyAt(project: Root, trackId: Project.ID, time: Rational): Theor } -export function meterChangeAt(project: Root, trackId: Project.ID, time: Rational): Project.MeterChange | null +export function meterChangeAt(project: ImmutableRoot, time: Rational): Project.MeterChange | null { - const meterChangeTrackId = Project.meterChangeTrackId(project) - const meterChangeTrackTimedElems = project.lists.get(meterChangeTrackId) + const meterChangeTrackTimedElems = project.lists.get(project.meterChangeTrackId) if (!meterChangeTrackTimedElems) return null @@ -284,9 +246,9 @@ export function meterChangeAt(project: Root, trackId: Project.ID, time: Rational } -export function meterAt(project: Root, trackId: Project.ID, time: Rational): Theory.Meter +export function meterAt(project: ImmutableRoot, time: Rational): Theory.Meter { - const meterCh = meterChangeAt(project, trackId, time) + const meterCh = meterChangeAt(project, time) if (meterCh) return meterCh.meter @@ -294,9 +256,9 @@ export function meterAt(project: Root, trackId: Project.ID, time: Rational): The } -export function withRefreshedRange(project: Root): Root +export function withRefreshedRange(project: ImmutableRoot): ImmutableRoot { - let range = new Range(new Rational(0), new Rational(4)); + let range = new Range(new Rational(0), new Rational(4)) for (const track of project.tracks) { @@ -305,36 +267,51 @@ export function withRefreshedRange(project: Root): Root range = range.merge(list.getTotalRange()) } - if (range.start.compare(project.range.start) == 0 && - range.end.compare(project.range.end) == 0) + if (range.start.compare(project.range.start) === 0 && + range.end.compare(project.range.end) === 0) return project return { ...project, range } } -export function getElem( - project: Root, - id: Project.ID, +export function getElem( + project: ImmutableRoot, + id: Project.ID | undefined) + : Project.Element | undefined +{ + if (id === undefined) + return undefined + + return project.elems.get(id) +} + + +export function getTypedElem( + project: ImmutableRoot, + id: Project.ID | undefined, type: T) - : Extract | null + : Extract | undefined { + if (id === undefined) + return undefined + const elem = project.elems.get(id) - if (!elem || elem.type != type) - return null + if (!elem || elem.type !== type) + return undefined return elem as Extract } export function getTrack( - project: Root, + project: ImmutableRoot, id: Project.ID, trackType: T) : Extract | null { const elem = project.elems.get(id) - if (!elem || elem.type != "track" || elem.trackType != trackType) + if (!elem || elem.type !== "track" || elem.trackType !== trackType) return null return elem as Extract @@ -342,10 +319,10 @@ export function getTrack( export function cloneElem( - fromProject: Root, + fromProject: ImmutableRoot, elem: Project.Element, - toProject: Root) - : Root + toProject: ImmutableRoot) + : ImmutableRoot { const newElem = { ...elem } newElem.id = -1 @@ -370,10 +347,10 @@ export function cloneElem( export function splitElem( - project: Root, + project: ImmutableRoot, elem: Project.Element, splitRange: Range) - : Root + : ImmutableRoot { const origProject = project const absRange = getAbsoluteRange(origProject, elem.parentId, elem.range) @@ -398,14 +375,14 @@ export function splitElem( function splitInnerElem( - origProject: Root, - project: Root, + origProject: ImmutableRoot, + project: ImmutableRoot, newParentId: Project.ID, elem: Project.Element, relativeDisplace: Rational, splitRange: Range, keepRange: Range) - : Root + : ImmutableRoot { const innerList = origProject.lists.get(elem.id) @@ -448,7 +425,7 @@ function splitInnerElem( } -export function parentTrackFor(project: Root, elemId: Project.ID): Project.Track +export function parentTrackFor(project: ImmutableRoot, elemId: Project.ID): Project.Track { while (true) { @@ -456,7 +433,7 @@ export function parentTrackFor(project: Root, elemId: Project.ID): Project.Track if (!elem) return null! - if (elem.type == "track") + if (elem.type === "track") return elem elemId = elem.parentId @@ -464,7 +441,7 @@ export function parentTrackFor(project: Root, elemId: Project.ID): Project.Track } -export function getAbsoluteTime(project: Root, parentId: Project.ID, time: Rational): Rational +export function getAbsoluteTime(project: ImmutableRoot, parentId: Project.ID, time: Rational): Rational { while (true) { @@ -472,7 +449,7 @@ export function getAbsoluteTime(project: Root, parentId: Project.ID, time: Ratio if (!elem) return time - if (elem.type == "track") + if (elem.type === "track") return time time = time.add(elem.range.start) @@ -481,7 +458,7 @@ export function getAbsoluteTime(project: Root, parentId: Project.ID, time: Ratio } -export function getRelativeTime(project: Root, parentId: Project.ID, time: Rational): Rational +export function getRelativeTime(project: ImmutableRoot, parentId: Project.ID, time: Rational): Rational { while (true) { @@ -489,7 +466,7 @@ export function getRelativeTime(project: Root, parentId: Project.ID, time: Ratio if (!elem) return time - if (elem.type == "track") + if (elem.type === "track") return time time = time.subtract(elem.range.start) @@ -498,7 +475,7 @@ export function getRelativeTime(project: Root, parentId: Project.ID, time: Ratio } -export function getAbsoluteRange(project: Root, parentId: Project.ID, range: Range): Range +export function getAbsoluteRange(project: ImmutableRoot, parentId: Project.ID, range: Range): Range { while (true) { @@ -506,7 +483,7 @@ export function getAbsoluteRange(project: Root, parentId: Project.ID, range: Ran if (!elem) return range - if (elem.type == "track") + if (elem.type === "track") return range range = range.displace(elem.range.start) @@ -515,7 +492,7 @@ export function getAbsoluteRange(project: Root, parentId: Project.ID, range: Ran } -export function getRelativeRange(project: Root, parentId: Project.ID, range: Range): Range +export function getRelativeRange(project: ImmutableRoot, parentId: Project.ID, range: Range): Range { while (true) { @@ -523,7 +500,7 @@ export function getRelativeRange(project: Root, parentId: Project.ID, range: Ran if (!elem) return range - if (elem.type == "track") + if (elem.type === "track") return range range = range.subtract(elem.range.start) @@ -532,7 +509,10 @@ export function getRelativeRange(project: Root, parentId: Project.ID, range: Ran } -export function getRangeForElems(project: Root, elemIds: Iterable): Range | null +export function getRangeForElems( + project: ImmutableRoot, + elemIds: Iterable) + : Range | null { let range: Range | null = null @@ -542,7 +522,7 @@ export function getRangeForElems(project: Root, elemIds: Iterable): if (!elem) continue - if (elem.type == "track") + if (elem.type === "track") continue const absRange = Project.getAbsoluteRange(project, elem.parentId, elem.range) @@ -553,13 +533,6 @@ export function getRangeForElems(project: Root, elemIds: Iterable): } -export function getMillisecondsAt(project: Root, time: Rational): number -{ - const measuresPerSecond = (project.baseBpm / 4 / 60) - return time.subtract(project.range.start).asFloat() / measuresPerSecond * 1000 -} - - export function defaultKey(): Theory.Key { return Theory.Key.parse("C Major") diff --git a/src/project/tempo.ts b/src/project/tempo.ts new file mode 100644 index 0000000..334cba8 --- /dev/null +++ b/src/project/tempo.ts @@ -0,0 +1,129 @@ +import Immutable from "immutable" +import * as Project from "./index.ts" +import * as Theory from "../theory" +import BinarySearch from "../utils/binarySearch.ts" +import Rational from "../utils/rational.ts" +import ListOfRanges from "../utils/listOfRanges.ts" +import Range from "../utils/range.ts" + + +export interface TempoCacheRegion +{ + time1: Rational + time2: Rational + bpm: number + startMs: number +} + + +let tempoCacheKey: ListOfRanges = null! +let tempoCache: TempoCacheRegion[] = [] +let firstTempoCh: Project.TempoChange | null = null +let lastTempoCh: Project.TempoChange | null = null + +const defaultTempoRegion: TempoCacheRegion = { + time1: new Rational(0), + time2: new Rational(0), + bpm: 120, + startMs: 0, +} + + +export function ensureTempoCacheRefreshed(project: Project.ImmutableRoot) +{ + const tempoChList = project.lists.get(project.tempoChangeTrackId) + if (!tempoChList) + return + + if (tempoChList === tempoCacheKey) + return + + tempoCacheKey = tempoChList + tempoCache = [] + + if (!tempoChList) + return + + let accumulatedMs = 0 + + for (const [elem1, elem2] of tempoChList.iterAllPairwise()) + { + if (!elem1) + firstTempoCh = elem2 as Project.TempoChange | null + + if (!elem2) + lastTempoCh = elem1 as Project.TempoChange | null + + if (!elem1) + continue + + const tempoCh1 = elem1 as Project.TempoChange + const tempoCh2 = elem2 as (Project.TempoChange | null) + + tempoCache.push({ + time1: elem1.range.start, + time2: tempoCh2 ? tempoCh2.range.start : project.range.end, + bpm: tempoCh1.bpm, + startMs: accumulatedMs, + }) + + if (tempoCh2) + { + const measuresPerSecond = (tempoCh1.bpm / 4 / 60) + const durationMs = + tempoCh2.range.start.subtract(tempoCh1.range.start).asFloat() / + measuresPerSecond * 1000 + + accumulatedMs += durationMs + } + } +} + + +export function getMillisecondsAt( + project: Project.ImmutableRoot, + time: Rational) + : number +{ + ensureTempoCacheRefreshed(project) + + const tempoCacheIndex = BinarySearch.findPreviousOrEqual( + tempoCache, + (m) => time.compare(m.time1)) + + const tempoRegion = + tempoCacheIndex !== null ? tempoCache[tempoCacheIndex] : + tempoCache.length > 0 ? tempoCache[0] : + defaultTempoRegion + + const measuresPerSecond = (tempoRegion.bpm / 4 / 60) + const localMs = + time.subtract(tempoRegion.time1).asFloat() / + measuresPerSecond * 1000 + + return tempoRegion.startMs + localMs +} + + +export function getTimeAtMilliseconds( + project: Project.ImmutableRoot, + timeMs: number) + : Rational +{ + ensureTempoCacheRefreshed(project) + + const tempoCacheIndex = BinarySearch.findPreviousOrEqual( + tempoCache, + (m) => timeMs - m.startMs) + + const tempoRegion = + tempoCacheIndex !== null ? tempoCache[tempoCacheIndex] : + tempoCache.length > 0 ? tempoCache[0] : + defaultTempoRegion + + const measuresPerSecond = (tempoRegion.bpm / 4 / 60) + const localBeats = + (timeMs - tempoRegion.startMs) / 1000 * measuresPerSecond + + return tempoRegion.time1.add(Rational.fromFloat(localBeats, Project.MAX_RATIONAL_DENOMINATOR)) +} \ No newline at end of file diff --git a/src/shortcuts.ts b/src/shortcuts.ts new file mode 100644 index 0000000..be4e8bc --- /dev/null +++ b/src/shortcuts.ts @@ -0,0 +1,65 @@ +import * as Solid from "solid-js" +import * as Global from "./state.ts" +import * as Timeline from "./timeline" +import * as Playback from "./playback" +import * as Command from "./command" + + +export function setupKeyboardShortcuts() +{ + window.addEventListener("keydown", handleKeyDown) + + Solid.onCleanup(() => window.removeEventListener("keydown", handleKeyDown)) +} + + +function handleKeyDown(ev: KeyboardEvent) +{ + if (document.activeElement && + document.activeElement.tagName === "INPUT") + return + + const key = ev.key.toLowerCase() + + if (key === " ") + { + ev.preventDefault() + ev.stopPropagation() + const project = Global.get().project + const timeline = Global.get().timeline + const playback = Global.get().playback + playback.setStartTime(timeline.playbackStartTime) + playback.togglePlaying() + return + } + + for (const command of Command.allCommands) + { + if (!command.shortcut) + continue + + if (command.isShortcutAvailable && !command.isShortcutAvailable()) + continue + + if (command.isAvailable && !command.isAvailable({})) + continue + + for (const shortcut of command.shortcut) + { + if (!!shortcut.ctrl !== ev.ctrlKey) + continue + + if (!!shortcut.shift !== ev.shiftKey) + continue + + if (key !== shortcut.key) + continue + + //console.log("handled keyboard command: ", command.name) + command.func({}) + ev.preventDefault() + ev.stopPropagation() + return + } + } +} \ No newline at end of file diff --git a/src/state.ts b/src/state.ts new file mode 100644 index 0000000..0657345 --- /dev/null +++ b/src/state.ts @@ -0,0 +1,55 @@ +import * as Solid from "solid-js" +import * as Project from "./project" +import * as Timeline from "./timeline" +import * as Playback from "./playback" +import * as Prefs from "./prefs" + + +const [refreshToken, setRefreshToken] = + Solid.createSignal(0) + + +export interface State +{ + prefs: Prefs.Prefs + project: Project.Mutable + lastSavedProject: Project.ImmutableRoot + timeline: Timeline.State + playback: Playback.Manager +} + + +export const state = makeNew() + + +export function makeNew(): State +{ + const newProject = Project.makeTest() + + return { + prefs: Prefs.makeNew(), + project: { root: newProject }, + lastSavedProject: newProject, + timeline: Timeline.makeNew(), + playback: new Playback.Manager(), + } +} + + +export function getStatic(): State +{ + return state +} + + +export function get(): State +{ + refreshToken() + return state +} + + +export function refresh() +{ + setRefreshToken(t => t + 1) +} \ No newline at end of file diff --git a/src/theory/chord.ts b/src/theory/chord.ts index 016324c..72d4c2b 100644 --- a/src/theory/chord.ts +++ b/src/theory/chord.ts @@ -1,21 +1,270 @@ -import * as MathUtils from "../util/mathUtils" -import Key from "./key" -import Utils from "./utils" +import * as Theory from "./index.ts" interface ChordMetadata { - pitches: number[] id: string - symbol: [boolean, string, string | null] name: string - startGroup?: string + + add3?: number + add5?: number + add7?: number + add9?: number + add11?: number + add13?: number + + suspended?: boolean + suppress5?: boolean + + nameBase?: string + nameSup?: string + nameSub?: string + + romanSup?: string + romanSub?: string } export const chordKinds: ChordMetadata[] = [ - { pitches: [0, 4, 7], id: "M", symbol: [false, "", null], name: "Major", startGroup: "Triads" }, + { + id: "M", + name: "Major", + add3: 0, + add5: 0, + }, + { + id: "m", + name: "Minor", + add3: -1, + add5: 0, + nameBase: "m", + }, + { + id: "+", + name: "Augmented", + add3: 0, + add5: 1, + suppress5: true, + nameSup: "+", + romanSup: "+", + }, + { + id: "o", + name: "Diminished", + add3: -1, + add5: -1, + suppress5: true, + nameSup: "o", + romanSup: "o", + }, + { + id: "oo", + name: "Doubly-diminished", + add3: -2, + add5: -1, + suppress5: true, + nameSup: "oo", + romanSup: "oo", + }, + { + id: "dom7", + name: "Dominant Seventh", + add3: 0, + add5: 0, + add7: -1, + nameBase: "7", + }, + { + id: "maj7", + name: "Major Seventh", + add3: 0, + add5: 0, + add7: 0, + nameBase: "maj7", + }, + { + id: "min7", + name: "Minor Seventh", + add3: -1, + add5: 0, + add7: -1, + nameBase: "m7", + }, + { + id: "minmaj7", + name: "Minor-major Seventh", + add3: -1, + add5: 0, + add7: 0, + nameBase: "mM7", + }, + { + id: "aug7", + name: "Augmented Seventh", + add3: 0, + add5: 1, + add7: -1, + suppress5: true, + nameBase: "7", + nameSup: "(\u{266f}5)", + romanSup: "+", + }, + { + id: "augmaj7", + name: "Augmented Major Seventh", + add3: 0, + add5: 1, + add7: 0, + suppress5: true, + nameBase: "maj7", + nameSup: "(\u{266f}5)", + romanSup: "+", + }, + { + id: "dim7", + name: "Diminished Seventh", + add3: -1, + add5: -1, + add7: -2, + suppress5: true, + nameSup: "o7", + romanSup: "o", + }, + { + id: "halfdim7", + name: "Half-diminished Seventh", + add3: -1, + add5: -1, + add7: -1, + suppress5: true, + nameBase: "m7", + nameSup: "(\u{266d}5)", + romanSup: "ø", + }, + { + id: "dom9", + name: "Dominant Ninth", + add3: 0, + add5: 0, + add7: -1, + add9: 0, + nameBase: "9", + }, + { + id: "maj9", + name: "Major Ninth", + add3: 0, + add5: 0, + add7: 0, + add9: 0, + nameBase: "maj9", + }, + { + id: "min9", + name: "Minor Ninth", + add3: -1, + add5: 0, + add7: -1, + add9: 0, + nameBase: "m9", + }, + { + id: "minmaj9", + name: "Minor-major Ninth", + add3: -1, + add5: 0, + add7: 0, + add9: 0, + nameBase: "mM9", + }, + { + id: "dom11", + name: "Dominant Eleventh", + add3: 0, + add5: 0, + add7: -1, + add9: 0, + add11: 0, + nameBase: "11", + }, + { + id: "maj11", + name: "Major Eleventh", + add3: 0, + add5: 0, + add7: 0, + add9: 0, + add11: 0, + nameBase: "maj11", + }, + { + id: "min11", + name: "Minor Eleventh", + add3: -1, + add5: 0, + add7: -1, + add9: 0, + add11: 0, + nameBase: "m11", + }, + { + id: "minmaj11", + name: "Minor-major Eleventh", + add3: -1, + add5: 0, + add7: 0, + add9: 0, + add11: 0, + nameBase: "mM11", + }, + { + id: "dom13", + name: "Dominant Thirteenth", + add3: 0, + add5: 0, + add7: -1, + add9: 0, + add11: 0, + add13: 0, + nameBase: "13", + }, + { + id: "maj13", + name: "Major Thirteenth", + add3: 0, + add5: 0, + add7: 0, + add9: 0, + add11: 0, + add13: 0, + nameBase: "maj13", + }, + { + id: "min13", + name: "Minor Thirteenth", + add3: -1, + add5: 0, + add7: -1, + add9: 0, + add11: 0, + add13: 0, + nameBase: "m13", + }, + { + id: "minmaj13", + name: "Minor-major Thirteenth", + add3: -1, + add5: 0, + add7: 0, + add9: 0, + add11: 0, + add13: 0, + nameBase: "mM13", + }, +] + +/* { pitches: [0, 4, 7], id: "M", symbol: [false, "", null], name: "Major", startGroup: "Triads" }, { pitches: [0, 3, 7], id: "m", symbol: [true, "", null], name: "Minor" }, { pitches: [0, 4, 8], id: "+", symbol: [false, "", "+"], name: "Augmented" }, { pitches: [0, 3, 6], id: "o", symbol: [true, "", "o"], name: "Diminished" }, @@ -47,7 +296,7 @@ export const chordKinds: ChordMetadata[] = { pitches: [0, 3, 6, 9, 13], id: "ob9", symbol: [true, "", "o♭9"], name: "Diminished Minor Ninth" }, { pitches: [0, 3, 6, 10, 14], id: "%9", symbol: [true, "", "ø9"], name: "Half-Diminished Ninth" }, { pitches: [0, 3, 6, 10, 13], id: "%b9", symbol: [true, "", "ø♭9"], name: "Half-Diminished Minor Ninth" }, -] +]*/ export interface ChordSuggestion @@ -58,29 +307,179 @@ export interface ChordSuggestion } +export interface ChordStringRepresentation +{ + nameBase: string + nameSup: string + nameSub: string + romanBase: string + romanSup: string + romanSub: string +} + + export default class Chord { static kinds: ChordMetadata[] = chordKinds rootChroma: number - kind: number + baseDegree: number inversion: number - modifiers: any + + sus2?: number + add3?: number + no3?: boolean + sus4?: number + add5?: number + no5?: boolean + add7?: number + add9?: number + add11?: number + add13?: number + + cachedStrKey?: Theory.Key + cachedStr?: ChordStringRepresentation - constructor(rootChroma: number, kind: number, inversion: number = 0, modifiers: any[] = []) + constructor(rootChroma: number, inversion: number = 0) { this.rootChroma = rootChroma - this.kind = kind this.inversion = inversion - this.modifiers = modifiers } - - - withChanges(obj: any): Chord + + + isEqualForPalette(other: Chord): boolean + { + return this.rootChroma === other.rootChroma && + this.inversion === other.inversion && + this.sus2 === other.sus2 && + this.add3 === other.add3 && + this.no3 === other.no3 && + this.sus4 === other.sus4 && + this.add5 === other.add5 && + this.no5 === other.no5 && + this.add7 === other.add7 && + this.add9 === other.add9 && + this.add11 === other.add11 && + this.add13 === other.add13 + } + + + clone(): Chord + { + const cloned = Object.assign({}, this) + Object.setPrototypeOf(cloned, Chord.prototype) + return cloned + } + + + withRoot(newRootChroma: number, newDegree: number) + { + const cloned = this.clone() + cloned.rootChroma = newRootChroma + cloned.baseDegree = newDegree + return cloned + } + + + static fromDiatonicTriad(key: Theory.Key, rootDegree: number) + { + const rootChroma = (key.tonic.chroma + key.scale.chromas[(rootDegree + 0) % key.scale.chromas.length]) % 12 + const chord = new Chord(rootChroma) + chord.baseDegree = rootDegree + chord.add3 = Chord.diatonicDegree(key, 2, rootChroma, rootDegree) + chord.add5 = Chord.diatonicDegree(key, 4, rootChroma, rootDegree) + return chord + } + + + static diatonicDegree( + key: Theory.Key, + degree: number, + rootChroma: number, + rootDegree: number) + { + const chroma = Theory.Utils.mod( + key.tonic.chroma + + key.scale.chromas[(rootDegree + degree) % key.scale.chromas.length], + 12) + + return Theory.Utils.modAccidental( + chroma - + (rootChroma + Theory.Scale.majorScale.chromas[degree % 7])) + } + + + withSuspended2(key: Theory.Key) + { + const chord = this.clone() + chord.sus2 = Chord.diatonicDegree(key, 1, this.rootChroma, this.baseDegree) + return chord + } + + + withNo3() + { + const chord = this.clone() + chord.no3 = true + return chord + } + + + withSuspended4(key: Theory.Key) + { + const chord = this.clone() + chord.sus4 = Chord.diatonicDegree(key, 3, this.rootChroma, this.baseDegree) + return chord + } + + + withAdded5(key: Theory.Key, accidental: number = 0) + { + const chord = this.clone() + chord.add5 = Chord.diatonicDegree(key, 4, this.rootChroma, this.baseDegree) + accidental + return chord + } + + + withNo5() + { + const chord = this.clone() + chord.no5 = true + return chord + } + + + withAdded7(key: Theory.Key) + { + const chord = this.clone() + chord.add7 = Chord.diatonicDegree(key, 6, this.rootChroma, this.baseDegree) + return chord + } + + + withAdded9(key: Theory.Key, accidental: number = 0) + { + const chord = this.clone() + chord.add9 = Chord.diatonicDegree(key, 8, this.rootChroma, this.baseDegree) + accidental + return chord + } + + + withAdded11(key: Theory.Key, accidental: number = 0) + { + const chord = this.clone() + chord.add11 = Chord.diatonicDegree(key, 10, this.rootChroma, this.baseDegree) + accidental + return chord + } + + + withAdded13(key: Theory.Key, accidental: number = 0) { - return Object.assign(new Chord(this.rootChroma, this.kind, this.inversion, this.modifiers), obj) + const chord = this.clone() + chord.add13 = Chord.diatonicDegree(key, 12, this.rootChroma, this.baseDegree) + accidental + return chord } @@ -90,15 +489,105 @@ export default class Chord } - static kindFromPitches(pitches: number[]): number + toJson(): Record + { + const json: Record = { + rootChroma: this.rootChroma, + baseDegree: this.baseDegree, + } + + if (this.inversion !== 0) + json.inversion = this.inversion + + if (this.sus2 !== undefined) + json.sus2 = this.sus2 + + if (this.add3 !== undefined) + json.add3 = this.add3 + + if (this.no3) + json.no3 = this.no3 + + if (this.sus4 !== undefined) + json.sus4 = this.sus4 + + if (this.add5 !== undefined) + json.add5 = this.add5 + + if (this.no5) + json.no5 = this.no5 + + if (this.add7 !== undefined) + json.add7 = this.add7 + + if (this.add9 !== undefined) + json.add9 = this.add9 + + if (this.add11 !== undefined) + json.add11 = this.add11 + + if (this.add13 !== undefined) + json.add13 = this.add13 + + return json + } + + + static fromJson(json: Record): Chord + { + let chord = new Chord(json.rootChroma, json.inversion) + for (const field of Object.entries(json)) + (chord as Record)[field[0]] = json[field[1]] + + return chord + } + + + static fromLegacyKind(rootChroma: number, inversion: number, kindId: string): Chord { - return chordKinds.findIndex(k => - k.pitches.length === pitches.length && - k.pitches.every((p, i) => pitches[i] === p)) + rootChroma = Theory.Utils.mod(rootChroma, 12) + + let chord = new Chord(rootChroma, inversion) + chord.baseDegree = Theory.Utils.mod( + Math.ceil(Theory.Utils.chromaToDegreeInCMajor(rootChroma)), 7) + + const legacyKinds: Record = { + "M": [0, 4, 7], + "m": [0, 3, 7], + "+": [0, 4, 8], + "o": [0, 3, 6], + "oo": [0, 2, 6], + "b5": [0, 4, 6], + "7": [0, 4, 7, 10], + "maj7": [0, 4, 7, 11], + "m7": [0, 3, 7, 10], + "mmaj7": [0, 3, 7, 11], + "+7": [0, 4, 8, 10], + "+maj7": [0, 4, 8, 11], + "o7": [0, 3, 6, 9], + "%7": [0, 3, 6, 10], + } + + const legacyKind = legacyKinds[kindId] + if (legacyKind) + { + chord.add3 = legacyKind[1] - 4 + chord.add5 = legacyKind[2] - 7 + if (legacyKind.length >= 4) + chord.add7 = legacyKind[3] - 11 + } + else + { + chord.add3 = 0 + chord.add5 = 0 + } + + console.log(kindId, chord) + return chord } - static suggestChordsForPitches(pitches: number[]): ChordSuggestion[] + /*static suggestChordsForPitches(pitches: number[]): ChordSuggestion[] { const suggestions: ChordSuggestion[] = [] @@ -142,115 +631,403 @@ export default class Chord //console.log("suggestions", pitches, suggestions) return suggestions.slice(0, 10) + }*/ + + + chordKind(): ChordMetadata | undefined + { + const compare = (a: number | undefined, b: number | undefined) => { + if (a === b) + return true + + if (a === undefined || + b === undefined) + return false + + if (Theory.Utils.mod(a, 12) === Theory.Utils.mod(b, 12)) + return true + + return false + } + + let chosenKind: ChordMetadata | undefined = undefined + let highestScore = 0 + + for (const meta of chordKinds) + { + let score = + (compare(meta.add3, this.add3) ? 1 : 0) + + (compare(meta.add5, this.add5) ? 1 : 0) + + (compare(meta.add7, this.add7) ? 1 : 0) + + (this.add7 !== undefined && + compare(meta.add9, this.add9) ? 1 : 0) + + (this.add7 !== undefined && + this.add9 !== undefined && + compare(meta.add11, this.add11) ? 1 : 0) + + (this.add7 !== undefined && + this.add9 !== undefined && + this.add11 !== undefined && + compare(meta.add13, this.add13) ? 1 : 0) + + /*let score = 0 + if (compare(meta.add3, this.add3) && + compare(meta.add5, this.add5)) + { + score += 1 + if (compare(meta.add7, this.add7)) + { + score += 1 + if (compare(meta.add9, this.add9)) + { + score += 1 + if (compare(meta.add11, this.add11)) + { + score += 1 + if (compare(meta.add13, this.add13)) + score += 1 + } + } + } + }*/ + + if (score > highestScore) + { + highestScore = score + chosenKind = meta + } + } + + return chosenKind } - get kindId(): string + accidentalStrForRoman( + key: Theory.Key, + baseDegree: number, + degree: number, + accidental: number) { - return chordKinds[this.kind].id + const chroma = key.chromaForDegree(baseDegree + degree) + const chromaInCMajor = this.rootChroma + Theory.Scale.majorScale.chromas[degree % 7] + accidental + const finalAccidental = Theory.Utils.modAccidental(chromaInCMajor - chroma) + return Theory.Utils.accidentalToStr(finalAccidental) } - - - romanBase(key: Key): string - { - const degree = key.degreeForMidi(this.rootChroma) - const chordKind = chordKinds[this.kind] || { symbol: [false, "", "?"] } - - let roman = Math.floor(degree) - let accidental = 0 - if (Math.floor(degree) != degree) + + + static accidentalStrForName(accidental: number) + { + const finalAccidental = Theory.Utils.modAccidental(accidental) + return Theory.Utils.accidentalToStr(finalAccidental) + } + + + normalizedRoman(key: Theory.Key) + { + let roman = this.baseDegree % key.scale.chromas.length + let romanChroma = key.chromaForDegree(roman) + let accidental = Theory.Utils.accidentalFor(this.rootChroma, romanChroma) + + if (accidental < -1 || accidental > 1) { - roman = MathUtils.mod(roman + 1, 7) - accidental = -1 + for (let r = 0; r < 7; r++) + { + const newRoman = (this.baseDegree + r) % key.scale.chromas.length + const newRomanChroma = key.chromaForDegree(newRoman) + const newAccidental = Theory.Utils.accidentalFor(this.rootChroma, newRomanChroma) + if (Math.abs(newAccidental) < Math.abs(accidental)) + { + roman = newRoman + romanChroma = newRomanChroma + accidental = newAccidental + } + } } - let baseStr = Utils.accidentalToStr(accidental, true) + Utils.degreeToRomanStr(roman) - if (chordKind.symbol[0]) - baseStr = baseStr.toLowerCase() - - return baseStr + chordKind.symbol[1] + return { + roman, + romanChroma, + accidental, + } } - romanSup(key: Key): string + str(key: Theory.Key): ChordStringRepresentation { - const chordKind = chordKinds[this.kind] || { symbol: [false, "", "?"] } + if (this.cachedStr && + this.cachedStrKey === key) + return this.cachedStr - let supStr = chordKind.symbol[2] || "" + const kind = this.chordKind() ?? { + id: "custom", + name: "Custom", + } + + let { roman, romanChroma, accidental } = this.normalizedRoman(key) + + let accidentalStr = Theory.Utils.accidentalToStr(accidental, true) + + const isLowercase = + kind !== undefined && + this.add3 !== undefined && + this.add3 < 0 - if (this.modifiers) + let nameBase = + Theory.Utils.lowercaseIf( + key.nameForDegree(roman).altered(accidental).strUnicode, + isLowercase) + + (kind.nameBase ?? "") + + let nameSup = kind.nameSup ?? "" + let nameSub = kind.nameSub ?? "" + + let romanBase = + accidentalStr + + Theory.Utils.lowercaseIf( + Theory.Utils.degreeToRomanStr(roman), + isLowercase) + + let romanSup = kind.romanSup ?? "" + let romanSub = kind.romanSub ?? "" + + if (this.sus2 !== undefined) { - if (this.modifiers.add9) - supStr += "(add9)" - - if (this.modifiers.add11) - supStr += "(add11)" - - if (this.modifiers.add13) - supStr += "(add13)" - - if (this.modifiers.no3) - supStr += "(no3)" - - if (this.modifiers.no5) - supStr += "(no5)" + nameSub += `sus${ Chord.accidentalStrForName(this.sus2) }2` + romanSub += `sus${ this.accidentalStrForRoman(key, roman, 1, this.sus2) }2` } - return supStr - } - - - romanSub(key: Key): string - { - let subStr = "" + if (this.sus4 !== undefined) + { + nameSub += `sus${ Chord.accidentalStrForName(this.sus4) }4` + romanSub += `sus${ this.accidentalStrForRoman(key, roman, 3, this.sus4) }4` + } + + let add3Name = this.add3 + let add3Roman = this.add3 + let add5Name = this.add5 + let add5Roman = this.add5 + let add7Name = this.add7 + let add7Roman = this.add7 + let add9Name = this.add9 + let add9Roman = this.add9 + let add11Name = this.add11 + let add11Roman = this.add11 + let add13Name = this.add13 + let add13Roman = this.add13 + + if (kind.suppress5) + { + add5Name = undefined + add5Roman = undefined + } + + if (kind.add5 === 0 && + this.add5 === 0) + { + add5Name = undefined + add5Roman = undefined + } + + if (add7Roman === -1 && + add5Roman === -1 && + add3Roman === -1) + { + romanSup += `ø` + add5Roman = undefined + add3Roman = undefined + } + + if (add5Roman === -1 && + add3Roman === -2) + { + romanSup += `oo` + add5Roman = undefined + add3Roman = undefined + } + + if (add5Roman === -1 && + add3Roman === -1) + { + romanSup += `o` + add5Roman = undefined + add3Roman = undefined + } - if (this.modifiers) + if (add5Roman === 1 && + add3Roman === 0) + { + romanSup += `+` + add5Roman = undefined + } + + if (add13Roman !== undefined && + add11Roman !== undefined && + add9Roman !== undefined && + add7Roman !== undefined) + { + romanSup += `${ this.accidentalStrForRoman(key, roman, 12, add13Roman) }13` + add13Roman = undefined + add11Roman = undefined + add9Roman = undefined + add7Roman = undefined + } + + if (add11Roman !== undefined && + add9Roman !== undefined && + add7Roman !== undefined) + { + romanSup += `${ this.accidentalStrForRoman(key, roman, 10, add11Roman) }11` + add11Roman = undefined + add9Roman = undefined + add7Roman = undefined + } + + if (add9Roman !== undefined && + add7Roman !== undefined) + { + romanSup += `${ this.accidentalStrForRoman(key, roman, 8, add9Roman) }9` + add9Roman = undefined + add7Roman = undefined + } + + if (add7Roman !== undefined) + { + romanSup += `${ this.accidentalStrForRoman(key, roman, 6, add7Roman) }7` + add7Roman = undefined + } + + if (this.no3) + romanSup += `(no3)` + + if (this.no5) + romanSup += `(no5)` + + if (!this.no5 && + add5Roman !== undefined && + (kind.add5 === undefined || add5Roman !== kind.add5)) { - if (this.modifiers.sus2) + const accidentalStr = this.accidentalStrForRoman(key, roman, 4, add5Roman) + if (accidentalStr !== "") { - if (this.modifiers.sus4) - subStr += "sus24" - else - subStr += "sus2" + romanSup += `(${ accidentalStr }5)` + add5Roman = undefined } - else if (this.modifiers.sus4) - subStr += "sus4" } - - return subStr + + + if (this.no3) + nameSup += `(no3)` + + if (this.no5) + nameSup += `(no5)` + + if (!this.no5 && + add5Name !== undefined && + (kind.add5 === undefined || add5Name !== kind.add5)) + { + nameSup += `(${ Chord.accidentalStrForName(add5Name) }5)` + add5Name = undefined + } + + if (add7Name !== undefined && + (kind.add7 === undefined || add7Name !== kind.add7)) + nameSup += `(${ Chord.accidentalStrForName(add7Name) }7)` + + if (add7Roman !== undefined) + romanSup += `(${ this.accidentalStrForRoman(key, roman, 6, add7Roman) }7)` + + const hasAdd9 = + this.add7 === undefined + + const hasAdd11 = + this.add7 === undefined || + this.add9 === undefined + + const hasAdd13 = + this.add7 === undefined || + this.add9 === undefined || + this.add11 === undefined + + if (add9Name !== undefined && + (kind.add9 === undefined || add9Name !== kind.add9)) + nameSup += `(${ hasAdd9 ? "add" : ""}${ Chord.accidentalStrForName(add9Name) }9)` + + if (add9Roman !== undefined) + romanSup += `(${ hasAdd9 ? "add" : ""}${ this.accidentalStrForRoman(key, roman, 8, add9Roman) }9)` + + if (add11Name !== undefined && + (kind.add11 === undefined || add11Name !== kind.add11)) + nameSup += `(${ hasAdd11 ? "add" : ""}${ Chord.accidentalStrForName(add11Name) }11)` + + if (add11Roman !== undefined) + romanSup += `(${ hasAdd11 ? "add" : ""}${ this.accidentalStrForRoman(key, roman, 10, add11Roman) }11)` + + if (add13Name !== undefined && + (kind.add13 === undefined || add13Name !== kind.add13)) + nameSup += `(${ hasAdd13 ? "add" : ""}${ Chord.accidentalStrForName(add13Name) }13)` + + if (add13Roman !== undefined) + romanSup += `(${ hasAdd13 ? "add" : ""}${ this.accidentalStrForRoman(key, roman, 12, add13Roman) }13)` + + this.cachedStr = { + nameBase, + nameSup, + nameSub, + romanBase, + romanSup, + romanSub, + } + + this.cachedStrKey = key + return this.cachedStr } - get pitches(): number[] + get pitchesRelativeToRoot(): number[] { - const chordData = chordKinds[this.kind] - if (!chordData) - return [] - - const rootMidi = Utils.mod(this.rootChroma, 12) - - let pitches = [] - for (let i = 0; i < chordData.pitches.length; i++) - pitches.push(rootMidi + chordData.pitches[i]) - - if (this.modifiers.sus2) - pitches[1] = rootMidi + 2 - - if (this.modifiers.sus4) - { - if (this.modifiers.sus2) - pitches.splice(2, 0, rootMidi + 5) - else - pitches[1] = rootMidi + 5 - } - + const pitches = [0] + + if (this.sus2 !== undefined) + pitches.push(this.sus2 + Theory.Scale.majorScale.chromas[1]) + + if (this.add3 !== undefined && + !this.no3 && + this.sus2 === undefined && + this.sus4 === undefined) + pitches.push(this.add3 + Theory.Scale.majorScale.chromas[2]) + + if (this.sus4 !== undefined) + pitches.push(this.sus4 + Theory.Scale.majorScale.chromas[3]) + + if (this.add5 !== undefined && + !this.no5) + pitches.push(this.add5 + Theory.Scale.majorScale.chromas[4]) + + if (this.add7 !== undefined) + pitches.push(this.add7 + Theory.Scale.majorScale.chromas[6]) + + if (this.add9 !== undefined) + pitches.push(12 + this.add9 + Theory.Scale.majorScale.chromas[1]) + + if (this.add11 !== undefined) + pitches.push(12 + this.add11 + Theory.Scale.majorScale.chromas[3]) + + if (this.add13 !== undefined) + pitches.push(12 + this.add13 + Theory.Scale.majorScale.chromas[5]) + return pitches } + get pitches(): number[] + { + const rootMidi = Theory.Utils.mod(this.rootChroma, 12) + return this.pitchesRelativeToRoot.map(p => p + rootMidi) + } + + get strummingPitches(): number[] { - const rootMidi = Utils.mod(this.rootChroma, 12) + const rootMidi = Theory.Utils.mod(this.rootChroma, 12) let pitches = this.pitches if (pitches.length == 0) return [] diff --git a/src/theory/chordOptions.ts b/src/theory/chordOptions.ts new file mode 100644 index 0000000..76c50f9 --- /dev/null +++ b/src/theory/chordOptions.ts @@ -0,0 +1,186 @@ +import * as Theory from "./index.ts" + + +export type ChordStacking = 0 | 7 | 9 | 11 | 13 + + +export interface ChordOptions +{ + withStacking: ChordStacking + withSus2: boolean + withSus4: boolean + withAdd9: boolean + withAdd11: boolean + withAdd13: boolean + withNo3: boolean + withNo5: boolean + withFlat5: boolean + withSharp5: boolean + withFlat9: boolean + withSharp9: boolean + withSharp11: boolean + withFlat13: boolean + borrowFromScaleId?: string +} + + +export namespace ChordOptions +{ + export function makeEmpty(): ChordOptions + { + return { + withStacking: 0, + withSus2: false, + withSus4: false, + withAdd9: false, + withAdd11: false, + withAdd13: false, + withNo3: false, + withNo5: false, + withFlat5: false, + withSharp5: false, + withFlat9: false, + withSharp9: false, + withSharp11: false, + withFlat13: false, + borrowFromScaleId: undefined, + } + } + + + export function buildChord( + key: Theory.Key, + rootDegree: number, + opts: ChordOptions) + { + let chord = Theory.Chord.fromDiatonicTriad(key, rootDegree) + + if (opts.withStacking >= 7) + chord = chord.withAdded7(key) + + if (opts.withStacking >= 9) + chord = chord.withAdded9(key) + + if (opts.withStacking >= 11) + chord = chord.withAdded11(key) + + if (opts.withStacking >= 13) + chord = chord.withAdded13(key) + + if (opts.withSus2) + chord = chord.withSuspended2(key) + + if (opts.withSus4) + chord = chord.withSuspended4(key) + + if (opts.withAdd9) + chord = chord.withAdded9(key) + + if (opts.withAdd11) + chord = chord.withAdded11(key) + + if (opts.withAdd13) + chord = chord.withAdded13(key) + + if (opts.withFlat5) + chord = chord.withAdded5(key, -1) + + if (opts.withSharp5) + chord = chord.withAdded5(key, 1) + + if (opts.withFlat9) + chord = chord.withAdded9(key, -1) + + if (opts.withSharp9) + chord = chord.withAdded9(key, 1) + + if (opts.withSharp11) + chord = chord.withAdded11(key, 1) + + if (opts.withFlat13) + chord = chord.withAdded13(key, -1) + + if (opts.withNo3) + chord = chord.withNo3() + + if (opts.withNo5) + chord = chord.withNo5() + + return chord + } + + + export function makeFromChord( + key: Theory.Key, + chord: Theory.Chord) + : ChordOptions + { + for (const withStacking of [0, 7, 9, 11, 13] satisfies ChordStacking[]) + { + for (let degree = 0; degree < 7; degree++) + { + const testOpts: ChordOptions = { ...makeEmpty(), withStacking } + if (chord.isEqualForPalette(buildChord(key, degree, testOpts))) + return testOpts + } + } + + const opts = makeEmpty() + + if (chord.add7 !== undefined) + { + opts.withStacking = 7 + if (chord.add9 !== undefined) + { + opts.withStacking = 9 + if (chord.add11 !== undefined) + { + opts.withStacking = 11 + if (chord.add13 !== undefined) + opts.withStacking = 13 + } + } + } + + if (chord.sus2 !== undefined) + opts.withSus2 = true + + if (chord.sus4 !== undefined) + opts.withSus4 = true + + if (chord.no3) + opts.withNo3 = true + + if (chord.no5) + opts.withNo3 = true + + if (chord.add5 === -1) + opts.withFlat5 = true + + if (chord.add5 === 1) + opts.withSharp5 = true + + if (chord.add9 === -1) + opts.withFlat9 = true + + if (chord.add9 === 0) + opts.withAdd9 = true + + if (chord.add9 === 1) + opts.withSharp9 = true + + if (chord.add11 === 0) + opts.withAdd11 = true + + if (chord.add11 === 1) + opts.withSharp11 = true + + if (chord.add13 === -1) + opts.withFlat13 = true + + if (chord.add13 === 0) + opts.withAdd13 = true + + return opts + } +} \ No newline at end of file diff --git a/src/theory/index.ts b/src/theory/index.ts index c08938e..4a803f0 100644 --- a/src/theory/index.ts +++ b/src/theory/index.ts @@ -1,7 +1,8 @@ -export { default as Pitch } from "./pitch" -export { default as PitchName } from "./pitchName" -export { default as Scale } from "./scale" -export { default as Key } from "./key" -export { default as Meter } from "./meter" -export { default as Chord } from "./chord" -export { default as Utils } from "./utils" \ No newline at end of file +export { default as Pitch } from "./pitch.ts" +export { default as PitchName } from "./pitchName.ts" +export { Scale, ScaleMetadata } from "./scale.ts" +export { default as Key } from "./key.ts" +export { default as Meter } from "./meter.ts" +export { default as Chord } from "./chord.ts" +export * from "./chordOptions.ts" +export { default as Utils } from "./utils.ts" \ No newline at end of file diff --git a/src/theory/key.ts b/src/theory/key.ts index 3bebda3..de5561a 100644 --- a/src/theory/key.ts +++ b/src/theory/key.ts @@ -1,7 +1,7 @@ -import PitchName from "./pitchName" -import Pitch from "./pitch" -import Scale from "./scale" -import Utils from "./utils" +import PitchName from "./pitchName.ts" +import Pitch from "./pitch.ts" +import { Scale } from "./scale.ts" +import Utils from "./utils.ts" export default class Key @@ -10,6 +10,8 @@ export default class Key scale: Scale _chromaToDegree: number[] + cachedNamedPitches?: PitchName[] + constructor(tonic: PitchName, scale: Scale) { @@ -38,6 +40,31 @@ export default class Key nextChroma += 1 } } + + /*let firstFrac: number | undefined = undefined + for (let c = 0; c <= this.scale.chromas.length; c++) + { + if (c >= this.scale.chromas.length || + (this._chromaToDegree[c] % 1) === 0) + { + if (firstFrac !== undefined) + { + const base = Math.floor(this._chromaToDegree[firstFrac]) + for (let d = firstFrac; d < c; d++) + { + const frac = (d - firstFrac + 1) / (c - d + 1) + this._chromaToDegree[d] = base + frac + } + + firstFrac = undefined + } + + continue + } + + if (firstFrac === undefined) + firstFrac = c + }*/ } @@ -55,8 +82,8 @@ export default class Key if (separator < 1) throw "invalid key string" - const tonicStr = str.substr(0, separator) - const scaleStr = str.substr(separator) + const tonicStr = str.substring(0, separator) + const scaleStr = str.substring(separator) const tonic = PitchName.parse(tonicStr) const scale = Scale.parse(scaleStr) @@ -138,16 +165,16 @@ export default class Key { const degree = this.degreeForMidi(midi) - const letter1 = Utils.mod(this.tonic.letter + Math.floor(degree), 7) + const letter1 = Utils.mod(this.tonic.letter + Math.floor(degree), this.scale.chromas.length) const accidental1 = Utils.mod(midi - Utils.letterToChroma(letter1) + 6, 12) - 6 if (degree == Math.floor(degree)) return new PitchName(letter1, accidental1) - const letter2 = Utils.mod(letter1 + 1, 7) + const letter2 = Utils.mod(letter1 + 1, this.scale.chromas.length) const accidental2 = Utils.mod(midi - Utils.letterToChroma(letter2) + 6, 12) - 6 - const letter3 = Utils.mod(letter1 - 1, 7) + const letter3 = Utils.mod(letter1 - 1, this.scale.chromas.length) const accidental3 = Utils.mod(midi - Utils.letterToChroma(letter3) + 6, 12) - 6 const attempts = [ @@ -194,6 +221,12 @@ export default class Key get namedPitches(): PitchName[] { - return this.scale.chromas.map(chroma => this.nameForMidi(chroma + this.tonic.midi)) + if (this.cachedNamedPitches) + return this.cachedNamedPitches + + this.cachedNamedPitches = this.scale.chromas.map(chroma => + this.nameForMidi(chroma + this.tonic.midi)) + + return this.cachedNamedPitches } } \ No newline at end of file diff --git a/src/theory/meter.ts b/src/theory/meter.ts index b432c26..559d1ec 100644 --- a/src/theory/meter.ts +++ b/src/theory/meter.ts @@ -1,33 +1,76 @@ -import Rational from "../util/rational" +import Rational from "../utils/rational.ts" -export default class Meter +export interface Ratio { numerator: number denominator: number +} + + +export default class Meter +{ + ratios: Ratio[] constructor(numerator: number, denominator: number) { - this.numerator = numerator - this.denominator = denominator + this.ratios = [{ numerator, denominator }] + } + + + clone() + { + let cloned = new Meter(0, 0) + cloned.ratios = [...this.ratios.map(r => ({ ...r }))] + return cloned + } + + + withNumerator(index: number, numerator: number) + { + let cloned = this.clone() + cloned.ratios[index].numerator = numerator + return cloned + } + + + withDenominator(index: number, denominator: number) + { + let cloned = this.clone() + cloned.ratios[index].denominator = denominator + return cloned } - - - withChanges(obj: any): Meter + + + withAddedRatio() { - return Object.assign(new Meter(this.numerator, this.denominator), obj) + let cloned = this.clone() + cloned.ratios.push({ ...cloned.ratios[cloned.ratios.length - 1] }) + return cloned + } + + + withRemovedRatio(index: number) + { + let cloned = this.clone() + cloned.ratios.splice(index, 1) + return cloned } *iterMeasuresPairwise(time: Rational | null = null): Generator<[number, number, any, any], void, void> { - time = time || new Rational(0) + time = time ?? new Rational(0) + + let ratioIndex = 0 while (true) { - const nextTime: Rational = time.add(new Rational(this.numerator, this.denominator)) - yield [this.numerator, this.denominator, time, nextTime] + const ratio = this.ratios[ratioIndex] + ratioIndex = (ratioIndex + 1) % this.ratios.length + const nextTime: Rational = time.add(new Rational(ratio.numerator, ratio.denominator)) + yield [ratio.numerator, ratio.denominator, time, nextTime] time = nextTime } } @@ -35,26 +78,59 @@ export default class Meter get fullCycleDuration(): Rational { - return new Rational(this.numerator, this.denominator) + let duration = new Rational(0) + for (const ratio of this.ratios) + duration = duration.add(new Rational(ratio.numerator, ratio.denominator)) + + return duration } get alternatingMeasureCount(): number { - return 1 + return this.ratios.length } - get str(): string + toJson(): [number, number][] { - return this.numerator + " / " + this.denominator + return this.ratios.map(r => [r.numerator, r.denominator]) + } + + + static fromJson(data: [number, number][]): Meter + { + let meter = new Meter(data[0][0], data[0][1]) + for (let i = 1; i < data.length; i++) + { + meter = meter.withAddedRatio() + meter.ratios[i].numerator = data[i][0] + meter.ratios[i].denominator = data[i][1] + } + + return meter + } + + + toString(): string + { + let str = "" + for (const ratio of this.ratios) + { + if (str !== "") + str += " -- " + + str += ratio.numerator + " / " + ratio.denominator + } + + return str } static parse(src: string): Meter { const split = src.split("/") - if (split.length != 2) + if (split.length !== 2) throw "invalid meter syntax" const numerator = parseInt(split[0].trim()) diff --git a/src/theory/pitch.ts b/src/theory/pitch.ts index d1d41b1..1b6338c 100644 --- a/src/theory/pitch.ts +++ b/src/theory/pitch.ts @@ -1,5 +1,5 @@ -import PitchName from "./pitchName" -import Utils from "./utils" +import PitchName from "./pitchName.ts" +import Utils from "./utils.ts" export default class Pitch diff --git a/src/theory/pitchName.ts b/src/theory/pitchName.ts index c8a88d6..cc1bb8f 100644 --- a/src/theory/pitchName.ts +++ b/src/theory/pitchName.ts @@ -1,4 +1,4 @@ -import Utils from "./utils" +import Utils from "./utils.ts" export default class PitchName @@ -6,6 +6,9 @@ export default class PitchName letter: number accidental: number + cachedStr?: string + cachedStrUnicode?: string + constructor(letter: number, accidental: number) { @@ -91,19 +94,27 @@ export default class PitchName get str(): string { + if (this.cachedStr) + return this.cachedStr + const letterStr = Utils.letterToStr(this.letter) const accidentalStr = Utils.accidentalToStr(this.accidental) - return letterStr + accidentalStr + this.cachedStr = letterStr + accidentalStr + return this.cachedStr } get strUnicode(): string { + if (this.cachedStrUnicode) + return this.cachedStrUnicode + const letterStr = Utils.letterToStr(this.letter) const accidentalStr = Utils.accidentalToStr(this.accidental, true) - return letterStr + accidentalStr + this.cachedStrUnicode = letterStr + accidentalStr + return this.cachedStrUnicode } diff --git a/src/theory/scale.ts b/src/theory/scale.ts index 2237013..a6a67df 100644 --- a/src/theory/scale.ts +++ b/src/theory/scale.ts @@ -1,35 +1,108 @@ -import PitchName from "./pitchName" +import PitchName from "./pitchName.ts" -interface ScaleMetadata +export interface ScaleMetadata { - chromas: number[] - mode: number id: string names: string[] + chromas: number[] + mode: number + circleOfFifthsOffset?: number } -export const knownScales: ScaleMetadata[] = -[ - { chromas: [0, 2, 4, 5, 7, 9, 11], mode: 0, id: "maj", names: ["Major", "Ionian"] }, - { chromas: [0, 2, 3, 5, 7, 9, 10], mode: 1, id: "dor", names: ["Dorian"] }, - { chromas: [0, 1, 3, 5, 7, 8, 10], mode: 2, id: "phr", names: ["Phrygian"] }, - { chromas: [0, 2, 4, 6, 7, 9, 11], mode: 3, id: "lyd", names: ["Lydian"] }, - { chromas: [0, 2, 4, 5, 7, 9, 10], mode: 4, id: "mix", names: ["Mixolydian"] }, - { chromas: [0, 2, 3, 5, 7, 8, 10], mode: 5, id: "min", names: ["Natural Minor", "Minor", "Aeolian"] }, - { chromas: [0, 1, 3, 5, 6, 8, 10], mode: 6, id: "loc", names: ["Locrian"] }, - - { chromas: [0, 1, 4, 5, 7, 8, 11], mode: 0, id: "dharmaj", names: ["Double Harmonic Major"] }, +export const knownScales: ScaleMetadata[] = [ + { + id: "maj", + names: ["Major", "Ionian"], + chromas: [0, 2, 4, 5, 7, 9, 11], + mode: 0, + circleOfFifthsOffset: 0, + }, + { + id: "dor", + names: ["Dorian"], + chromas: [0, 2, 3, 5, 7, 9, 10], + mode: 1, + circleOfFifthsOffset: 2, + }, + { + id: "phr", + names: ["Phrygian"], + chromas: [0, 1, 3, 5, 7, 8, 10], + mode: 2, + circleOfFifthsOffset: 4, + }, + { + id: "lyd", + names: ["Lydian"], + chromas: [0, 2, 4, 6, 7, 9, 11], + mode: 3, + circleOfFifthsOffset: -1, + }, + { + id: "mix", + names: ["Mixolydian"], + chromas: [0, 2, 4, 5, 7, 9, 10], + mode: 4, + circleOfFifthsOffset: 1, + }, + { + id: "min", + names: ["Natural Minor", "Minor", "Aeolian"], + chromas: [0, 2, 3, 5, 7, 8, 10], + mode: 5, + circleOfFifthsOffset: 3, + }, + { + id: "loc", + names: ["Locrian"], + chromas: [0, 1, 3, 5, 6, 8, 10], + mode: 6, + circleOfFifthsOffset: 5, + }, + + { + id: "phrdom", + names: ["Phrygian Dominant"], + chromas: [0, 1, 4, 5, 7, 8, 10], + mode: 2, + circleOfFifthsOffset: 4, + }, + + { + id: "harmaj", + names: ["Harmonic Major"], + chromas: [0, 2, 4, 5, 7, 8, 11], + mode: 0, + circleOfFifthsOffset: 0, + }, + { + id: "dharmaj", + names: ["Double Harmonic Major"], + chromas: [0, 1, 4, 5, 7, 8, 11], + mode: 0, + circleOfFifthsOffset: 0, + }, + + { + id: "whole", + names: ["Whole-tone"], + chromas: [0, 2, 4, 6, 8, 10], + mode: 0, + circleOfFifthsOffset: 0, + }, ] -export default class Scale +export class Scale { static list = knownScales + static majorScale = knownScales[0] + chromas: number[] - metadata: ScaleMetadata | null | undefined + metadata: ScaleMetadata | undefined constructor(chromas: number[]) @@ -40,8 +113,8 @@ export default class Scale this.chromas = chromas this.metadata = knownScales.find(s => - s.chromas.length == this.chromas.length && - s.chromas.every((p, index) => p == this.chromas[index])) + s.chromas.length === this.chromas.length && + s.chromas.every((p, index) => p === this.chromas[index])) } @@ -53,7 +126,11 @@ export default class Scale static fromId(id: string): Scale { - return new Scale(knownScales.find(s => s.id === id)!.chromas) + const meta = knownScales.find(s => s.id === id) + if (!meta) + throw "scale id not found" + + return new Scale(meta.chromas) } @@ -61,7 +138,7 @@ export default class Scale { str = str.toLowerCase().trim() - const knownScale = knownScales.find(s => s.names.some(n => n.toLowerCase() == str)) + const knownScale = knownScales.find(s => s.names.some(n => n.toLowerCase() === str)) if (!knownScale) throw "no known scale with given name" @@ -69,19 +146,19 @@ export default class Scale } - get id(): string | null + get id(): string | undefined { if (!this.metadata) - return null + return undefined return this.metadata.id } - get name(): string | null + get name(): string | undefined { if (!this.metadata) - return null + return undefined return this.metadata.names[0] } @@ -89,7 +166,7 @@ export default class Scale get alterationsFromMajor(): number[] { - if (this.chromas.length != 7) + if (this.chromas.length !== 7) throw "not a seven-note scale" return this.chromas.map((pitch, i) => pitch - knownScales[0].chromas[i]) diff --git a/src/theory/utils.ts b/src/theory/utils.ts index 6edb860..e72d52b 100644 --- a/src/theory/utils.ts +++ b/src/theory/utils.ts @@ -5,7 +5,39 @@ export default class Utils return (x % m + m) % m } + static modAccidental(accidental: number) + { + return Utils.mod(accidental + 6, 12) - 6 + } + + static modRomanAccidental(accidental: number) + { + return Utils.mod(accidental + 4, 7) - 4 + } + + static accidentalFor(chroma: number, fromBaseChroma: number) + { + const accidental1 = chroma - fromBaseChroma + const accidental2 = chroma - fromBaseChroma - 12 + + if (Math.abs(accidental1) < Math.abs(accidental2)) + return accidental1 + else + return accidental2 + } + static lowercaseIf(str: string, lowercase: boolean) + { + if (lowercase) + return str.toLowerCase() + + return str + } + + static midiMiddleC = 60 + + static degreeToChromaInCMajor = (degree: number): number => [0, 2, 4, 5, 7, 9, 11][degree] + static chromaToLetter = (chroma: number): number => [0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6][chroma] static chromaToAccidental = (chroma: number): number => [0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0][chroma] static chromaToDegreeInCMajor = (chroma: number): number => [0, 0.5, 1, 1.5, 2, 3, 3.5, 4, 4.5, 5, 5.5, 6][chroma] @@ -18,21 +50,36 @@ export default class Utils return map[str] } - static degreeToRomanStr = (degree: number): string => - ["I", "II", "III", "IV", "V", "VI", "VII"][degree] + static circleOfFifthsToLetter = (index: number): number => + [0, 4, 1, 5, 2, 6, 3][Utils.mod(index, 7)] - static degreeToColor = (degree: number): string => - ["#f00", "#f80", "#fd0", "#0d0", "#00f", "#80f", "#f0f"][degree] + static circleOfFifthsToAccidental = (index: number): number => + Math.floor((index + 1) / 7) + + static degreeToRomanStr = (degree: number): string => + ["I", "II", "III", "IV", "V", "VI", "VII"][Utils.mod(degree, 7)] + + static degreeToLetterStr = (degree: number): string => + Utils.letterToStr(Utils.mod(degree, 7)) + + static degreeToColor = (degree: number): string => + ["#f20", "#f80", "#edcd00", "#0b0", "#02f", "#80f", "#f0e"][degree] static degreeToColorFaded = (degree: number): string => - ["#400", "#420", "#430", "#030", "#004", "#204", "#404"][degree] + ["#400", "#420", "#430", "#030", "#025", "#2c0c4c", "#404"][degree] - static accidentalToStr(accidental: number, useUnicode: boolean = false): string + static accidentalToStr(accidental: number, useUnicode: boolean = true): string { + //if (useUnicode && accidental === 2) + // return "𝄪" + + if (useUnicode && accidental === -2) + return "𝄫" + if (accidental < 0) - return (useUnicode ? "\u{266d}" : "b").repeat(-accidental) + return (useUnicode ? "♭" : "b").repeat(-accidental) else - return (useUnicode ? "\u{266f}" : "#").repeat(accidental) + return (useUnicode ? "♯" : "#").repeat(accidental) } } \ No newline at end of file diff --git a/src/timeline/Element.tsx b/src/timeline/Element.tsx index 53b28b2..a3d1f3b 100644 --- a/src/timeline/Element.tsx +++ b/src/timeline/Element.tsx @@ -1,393 +1,250 @@ -import React from "react" -import * as Timeline from "./index" -import * as Project from "../project" +import * as Solid from "solid-js" +import * as Global from "../state.ts" +import * as Timeline from "./index.ts" import * as Playback from "../playback" -import * as Prefs from "../prefs" -import * as Popup from "../popup" -import * as Dockable from "../dockable" -import * as UI from "../ui" -import { useRefState, RefState } from "../util/refState" -import Rect from "../util/rect" -import styled from "styled-components" - - -const StyledTrackButton = styled.button` - pointer-events: auto; - color: #fff; - border: 1px solid #888; - border-radius: 0.5em; - background-color: #2f3136; - padding: 0.1em 0.3em; - cursor: pointer; - - &:hover - { - border: 1px solid #fff; - } -` - - -export function TimelineElement(props: { state?: RefState }) -{ - const refDiv = React.useRef(null) - const refCanvas = React.useRef(null) +import Rect from "../utils/rect.ts" - const editorState = props.state ?? useRefState(() => Timeline.init()) - const dockableWindow = Dockable.useWindow() - const lastTimelineRenderRef = React.useRef(0) - - const render = (force?: boolean) => - { - if (!refCanvas.current) - return - - const now = new Date().getTime() - if (!force && now < lastTimelineRenderRef.current + 15) - return +export const eventTimelineRedraw = "timelineRedraw" +export const eventTimelineRelayout = "timelineRelayout" - //console.log("Timeline render") - lastTimelineRenderRef.current = now - Timeline.render( - editorState.ref.current, - refCanvas.current.getContext("2d")!) - } +export function Element(props: {}) +{ + let div: HTMLDivElement | undefined = undefined + let canvas: HTMLCanvasElement | undefined = undefined - const onResize = () => - { - if (!refDiv.current || !refCanvas.current) + Solid.createEffect(() => { + if (!div || !canvas) return - const pixelRatio = window.devicePixelRatio || 1 - - const rect = refDiv.current.getBoundingClientRect() - const x = Math.floor(rect.x) - const y = Math.floor(rect.y) - const w = Math.floor(rect.width * pixelRatio) - const h = Math.floor(rect.height * pixelRatio) - - refCanvas.current.style.width = Math.floor(rect.width) + "px" - refCanvas.current.style.height = Math.floor(rect.height) + "px" - refCanvas.current.width = w - refCanvas.current.height = h - - const renderRect = new Rect(x, y, w, h) + const cleanup = registerHandlers(div, canvas) - Timeline.resize(editorState.ref.current, pixelRatio, renderRect) - render(true) - } + Solid.onCleanup(cleanup) + }) - React.useLayoutEffect(() => - { - if (!refDiv.current) - return - const observer = new ResizeObserver(entries => - { - onResize() - }) + return
+ +
+} - const elem = refDiv.current - observer.observe(elem) - onResize() - return () => observer.unobserve(elem) +function canvasResize( + div: HTMLDivElement, + canvas: HTMLCanvasElement, + timeline: Timeline.State) +{ + const pixelRatio = window.devicePixelRatio ?? 1 + + const domRect = div.getBoundingClientRect() + const x = Math.floor(domRect.x) + const y = Math.floor(domRect.y) + const w = Math.floor(domRect.width * pixelRatio) + const h = Math.floor(domRect.height * pixelRatio) + + if (canvas.width === w && + canvas.height === h && + timeline.renderRect.w === w && + timeline.renderRect.h === h) + return + + canvas.style.width = Math.floor(domRect.width) + "px" + canvas.style.height = Math.floor(domRect.height) + "px" + canvas.width = w + canvas.height = h + + console.log("resize", w, h) + + const rect = new Rect(0, 0, w, h) + + Timeline.resize(timeline, pixelRatio, rect) +} - }, [refDiv.current]) - React.useEffect(() => - { - Timeline.scrollPlaybackTimeIntoView(editorState.ref.current) - render(!Playback.global.playing) +function registerHandlers( + div: HTMLDivElement, + canvas: HTMLCanvasElement) +{ + const ctx = canvas.getContext("2d")! - }, [Playback.globalObservable.updateToken]) - - React.useEffect(() => - { - Timeline.refreshTracks(editorState.ref.current) - render(true) - - }, [Project.global.project.tracks]) + const timeline = Global.getStatic().timeline + const project = Global.getStatic().project + const prefs = Global.getStatic().prefs + const playback = Global.getStatic().playback + + const transformMousePos = (canvas: HTMLCanvasElement, ev: MouseEvent) => { + const rect = canvas.getBoundingClientRect() + return { + x: (ev.clientX - rect.left) * timeline.pixelRatio, + y: (ev.clientY - rect.top) * timeline.pixelRatio, + } + } - React.useEffect(() => - { - const refCanvasCurrent = refCanvas.current - if (!refCanvasCurrent) - return + const setCursor = () => { + const action = timeline.mouse.down ? + timeline.mouse.action : + timeline.hover?.action + + canvas.style.cursor = + action === Timeline.MouseAction.DragTime || + action === Timeline.MouseAction.DragRow || + action === Timeline.MouseAction.DragTimeAndRow ? + timeline.mouse.down ? "grabbing" : "grab" : + action === Timeline.MouseAction.StretchTimeStart || + action === Timeline.MouseAction.StretchTimeEnd ? + "col-resize" : + action === Timeline.MouseAction.SelectCursor ? + timeline.cursor.rectMode ? "crosshair" : "text" : + "text" + } - const transformMousePos = (canvas: HTMLCanvasElement, ev: MouseEvent) => - { - const rect = canvas.getBoundingClientRect() - return { - x: (ev.clientX - rect.left) * editorState.ref.current.pixelRatio, - y: (ev.clientY - rect.top) * editorState.ref.current.pixelRatio, - } - } + let needsRedraw = true - const preventDefault = (ev: Event) => ev.preventDefault() + let drawLoopHandle = -1 - const setCursor = (state: Timeline.State) => + const drawLoop = () => { + if (needsRedraw) { - let cursor = "text" - const mouseAction = - state.mouse.down ? state.mouse.action : - state.hoverControl != Timeline.TrackControl.None ? Timeline.MouseAction.DragTrackControl : - state.hover ? state.hover.action : - Timeline.MouseAction.None - - if (state.tracks.some((tr: any) => !!tr.pencil) || - mouseAction == Timeline.MouseAction.Pencil) - cursor = "crosshair" - else if (mouseAction == Timeline.MouseAction.DragTrackControl) - cursor = "crosshair" - else if (mouseAction == Timeline.MouseAction.DragClone) - cursor = "crosshair" - else if (mouseAction == Timeline.MouseAction.DragTime || - mouseAction == Timeline.MouseAction.DragTimeAndRow || - mouseAction == Timeline.MouseAction.DragRow || - mouseAction == Timeline.MouseAction.DragTrackHeader) - cursor = (state.mouse.down ? "grabbing" : "grab") - else if (mouseAction == Timeline.MouseAction.Pan) - cursor = "move" - else if (mouseAction == Timeline.MouseAction.StretchTimeStart || - mouseAction == Timeline.MouseAction.StretchTimeEnd) - cursor = "col-resize" - - refCanvasCurrent.style.cursor = cursor + needsRedraw = false + Timeline.draw(timeline, playback, prefs, ctx) } - const onMouseMove = (ev: MouseEvent) => - { - const state = editorState.ref.current - if (state.mouse.down) - ev.preventDefault() - - const pos = transformMousePos(refCanvasCurrent, ev) - const projectBefore = Project.global.project - const needsRender1 = Timeline.mouseMove(state, pos) - const needsRender2 = Timeline.mouseDrag(state, pos, false) - - if (projectBefore !== Project.global.project) - Project.notifyObservers() + window.requestAnimationFrame(drawLoop) + } - if (needsRender1 || needsRender2) - render(true) + drawLoopHandle = window.requestAnimationFrame(drawLoop) - setCursor(state) - } - - const onMouseDown = (ev: MouseEvent) => - { - if (document.activeElement instanceof HTMLElement) - document.activeElement.blur() + const queueRedraw = () => { + needsRedraw = true + } - ev.preventDefault() + const relayout = () => { + Timeline.layout(timeline, project.root, prefs) + queueRedraw() + } - Dockable.removeEphemerals(Dockable.global.state) - Dockable.notifyObservers() + const onResize = () => { + canvasResize(div, canvas, timeline) + relayout() + } - const state = editorState.ref.current - const pos = transformMousePos(refCanvasCurrent, ev) - const projectBefore = Project.global.project - Timeline.mouseMove(state, pos) - Timeline.mouseDown(state, ev.button != 0) - Timeline.mouseDrag(state, pos, true) + const onMouseMove = (ev: MouseEvent) => { + ev.preventDefault() - if (projectBefore !== Project.global.project) - Project.notifyObservers() + const mouse = transformMousePos(canvas, ev) - render() - setCursor(state) - editorState.commit() - } - - const onMouseUp = (ev: MouseEvent) => - { - const state = editorState.ref.current - if (state.mouse.down) - ev.preventDefault() - - const pos = transformMousePos(refCanvasCurrent, ev) - const projectBefore = Project.global.project - Timeline.mouseUp(state) - - if (projectBefore !== Project.global.project) - { - Project.notifyObservers() - window.dispatchEvent(new Event("timelineRefresh")) - } - - render() - setCursor(state) - editorState.commit() - } - - const onMouseWheel = (ev: WheelEvent) => - { - Timeline.mouseWheel(editorState.ref.current, ev.deltaX, ev.deltaY) - render() - } - - const onKeyDown = (ev: KeyboardEvent) => - { - const isActiveWindow = - Dockable.global.state.activePanel === dockableWindow.panel && - (!document.activeElement || document.activeElement.tagName != "INPUT") - - const projectBefore = Project.global.project - Timeline.keyDown(editorState.ref.current, isActiveWindow, ev.key.toLowerCase()) - - if (projectBefore !== Project.global.project) - Project.notifyObservers() - - render() - editorState.commit() - } - - const onKeyUp = (ev: KeyboardEvent) => - { - Timeline.keyUp(editorState.ref.current, ev.key.toLowerCase()) - } + Timeline.mouseMove(timeline, project.root, mouse.x, mouse.y) - const onRewind = (ev: Event) => + if (Timeline.mouseDrag(timeline, project, playback, prefs)) { - Timeline.rewind(editorState.ref.current) - editorState.commit() - render(true) - } - - const onRefresh = (ev: Event) => - { - Timeline.refreshTracks(editorState.ref.current) - editorState.commit() - render(true) - } - - const onReset = (ev: Event) => - { - const state = editorState.ref.current - Timeline.modeStackPop(state, 0) - Timeline.reset(state) - Timeline.rewind(state) - Timeline.refreshTracks(state) - editorState.commit() - render(true) + Timeline.layout(timeline, project.root, prefs) + Global.refresh() } - refCanvasCurrent.addEventListener("mousedown", onMouseDown) - refCanvasCurrent.addEventListener("wheel", onMouseWheel) - refCanvasCurrent.addEventListener("contextmenu", preventDefault) - - window.addEventListener("mousemove", onMouseMove) - window.addEventListener("mouseup", onMouseUp) - window.addEventListener("keydown", onKeyDown) - window.addEventListener("keyup", onKeyUp) + queueRedraw() + setCursor() + } - window.addEventListener("timelineRewind", onRewind) - window.addEventListener("timelineRefresh", onRefresh) - window.addEventListener("timelineReset", onReset) + const onMouseDown = (ev: MouseEvent) => { + ev.preventDefault() + canvas.focus() - return () => - { - refCanvasCurrent.removeEventListener("mousedown", onMouseDown) - refCanvasCurrent.removeEventListener("wheel", onMouseWheel) - refCanvasCurrent.removeEventListener("contextmenu", preventDefault) - - window.removeEventListener("mousemove", onMouseMove) - window.removeEventListener("mouseup", onMouseUp) - window.removeEventListener("keydown", onKeyDown) - window.removeEventListener("keyup", onKeyUp) - - window.removeEventListener("timelineRewind", onRewind) - window.removeEventListener("timelineRefresh", onRefresh) - window.removeEventListener("timelineReset", onReset) - } + const mouse = transformMousePos(canvas, ev) + + Timeline.mouseMove(timeline, project.root, mouse.x, mouse.y) + Timeline.mouseDown(timeline, project, playback, prefs, ev.button !== 0) + queueRedraw() + Global.refresh() + setCursor() + } - }, [refCanvas.current]) + const onMouseUp = (ev: MouseEvent) => { + ev.preventDefault() + const mouse = transformMousePos(canvas, ev) + + Timeline.mouseMove(timeline, project.root, mouse.x, mouse.y) - const yTrackEnd = - editorState.ref.current.tracks.length == 0 ? - 0 : - editorState.ref.current.tracks[editorState.ref.current.tracks.length - 1].renderRect.y2 + if (Timeline.mouseUp(timeline, project, ev.button !== 0)) + Timeline.layout(timeline, project.root, prefs) - const onAddTrack = () => - { - let proj = Project.global.project - proj = Project.upsertTrack(proj, Project.makeTrackNotes()) - Project.global.project = proj - Project.splitUndoPoint() - Project.addUndoPoint("addTrack") - Project.notifyObservers() + queueRedraw() + Global.refresh() + setCursor() + } + + const onMouseWheel = (ev: WheelEvent) => { + ev.preventDefault() - window.dispatchEvent(new Event("timelineRefresh")) + Timeline.mouseWheel(timeline, ev.deltaX, ev.deltaY) + Timeline.layout(timeline, project.root, prefs) + queueRedraw() + Global.refresh() } - const onTrackSettings = (ev: React.MouseEvent, trackIndex: number) => - { - Popup.global.elem = () => - { - return - - - } - Popup.global.rect = Rect.fromElement(ev.target as HTMLElement) - Popup.notifyObservers() + const onKeyDown = (ev: KeyboardEvent) => { + if (document.activeElement && + document.activeElement.tagName === "INPUT") + return + + Timeline.keyDown(timeline, project, playback, prefs, ev.key.toLowerCase()) + Timeline.layout(timeline, project.root, prefs) + queueRedraw() + Global.refresh() } - const pixelRatio = window.devicePixelRatio || 1 - - return React.useMemo(() => -
- - -
- - +{/*➕*/} - -
-
- , [ - Project.global.project.tracks, - editorState.ref.current.tracks, - editorState.ref.current.trackScroll, - ]) -} - + const onKeyUp = (ev: KeyboardEvent) => { + Timeline.keyUp(timeline, ev.key.toLowerCase()) + Global.refresh() + } -export function sendEventRefresh() -{ - window.dispatchEvent(new Event("timelineRefresh")) -} + const onWindowBlur = () => { + console.log("blur") + Timeline.allKeysUp(timeline) + } + const preventDefault = (ev: MouseEvent) => { + ev.preventDefault() + } -export function sendEventReset() -{ - window.dispatchEvent(new Event("timelineReset")) + onResize() + + Solid.createComputed( + (prevProject) => { + if (Global.get().project.root !== prevProject) + relayout() + }, + project.root) + + window.addEventListener(eventTimelineRedraw, queueRedraw) + window.addEventListener(eventTimelineRelayout, relayout) + window.addEventListener("resize", onResize) + window.addEventListener("mousemove", onMouseMove) + canvas.addEventListener("mousedown", onMouseDown) + window.addEventListener("mouseup", onMouseUp) + canvas.addEventListener("wheel", onMouseWheel) + canvas.addEventListener("contextmenu", preventDefault) + window.addEventListener("blur", onWindowBlur) + window.addEventListener("keydown", onKeyDown) + window.addEventListener("keyup", onKeyUp) + + return () => { + window.cancelAnimationFrame(drawLoopHandle) + window.removeEventListener(eventTimelineRedraw, queueRedraw) + window.removeEventListener(eventTimelineRelayout, relayout) + window.removeEventListener("resize", onResize) + window.removeEventListener("mousemove", onMouseMove) + canvas.removeEventListener("mousedown", onMouseDown) + window.removeEventListener("mouseup", onMouseUp) + canvas.removeEventListener("wheel", onMouseWheel) + canvas.removeEventListener("contextmenu", preventDefault) + window.removeEventListener("blur", onWindowBlur) + window.removeEventListener("keydown", onKeyDown) + window.removeEventListener("keyup", onKeyUp) + } } \ No newline at end of file diff --git a/src/timeline/draw.ts b/src/timeline/draw.ts new file mode 100644 index 0000000..be057e3 --- /dev/null +++ b/src/timeline/draw.ts @@ -0,0 +1,867 @@ +import * as Timeline from "./index.ts" +import * as Playback from "../playback" +import * as Prefs from "../prefs.ts" +import * as Theory from "../theory" +import Rational from "../utils/rational.ts" +import * as CanvasUtils from "../utils/canvasUtils.ts" +import Rect from "../utils/rect.ts" + + +export function draw( + timeline: Timeline.State, + playback: Playback.Manager, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D) +{ + ctx.save() + ctx.translate(0.5, 0.5) + + ctx.fillStyle = "#fff" + ctx.clearRect(0, 0, timeline.renderRect.w, timeline.renderRect.h) + + for (const lane of timeline.layout.lanes) + drawLane(ctx, timeline, playback, prefs, lane) + + if (timeline.cursor.visible) + { + const timeMin = timeline.cursor.time1.min(timeline.cursor.time2) + const timeMax = timeline.cursor.time1.max(timeline.cursor.time2) + drawCursorBeam(timeline, prefs, ctx, timeMin, false) + drawCursorBeam(timeline, prefs, ctx, timeMax, true) + drawCursorRect(timeline, prefs, ctx) + } + + if (playback.playing) + { + drawPlaybackBeam(timeline, prefs, ctx, playback.playTime) + } + + ctx.restore() +} + + +function drawLane( + ctx: CanvasRenderingContext2D, + timeline: Timeline.State, + playback: Playback.Manager, + prefs: Prefs.Prefs, + lane: Timeline.Lane) +{ + if (lane instanceof Timeline.LaneChords) + { + ctx.save() + ctx.beginPath() + ctx.rect( + lane.rect.x, + lane.rect.y, + lane.rect.w, + lane.rect.h) + ctx.clip() + + ctx.translate(lane.rect.x, lane.rect.y) + + drawLaneBkgSolid(timeline, prefs, ctx, lane) + drawLaneBkgOutOfBounds(timeline, prefs, ctx, lane) + drawLaneBkgMeasures(timeline, prefs, ctx, lane, false) + drawLaneBkgMeasures(timeline, prefs, ctx, lane, true) + drawCursorBkg(timeline, prefs, ctx, lane) + drawElements(timeline, playback, prefs, ctx, lane.elements) + + ctx.save() + ctx.globalAlpha = 0.05 + drawLaneBkgMeasures(timeline, prefs, ctx, lane, false) + drawLaneBkgMeasures(timeline, prefs, ctx, lane, true) + ctx.restore() + + drawLaneFrgOutline(timeline, prefs, ctx, lane) + + ctx.restore() + } + + else if (lane instanceof Timeline.LaneNotes) + { + ctx.save() + ctx.beginPath() + ctx.rect( + lane.rect.x, + lane.rect.y, + lane.rect.w, + lane.rect.h) + ctx.clip() + + ctx.translate(lane.rect.x, lane.rect.y) + + drawLaneBkgSolid(timeline, prefs, ctx, lane) + drawLaneBkgOutOfBounds(timeline, prefs, ctx, lane) + drawLaneBkgChordTones(timeline, prefs, ctx, lane) + drawLaneBkgOctaves(timeline, prefs, ctx, lane, false) + drawLaneBkgMeasures(timeline, prefs, ctx, lane, false) + drawLaneBkgOctaves(timeline, prefs, ctx, lane, true) + drawLaneBkgMeasures(timeline, prefs, ctx, lane, true) + drawCursorBkg(timeline, prefs, ctx, lane) + drawElements(timeline, playback, prefs, ctx, lane.elements) + + for (const tuple of lane.tupleIndicators) + { + ctx.strokeStyle = prefs.timeline.octaveLabelColor + ctx.lineWidth = 2 + ctx.beginPath() + ctx.moveTo(tuple.rect.x, tuple.rect.y2 + 2) + ctx.lineTo(tuple.rect.x, tuple.rect.y2 + 5) + ctx.lineTo(tuple.rect.x2, tuple.rect.y2 + 5) + ctx.lineTo(tuple.rect.x2, tuple.rect.y2 + 2) + ctx.stroke() + + ctx.fillStyle = prefs.timeline.octaveLabelColor + ctx.font = Math.floor(timeline.noteRowH - 4) + "px system-ui" + ctx.textAlign = "center" + ctx.textBaseline = "top" + ctx.fillText( + tuple.denominator.toString(), + tuple.rect.xCenter, + tuple.rect.y2 + 8) + } + + drawLaneFrgOutline(timeline, prefs, ctx, lane) + + ctx.restore() + } +} + + +function drawElements( + timeline: Timeline.State, + playback: Playback.Manager, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + elements: Timeline.LayoutElement[]) +{ + for (const element of elements) + { + if (element.id !== undefined && + timeline.selection.has(element.id)) + element.zIndexForHover = 1 + + else if (timeline.hover?.id === element.id) + element.zIndexForHover = 2 + + else + element.zIndexForHover = 0 + + if (element.ghost) + element.zIndexForHover -= 5 + } + + elements.sort((a, b) => + ((a.zIndex ?? 0) + (a.zIndexForHover ?? 0)) - + ((b.zIndex ?? 0) + (b.zIndexForHover ?? 0))) + + for (const element of elements) + { + if (element.kind === "note") + drawNote(timeline, playback, prefs, ctx, element) + + else if (element.kind === "chord") + drawChord(timeline, playback, prefs, ctx, element) + + else if (element.kind === "marker") + drawMarker(timeline, prefs, ctx, element) + } +} + + +function drawNote( + timeline: Timeline.State, + playback: Playback.Manager, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + element: Timeline.LayoutElementNote) +{ + const isSelected = + element.id !== undefined && + timeline.selection.has(element.id) + + const needsClipping = + isSelected || + element.cutStart || + element.cutEnd + + if (needsClipping) + { + ctx.save() + ctx.beginPath() + ctx.roundRect( + element.rect.x, + element.rect.y, + element.rect.w, + element.rect.h, + timeline.noteRowH / 4) + ctx.clip() + } + + const x1 = element.rect.x + (element.cutStart ? -16 : 0) + const x2 = element.rect.x2 + (element.cutEnd ? 16 : 0) + + const key = element.key + const mode = key.scale.metadata?.mode ?? 0 + const fillStyle = CanvasUtils.fillStyleForDegree( + ctx, + key.degreeForMidi(element.note.midiPitch) + mode, + !!element.ghost) + + //ctx.globalAlpha = element.ghost ? 0.5 : 1 + + ctx.fillStyle = fillStyle + ctx.beginPath() + ctx.roundRect( + x1, + element.rect.y, + x2 - x1, + element.rect.h, + timeline.noteRowH / 4) + ctx.fill() + + if (timeline.hover?.id === element.id) + { + ctx.fillStyle = "#fff8" + ctx.fill() + } + + if (playback.playing && + element.note.range.overlapsPoint(playback.playTime)) + { + ctx.fillStyle = + element.ghost ? "#fff2" : + "#fff8" + ctx.fill() + } + + if (isSelected) + { + ctx.strokeStyle = "#fff8" + ctx.lineWidth = 6 + ctx.stroke() + } + + if (needsClipping) + ctx.restore() +} + + +function drawChord( + timeline: Timeline.State, + playback: Playback.Manager, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + element: Timeline.LayoutElementChord) +{ + ctx.save() + ctx.beginPath() + ctx.roundRect( + element.rect.x, + element.rect.y, + element.rect.w, + element.rect.h, + 5) + ctx.clip() + + const x1 = element.rect.x + (element.cutStart ? -16 : 0) + const x2 = element.rect.x2 + (element.cutEnd ? 16 : 0) + + CanvasUtils.drawChord( + ctx, + element.rect, + prefs, + element.chord.chord, + element.key) + + ctx.beginPath() + ctx.roundRect( + x1, + element.rect.y, + x2 - x1, + element.rect.h, + 5) + + if (timeline.hover?.id === element.id) + { + ctx.fillStyle = "#fff8" + ctx.fill() + } + + if (playback.playing && + element.chord.range.overlapsPoint(playback.playTime)) + { + ctx.fillStyle = "#fff8" + ctx.fill() + } + + if (element.id !== undefined && + timeline.selection.has(element.id)) + { + ctx.strokeStyle = "#fff8" + ctx.lineWidth = 6 + ctx.stroke() + } + + ctx.restore() +} + + +function drawMarker( + timeline: Timeline.State, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + element: Timeline.LayoutElementMarker) +{ + ctx.save() + + const color = + element.tempoCh ? prefs.timeline.tempoChangeColor : + element.keyCh ? prefs.timeline.keyChangeColor : + element.meterCh ? prefs.timeline.meterChangeColor : + prefs.timeline.measureLabelColor + + const makeKeyChangeDifferenceText = (chromaPrev: number | undefined, chromaNext: number) => { + if (chromaPrev === undefined) + return "" + + const diff1 = Theory.Utils.mod(chromaNext - chromaPrev, 12) + const diff2 = diff1 - 12 + const diff = Math.abs(diff1) < Math.abs(diff2) ? diff1 : diff2 + return ` (${diff >= 0 ? "+" : ""}${diff})` + } + + const text = + element.tempoCh ? + element.tempoCh.bpm.toString() + " bpm" : + element.keyCh ? + element.keyCh.key.toString() + + makeKeyChangeDifferenceText( + element.keyChPrev?.key.tonic.chroma, + element.keyCh.key.tonic.chroma) : + element.meterCh ? + element.meterCh.meter.toString() : + "" + + ctx.fillStyle = color + ctx.strokeStyle = prefs.timeline.bkgColor + ctx.font = `bold ${element.rect.h * 0.85}px ${prefs.timeline.fontNameMarker}` + ctx.textAlign = "left" + ctx.textBaseline = "middle" + ctx.lineWidth = 8 + + if (element.rect.xCenter >= 0) + { + ctx.strokeText( + text, + element.rect.x2 + 8, + element.rect.yCenter) + ctx.fillText( + text, + element.rect.x2 + 8, + element.rect.yCenter) + } + + ctx.beginPath() + ctx.moveTo(element.rect.x, element.rect.y) + ctx.lineTo(element.rect.x2, element.rect.y) + ctx.lineTo(element.rect.x2, element.rect.y + element.rect.h * 0.65) + ctx.lineTo(element.rect.xCenter, element.rect.y2) + ctx.lineTo(element.rect.x, element.rect.y + element.rect.h * 0.65) + ctx.lineTo(element.rect.x, element.rect.y) + ctx.clip() + ctx.fill() + + if (timeline.hover?.id === element.id) + { + ctx.fillStyle = "#fff8" + ctx.fill() + } + + if (element.id !== undefined && + timeline.selection.has(element.id)) + { + ctx.strokeStyle = "#fff8" + ctx.lineWidth = 6 + ctx.stroke() + } + + ctx.restore() +} + + +export function drawLaneBkgCenterStroke( + timeline: Timeline.State, + ctx: CanvasRenderingContext2D, + lane: Timeline.Lane) +{ + const yCenter = Math.floor(lane.rect.y + lane.rect.h / 2) + 0.5 + ctx.strokeStyle = "#fff" + ctx.lineWidth = 2 + ctx.beginPath() + ctx.moveTo(lane.rect.x, yCenter) + ctx.lineTo(lane.rect.x + lane.rect.w, yCenter) + ctx.stroke() +} + + +export function drawLaneBkgSolid( + timeline: Timeline.State, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + lane: Timeline.Lane) +{ + if (timeline.layout.measures.length > 20) + { + ctx.fillStyle = prefs.timeline.bkgColor + ctx.fillRect( + 0, + 0, + lane.rect.w, + lane.rect.h) + + return + } + + for (const measure of timeline.layout.measures) + { + const x1 = Math.floor(Timeline.xAtTime(timeline, measure.time1)) + const x2 = Math.floor(Timeline.xAtTime(timeline, measure.time2)) + + ctx.fillStyle = + measure.num % 2 === 0 ? prefs.timeline.bkgColor : + prefs.timeline.bkgAlternateMeasureColor + + ctx.fillRect( + x1, + 0, + x2 - x1, + lane.rect.h) + } +} + + +export function drawLaneBkgChordTones( + timeline: Timeline.State, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + lane: Timeline.Lane) +{ + if (!timeline.layout.chordTonesVisible) + return + + const rowAtTop = Timeline.rowAtY(timeline, lane, 0) + const rowAtBottom = Timeline.rowAtY(timeline, lane, lane.rect.h) + + const octaveAtTop = Math.ceil(rowAtTop / 7) + 1 + const octaveAtBottom = Math.floor(rowAtBottom / 7) - 1 + + const chordToneH = timeline.noteRowH / 3 + + for (const chordRegion of timeline.layout.chordRegions) + { + const key = chordRegion.key + const scaleLength = key.scale.chromas.length + + for (const tone of chordRegion.tones) + { + const mode = key.scale.metadata?.mode ?? 0 + ctx.fillStyle = CanvasUtils.fillStyleForDegree( + ctx, + tone.degree + mode, + true) + + for (let i = octaveAtBottom; i <= octaveAtTop; i++) + { + const y = Math.floor( + Timeline.yForRow(timeline, lane, tone.row + (5 + i) * scaleLength)) + + ctx.fillRect( + chordRegion.x1, + y + timeline.noteRowH / 2 - chordToneH / 2, + chordRegion.x2 - chordRegion.x1, + chordToneH) + } + } + } +} + + +export function drawLaneFrgOutline( + timeline: Timeline.State, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + lane: Timeline.Lane) +{ + ctx.strokeStyle = prefs.timeline.measureColor + ctx.lineWidth = 1 + ctx.beginPath() + ctx.moveTo(0, 0) + ctx.lineTo(lane.rect.w, 0) + ctx.moveTo(0, lane.rect.h) + ctx.lineTo(lane.rect.w, lane.rect.h) + ctx.stroke() +} + + +export function drawLaneBkgOutOfBounds( + timeline: Timeline.State, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + lane: Timeline.Lane) +{ + ctx.fillStyle = CanvasUtils.fillStyleForOutOfBounds(ctx, prefs) + + const leftX2 = Math.floor(Timeline.xAtTime(timeline, timeline.layout.inBoundsRange.start)) + + if (leftX2 > 0) + { + ctx.fillRect( + 0, + 0, + leftX2, + lane.rect.h) + } + + const rightX1 = Math.floor(Timeline.xAtTime(timeline, timeline.layout.inBoundsRange.end)) + + if (rightX1 < lane.rect.w) + { + ctx.fillRect( + rightX1, + 0, + lane.rect.w - rightX1, + lane.rect.h) + } +} + + +export function drawLaneBkgMeasures( + timeline: Timeline.State, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + lane: Timeline.Lane, + mainLinePass: boolean) +{ + for (const measure of timeline.layout.measures) + { + const x1 = Math.floor(Timeline.xAtTime(timeline, measure.time1)) + const x2 = Math.floor(Timeline.xAtTime(timeline, measure.time2)) + + const submeasureSize = + Timeline.xAtTime(timeline, new Rational(1, measure.denominator)) - + Timeline.xAtTime(timeline, new Rational(0)) + + if (mainLinePass) + { + ctx.strokeStyle = prefs.timeline.measureColor + ctx.lineWidth = 2 + ctx.beginPath() + ctx.moveTo(x1 + 0.5, 0) + ctx.lineTo(x1 + 0.5, lane.rect.h) + ctx.stroke() + } + + if (!mainLinePass && + submeasureSize > 8) + { + ctx.strokeStyle = prefs.timeline.submeasureColor + ctx.lineWidth = 1 + ctx.beginPath() + + for (let n = 1; n < measure.numerator; n++) + { + const submeasureX = x1 + Math.round(submeasureSize * n) + if (submeasureX >= x2) + break + + ctx.moveTo(submeasureX, 0) + ctx.lineTo(submeasureX, lane.rect.h) + } + + ctx.stroke() + } + } + + if (mainLinePass) + { + for (const marker of timeline.layout.markers) + { + const x = Math.floor(Timeline.xAtTime(timeline, marker.time)) + + const color = + marker.tempoCh ? prefs.timeline.tempoChangeColor : + marker.keyCh ? prefs.timeline.keyChangeColor : + marker.meterCh ? prefs.timeline.meterChangeColor : + prefs.timeline.measureColor + + ctx.strokeStyle = color + ctx.lineWidth = 2 + ctx.beginPath() + ctx.moveTo(x + 0.5, 0) + ctx.lineTo(x + 0.5, lane.rect.h) + ctx.stroke() + } + } +} + + +export function drawLaneBkgOctaves( + timeline: Timeline.State, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + lane: Timeline.Lane, + mainLinePass: boolean) +{ + const rowAtTop = Timeline.rowAtY(timeline, lane, 0) + const rowAtBottom = Timeline.rowAtY(timeline, lane, lane.rect.h) + + const octaveAtTop = Math.ceil(rowAtTop / 7) + 1 + const octaveAtBottom = Math.floor(rowAtBottom / 7) - 1 + + ctx.fillStyle = prefs.timeline.octaveLabelColor + ctx.font = Math.floor(timeline.noteRowH - 4) + "px system-ui" + ctx.textAlign = "left" + ctx.textBaseline = "bottom" + + for (const keyRegion of timeline.layout.keyRegions) + { + const key = keyRegion.keyCh1.key + const tonicRowOffset = Theory.Utils.chromaToDegreeInCMajor(key.tonic.chroma) + const scaleLength = key.scale.chromas.length + + let needsOctaveLabels = true + let drewOctaveLabels = false + + for (const measure of timeline.layout.measures) + { + if (measure.time1.compare(keyRegion.keyCh2.range.start) >= 0) + continue + + if (measure.time2.compare(keyRegion.keyCh1.range.start) <= 0) + continue + + const time1 = measure.time1.max(keyRegion.keyCh1.range.start) + const time2 = measure.time2.min(keyRegion.keyCh2.range.start) + + const x1 = Math.floor(Timeline.xAtTime(timeline, time1)) + const x2 = Math.floor(Timeline.xAtTime(timeline, time2)) + + for (let i = octaveAtBottom; i <= octaveAtTop; i++) + { + const y = Math.floor( + Timeline.yForRow(timeline, lane, tonicRowOffset + i * scaleLength) + + timeline.noteRowH) + + if (mainLinePass) + { + const labelX = Math.max(x1 + 5, 5) + if (needsOctaveLabels && labelX + 30 < x2) + { + for (let j = 0; j < scaleLength; j += 1) + { + const pitchName = key.namedPitches[j] + const chroma = key.scale.chromas[j] + key.tonic.chroma + ((i + 5) * 12) + const octave = Math.floor(chroma / 12) + ctx.fillText( + pitchName.str, + labelX, + y - 1 - j * timeline.noteRowH) + + if (j === 0) + { + const letterMeasure = ctx.measureText(pitchName.str) + ctx.fillText( + octave.toString(), + labelX + Math.max(14, letterMeasure.width), + y - 1 - j * timeline.noteRowH) + } + } + + drewOctaveLabels = true + } + + ctx.strokeStyle = prefs.timeline.measureColor + ctx.beginPath() + ctx.moveTo(x1, y) + ctx.lineTo(x2, y) + ctx.moveTo(x1, y + 1) + ctx.lineTo(x2, y + 1) + ctx.stroke() + } + + if (!mainLinePass) + { + ctx.strokeStyle = prefs.timeline.submeasureColor + ctx.beginPath() + for (let j = 1; j < scaleLength; j += 1) + { + const ySuboctave = Math.floor( + Timeline.yForRow(timeline, lane, tonicRowOffset + i * scaleLength + j) + timeline.noteRowH) + + ctx.moveTo(x1, ySuboctave) + ctx.lineTo(x2, ySuboctave) + } + ctx.stroke() + } + } + + if (drewOctaveLabels) + needsOctaveLabels = false + } + } +} + + +function drawCursorBeam( + timeline: Timeline.State, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + time: Rational, + tipOffsetSide: boolean) +{ + if (timeline.cursor.rectMode) + return + + const laneIndexMin = Timeline.cursorGetLaneIndexMin(timeline) + const laneIndexMax = Timeline.cursorGetLaneIndexMax(timeline) + + const laneMin = timeline.layout.lanes[laneIndexMin] + const laneMax = timeline.layout.lanes[laneIndexMax] + + if (!laneMin || !laneMax) + return + + const x = 0.5 + Math.floor(Timeline.xAtTime(timeline, time)) + + ctx.strokeStyle = prefs.timeline.selectionCursorColor + ctx.fillStyle = prefs.timeline.selectionCursorColor + ctx.lineCap = "square" + ctx.lineWidth = 2 + + const headYSize = 10 + const headXSize = headYSize * (tipOffsetSide ? -1 : 1) + + const y1 = Math.floor(laneMin.rect.y) + const y2 = Math.floor(laneMax.rect.y2) + + ctx.beginPath() + ctx.moveTo(x, y1 + headYSize) + ctx.lineTo(x + headXSize, y1) + ctx.lineTo(x, y1) + ctx.fill() + + ctx.beginPath() + ctx.moveTo(x, y2 - headYSize) + ctx.lineTo(x + headXSize, y2) + ctx.lineTo(x, y2) + ctx.fill() + + ctx.beginPath() + ctx.moveTo(x, y1 + 1) + ctx.lineTo(x, y2 - 1) + ctx.stroke() +} + + +function drawCursorRect( + timeline: Timeline.State, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D) +{ + if (!timeline.cursor.rectMode) + return + + const laneIndexMin = Timeline.cursorGetLaneIndexMin(timeline) + const laneIndexMax = Timeline.cursorGetLaneIndexMax(timeline) + + const laneMin = timeline.layout.lanes[laneIndexMin] + const laneMax = timeline.layout.lanes[laneIndexMax] + + if (!laneMin || !laneMax) + return + + const timeMin = timeline.cursor.time1.min(timeline.cursor.time2) + const timeMax = timeline.cursor.time1.max(timeline.cursor.time2) + const x1 = 0.5 + Timeline.xAtTime(timeline, timeMin) + const x2 = 0.5 + Timeline.xAtTime(timeline, timeMax) + + ctx.strokeStyle = prefs.timeline.selectionCursorColor + ctx.fillStyle = prefs.timeline.selectionCursorColor + ctx.lineCap = "square" + ctx.lineWidth = 2 + + const yMin = Math.min(timeline.cursor.verticalRegion.y1, timeline.cursor.verticalRegion.y2) + const yMax = Math.max(timeline.cursor.verticalRegion.y1, timeline.cursor.verticalRegion.y2) + const y1 = Math.floor(laneMin.rect.y + yMin) + const y2 = Math.floor(laneMax.rect.y + yMax) + + ctx.strokeRect(x1, y1, x2 - x1, y2 - y1) +} + + +function drawCursorBkg( + timeline: Timeline.State, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + lane: Timeline.Lane) +{ + if (!timeline.cursor.visible) + return + + const timeMin = timeline.cursor.time1.min(timeline.cursor.time2) + const timeMax = timeline.cursor.time1.max(timeline.cursor.time2) + const laneIndexMin = Timeline.cursorGetLaneIndexMin(timeline) + const laneIndexMax = Timeline.cursorGetLaneIndexMax(timeline) + + if (laneIndexMin > lane.laneIndex || + laneIndexMax < lane.laneIndex) + return + + const yMin = Math.min(timeline.cursor.verticalRegion.y1, timeline.cursor.verticalRegion.y2) + const yMax = Math.max(timeline.cursor.verticalRegion.y1, timeline.cursor.verticalRegion.y2) + + const y1 = timeline.cursor.rectMode ? + Math.floor(yMin) : + 0 + + const y2 = timeline.cursor.rectMode ? + Math.floor(yMax) : + Math.floor(lane.rect.h) + + const x1 = Timeline.xAtTime(timeline, timeMin) + const x2 = Timeline.xAtTime(timeline, timeMax) + + ctx.fillStyle = prefs.timeline.selectionBkgColor + ctx.fillRect(x1, y1, x2 - x1, y2 - y1) +} + + +function drawPlaybackBeam( + timeline: Timeline.State, + prefs: Prefs.Prefs, + ctx: CanvasRenderingContext2D, + time: Rational) +{ + const laneMin = timeline.layout.lanes[0] + const laneMax = timeline.layout.lanes[timeline.layout.lanes.length - 1] + + if (!laneMin || !laneMax) + return + + const x = 0.5 + Math.floor(Timeline.xAtTime(timeline, time)) + + ctx.strokeStyle = prefs.timeline.playbackCursorColor + ctx.fillStyle = prefs.timeline.playbackCursorColor + ctx.lineCap = "square" + ctx.lineWidth = 2 + + const y1 = Math.floor(laneMin.rect.y) + const y2 = Math.floor(laneMax.rect.y2) + + ctx.beginPath() + ctx.moveTo(x, y1 + 1) + ctx.lineTo(x, y2 - 1) + ctx.stroke() +} \ No newline at end of file diff --git a/src/timeline/index.ts b/src/timeline/index.ts index 924bf5f..97c3c87 100644 --- a/src/timeline/index.ts +++ b/src/timeline/index.ts @@ -1,18 +1,14 @@ -export * from "./state" -export * from "./state_keyDown" -export * from "./state_keyUp" -export * from "./state_mouseMove" -export * from "./state_mouseDown" -export * from "./state_mouseUp" -export * from "./state_mouseDrag" -export * from "./state_mouseWheel" -export * from "./state_render" -export * from "./track" -export * from "./trackKeyChanges" -export * from "./trackMeterChanges" -export * from "./trackNoteBlocks" -export * from "./trackChords" -export * from "./trackNotes" -export * from "./trackNoteVolumes" -export * from "./trackNoteVelocities" -export * from "./Element" \ No newline at end of file +export * from "./Element.tsx" +export * from "./timeline.ts" +export * from "./layout.ts" +export * from "./lane.ts" +export * from "./lane_notes.ts" +export * from "./lane_chords.ts" +export * from "./mouse_move.ts" +export * from "./mouse_down.ts" +export * from "./mouse_drag.ts" +export * from "./mouse_up.ts" +export * from "./mouse_wheel.ts" +export * from "./key_down.ts" +export * from "./key_up.ts" +export * from "./draw.ts" \ No newline at end of file diff --git a/src/timeline/key_down.ts b/src/timeline/key_down.ts new file mode 100644 index 0000000..7839b19 --- /dev/null +++ b/src/timeline/key_down.ts @@ -0,0 +1,515 @@ +import * as Project from "../project" +import * as Timeline from "./index.ts" +import * as Playback from "../playback" +import * as Prefs from "../prefs.ts" +import * as Theory from "../theory" +import Rational from "../utils/rational.ts" +import Range from "../utils/range.ts" + + +export function keyDown( + timeline: Timeline.State, + project: Project.Mutable, + playback: Playback.Manager, + prefs: Prefs.Prefs, + key: string) +{ + timeline.keysDown.add(key) + + switch (key) + { + case "escape": + { + handleEscape(timeline, project.root) + break + } + + case "enter": + { + handleEnter(timeline, project) + break + } + + case "delete": + { + handleDelete(timeline, project) + break + } + + case "backspace": + { + handleBackspace(timeline, project) + break + } + + case "arrowright": + case "arrowleft": + { + handleLeftRight(timeline, project, prefs, key === "arrowleft") + break + } + + case "arrowup": + case "arrowdown": + { + handleUpDown(timeline, project, playback, prefs, key === "arrowup", false) + break + } + + case ".": + case ">": + case ",": + case "<": + { + handleUpDown(timeline, project, playback, prefs, key === "." || key === ">", true) + break + } + + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + { + const degree = key.charCodeAt(0) - "1".charCodeAt(0) + handleInsertByDegree(timeline, project, playback, prefs, degree) + break + } + + case "h": + { + handleLengthChange(timeline, project, prefs, 0) + break + } + + case "j": + { + handleLengthChange(timeline, project, prefs, 1) + break + } + + case "k": + { + handleLengthChange(timeline, project, prefs, 2) + break + } + + case "l": + { + handleLengthChange(timeline, project, prefs, 3) + break + } + + case ";": + case ":": + { + handleLengthChange(timeline, project, prefs, 4) + break + } + } +} + + +function modifySelectedElems( + timeline: Timeline.State, + project: Project.Mutable, + func: (elem: Project.Element) => Project.Element) +{ + for (const id of timeline.selection) + { + const elem = project.root.elems.get(id) + if (!elem) + continue + + const newElem = func(elem) + if (newElem === elem) + continue + + project.root = Project.upsertElement(project.root, newElem) + } + + project.root = Project.withRefreshedRange(project.root) +} + + +function handleEscape( + timeline: Timeline.State, + project: Project.ImmutableRoot) +{ + /*if (Playback.global.playing) + { + Playback.setStartTime(Project.global.project.range.start) + Playback.setPlaying(true) + } + else*/ + { + Timeline.rewind(timeline, project) + } +} + + +function handleEnter( + timeline: Timeline.State, + project: Project.Mutable) +{ + if (timeline.cursor.visible && + timeline.selection.size !== 0) + { + timeline.cursor.visible = false + return + } + + timeline.cursor.visible = true + + const range = Timeline.selectionRange(timeline, project.root) + if (range) + { + Timeline.cursorSetTime(timeline, range.end, range.end) + //Timeline.cursorSetTrack(state, trackIndex, trackIndex) + Timeline.scrollTimeIntoView(timeline, range.end) + } + else + { + const time = Rational.max( + timeline.cursor.time1, + timeline.cursor.time2) + + Timeline.cursorSetTime(timeline, time, time) + Timeline.scrollTimeIntoView(timeline, time) + } + + Timeline.keyHandlePendingFinish(timeline, project) + Timeline.selectionClear(timeline) +} + + +function handleDelete( + state: Timeline.State, + project: Project.Mutable) +{ + Timeline.deleteElems(state, project, state.selection) +} + + +function handleBackspace( + state: Timeline.State, + project: Project.Mutable) +{ + if (!state.cursor.visible) + { + Timeline.deleteElems(state, project, state.selection) + return + } + + const lane1 = Math.min(state.cursor.laneIndex1, state.cursor.laneIndex2) + const lane2 = Math.max(state.cursor.laneIndex1, state.cursor.laneIndex2) + + if (state.cursor.time1.compare(state.cursor.time2) === 0) + { + const time = state.cursor.time1.min(state.cursor.time2) + const prevAnchor = Timeline.findPreviousAnchor(state, project.root, time, lane1, lane2) + const range = new Range(prevAnchor, time, false, false) + Timeline.deleteRange(state, project, range, lane1, lane2) + + state.cursor.visible = true + Timeline.cursorSetTime(state, prevAnchor, prevAnchor) + Timeline.scrollTimeIntoView(state, prevAnchor) + } + else + { + const time1 = state.cursor.time1.min(state.cursor.time2) + const time2 = state.cursor.time1.max(state.cursor.time2) + const range = new Range(time1, time2, false, false) + Timeline.deleteRange(state, project, range, lane1, lane2) + + state.cursor.visible = true + Timeline.cursorSetTime(state, time1, time1) + Timeline.scrollTimeIntoView(state, time1) + } + + project.root = Project.withRefreshedRange(project.root) +} + + +function handleLeftRight( + timeline: Timeline.State, + project: Project.Mutable, + prefs: Prefs.Prefs, + isLeft: boolean) +{ + const keyFast = timeline.keysDown.has(prefs.timeline.keyDisplaceFast) + const keyCursor2 = timeline.keysDown.has(prefs.timeline.keyDisplaceCursor2) + const keyStretch = timeline.keysDown.has(prefs.timeline.keyDisplaceStretch) + + + /*if (Playback.global.playing) + { + const timeDelta = state.timeSnap.multiplyByFloat( + (keyFast ? 64 : 16) * (isLeft ? -1 : 1)) + + Playback.setStartTime(Playback.global.playTime.add(timeDelta)) + Playback.setPlaying(true) + } + else*/ + if (timeline.cursor.visible && + (timeline.selection.size === 0 || keyCursor2)) + { + const timeDelta = timeline.timeSnap.multiplyByFloat( + (keyFast ? 16 : 1) * (isLeft ? -1 : 1)) + + Timeline.keyHandlePendingFinish(timeline, project) + + if (keyCursor2) + { + const newTime = timeline.cursor.time2.add(timeDelta) + Timeline.cursorSetTime(timeline, null, newTime) + Timeline.selectionClear(timeline) + Timeline.selectionAddAtCursor(timeline, project.root) + Timeline.scrollTimeIntoView(timeline, newTime) + //Playback.setStartTime(newTime) + } + else + { + const timeMin = timeline.cursor.time1.min(timeline.cursor.time2) + const timeMax = timeline.cursor.time1.max(timeline.cursor.time2) + + const newTime = (isLeft ? timeMin : timeMax).add(timeDelta) + + Timeline.cursorSetTime(timeline, newTime, newTime) + Timeline.scrollTimeIntoView(timeline, newTime) + //Playback.setStartTime(newTime) + } + } + else + { + const timeDelta = timeline.timeSnap.multiplyByFloat( + (keyFast ? 16 : 1) * (isLeft ? -1 : 1)) + + const selectionRange = Timeline.selectionRange(timeline, project.root) + + let playedPreview = false + modifySelectedElems(timeline, project, (elem) => { + if (elem.type == "track") + return elem + + let newRange = elem.range + + if (keyStretch && selectionRange) + { + const absRange = Project.getAbsoluteRange( + project.root, + elem.parentId, + elem.range) + + newRange = Project.getRelativeRange( + project.root, + elem.parentId, + absRange.stretch( + timeDelta, + selectionRange.start, + selectionRange.end)) + } + else + { + newRange = elem.range.displace(timeDelta) + } + + if (!elem.range.duration.isZero() && newRange.duration.isZero()) + return elem + + if (!playedPreview) + { + if (elem.type == "note") + { + playedPreview = true + timeline.insertion.nearMidiPitch = elem.midiPitch + timeline.insertion.duration = newRange.duration + } + else if (elem.type == "chord") + { + playedPreview = true + timeline.insertion.duration = newRange.duration + } + } + + return Project.elemModify(elem, { + range: newRange + }) + }) + + const range = + Timeline.selectionRange(timeline, project.root) || + new Range(new Rational(0), new Rational(0)) + + const newTime = (isLeft && !keyStretch ? range.start : range.end) + timeline.cursor.visible = false + Timeline.cursorSetTime(timeline, newTime, newTime) + Timeline.scrollTimeIntoView(timeline, newTime) + + timeline.needsKeyFinish = true + } +} + + +function handleUpDown( + timeline: Timeline.State, + project: Project.Mutable, + playback: Playback.Manager, + prefs: Prefs.Prefs, + isUp: boolean, + isChromatic: boolean) +{ + const keyFast = timeline.keysDown.has(prefs.timeline.keyDisplaceFast) + const keyCursor2 = timeline.keysDown.has(prefs.timeline.keyDisplaceCursor2) + const keyChromatic = timeline.keysDown.has(prefs.timeline.keyDisplaceChromatically) + + + if (!isChromatic && + timeline.cursor.visible && + (timeline.selection.size === 0 || keyCursor2)) + { + const trackDelta = (isUp ? -1 : 1) + + Timeline.keyHandlePendingFinish(timeline, project) + + if (keyCursor2) + { + const newTrack = timeline.cursor.laneIndex2 + trackDelta + Timeline.cursorSetLaneIndex(timeline, null, newTrack) + Timeline.selectionClear(timeline) + Timeline.selectionAddAtCursor(timeline, project.root) + } + else + { + const trackMin = Math.min(timeline.cursor.laneIndex1, timeline.cursor.laneIndex2) + const trackMax = Math.max(timeline.cursor.laneIndex1, timeline.cursor.laneIndex2) + + const newTrack = (isUp ? trackMin : trackMax) + trackDelta + Timeline.cursorSetLaneIndex(timeline, newTrack, newTrack) + } + } + else + { + const pitchDelta = (keyFast ? 12 : (keyChromatic || isChromatic ? 1 : 0)) * (isUp ? 1 : -1) + const degreeDelta = (keyFast || isChromatic ? 0 : 1) * (isUp ? 1 : -1) + + let playedPreview = false + modifySelectedElems(timeline, project, (elem) => { + if (elem.type === "note") + { + const track = Project.parentTrackFor(project.root, elem.parentId) + const key = Project.keyAt(project.root, elem.range.start) + const degree = key.octavedDegreeForMidi(elem.midiPitch) + const newDegree = degree + degreeDelta + const newPitch = pitchDelta != 0 ? + elem.midiPitch + pitchDelta : + key.midiForDegree(degreeDelta >= 0 ? Math.floor(newDegree) : Math.ceil(newDegree)) + + if (!playedPreview) + { + playedPreview = true + playback.playNotePreview(project.root, track.id, newPitch) + timeline.insertion.nearMidiPitch = newPitch + timeline.insertion.duration = elem.range.duration + } + + return Project.elemModify(elem, { midiPitch: newPitch }) + } + else if (elem.type === "chord") + { + const track = Project.parentTrackFor(project.root, elem.parentId) + const key = Project.keyAt(project.root, elem.range.start) + const degree = key.octavedDegreeForMidi(elem.chord.rootChroma) + /*const newDegree = degree + degreeDelta + const newRoot = pitchDelta != 0 ? + elem.chord.rootChroma + pitchDelta : + key.midiForDegree(degreeDelta >= 0 ? Math.floor(newDegree) : Math.ceil(newDegree))*/ + const newDegree = degreeDelta >= 0 ? + Math.floor(degree + degreeDelta) : + Math.ceil(degree + degreeDelta) + const newRoot = + key.chromaForDegree(newDegree) + + const newChord = elem.chord.withRoot(newRoot, newDegree) + console.log(newChord) + + if (!playedPreview) + { + playedPreview = true + playback.playChordPreview(project.root, track.id, newChord) + timeline.insertion.duration = elem.range.duration + } + + return Project.elemModify(elem, { chord: newChord }) + } + else + { + return elem + } + }) + + timeline.cursor.visible = false + timeline.needsKeyFinish = true + } +} + + +function handleInsertByDegree( + timeline: Timeline.State, + project: Project.Mutable, + playback: Playback.Manager, + prefs: Prefs.Prefs, + degree: number) +{ + const time = timeline.cursor.time1.min(timeline.cursor.time2) + const lane = timeline.layout.lanes[timeline.cursor.laneIndex1] + lane.insertByDegree(timeline, project, playback, prefs, time, degree) +} + + +function handleLengthChange( + timeline: Timeline.State, + project: Project.Mutable, + prefs: Prefs.Prefs, + lengthIndex: number) +{ + const lengths = [ + new Rational(1, 16), + new Rational(1, 8), + new Rational(1, 4), + new Rational(1, 2), + new Rational(1, 1), + ] + + if (lengthIndex < 0 || + lengthIndex >= lengths.length) + return + + const length = lengths[lengthIndex] + + modifySelectedElems(timeline, project, (elem) => { + if (elem.type === "note" || + elem.type === "chord") + { + timeline.insertion.duration = length + + const newRange = Range.fromStartDuration(elem.range.start, length) + return Project.elemModify(elem, { range: newRange }) + } + else + return elem + }) + + const range = + Timeline.selectionRange(timeline, project.root) || + new Range(new Rational(0), new Rational(0)) + + const newTime = range.end + timeline.cursor.visible = false + Timeline.cursorSetTime(timeline, newTime, newTime) + Timeline.scrollTimeIntoView(timeline, newTime) + + timeline.needsKeyFinish = true +} \ No newline at end of file diff --git a/src/timeline/key_up.ts b/src/timeline/key_up.ts new file mode 100644 index 0000000..dba6b78 --- /dev/null +++ b/src/timeline/key_up.ts @@ -0,0 +1,16 @@ +import * as Timeline from "./index.ts" + + +export function keyUp( + timeline: Timeline.State, + key: string) +{ + timeline.keysDown.delete(key) +} + + +export function allKeysUp( + timeline: Timeline.State) +{ + timeline.keysDown.clear() +} \ No newline at end of file diff --git a/src/timeline/lane.ts b/src/timeline/lane.ts new file mode 100644 index 0000000..1ae89b7 --- /dev/null +++ b/src/timeline/lane.ts @@ -0,0 +1,104 @@ +import * as Project from "../project" +import * as Timeline from "./index.ts" +import * as Playback from "../playback" +import * as Prefs from "../prefs.ts" +import * as Theory from "../theory/index.ts" +import Rect from "../utils/rect.ts" +import Range from "../utils/range.ts" +import Rational from "../utils/rational.ts" + + +export class Lane +{ + rect: Rect = Rect.fromVertices(0, 0, 0, 0) + + laneIndex: number = -1 + elements: Timeline.LayoutElement[] = [] + + + constructor() + { + + } + + + add(elem: Timeline.LayoutElement) + { + this.elements.push(elem) + } + + + refreshLayout( + timeline: Timeline.State, + project: Project.ImmutableRoot, + prefs: Prefs.Prefs) + { + + } + + + *iterElementsForSelection( + timeline: Timeline.State, + project: Project.ImmutableRoot, + range: Range, + verticalRegion?: Timeline.VerticalRegion) + : Generator + { + + } + + + click( + timeline: Timeline.State, + project: Project.ImmutableRoot, + playback: Playback.Manager, + element: Timeline.LayoutElement) + { + + } + + + findPreviousAnchor( + timeline: Timeline.State, + project: Project.ImmutableRoot, + time: Rational) + : Rational | null + { + return null + } + + + allowsRectSelect(): boolean + { + return false + } + + + deleteRange( + timeline: Timeline.State, + project: Project.Mutable, + range: Range) + { + + } + + + insertByDegree( + timeline: Timeline.State, + project: Project.Mutable, + playback: Playback.Manager, + prefs: Prefs.Prefs, + time: Rational, + degree: number) + { + + } + + + rowAtY( + timeline: Timeline.State, + y: number) + { + return 0 + } +} \ No newline at end of file diff --git a/src/timeline/lane_chords.ts b/src/timeline/lane_chords.ts new file mode 100644 index 0000000..fd97203 --- /dev/null +++ b/src/timeline/lane_chords.ts @@ -0,0 +1,272 @@ +import * as Project from "../project" +import * as Theory from "../theory" +import * as Timeline from "./index.ts" +import * as Playback from "../playback" +import * as Prefs from "../prefs.ts" +import Rect from "../utils/rect.ts" +import Range from "../utils/range.ts" +import Rational from "../utils/rational.ts" + + +export class LaneChords extends Timeline.Lane +{ + override refreshLayout( + timeline: Timeline.State, + project: Project.ImmutableRoot, + prefs: Prefs.Prefs) + { + this.elements = [] + + for (const [chord, keyChPair] of iterChordsAndKeyChanges(timeline, project)) + { + const key = keyChPair.keyCh1.key + + const [rect, cutStart, cutEnd] = rectForChord( + timeline, + this, + chord.range, + keyChPair.x1, + keyChPair.x2) + + if (!cutStart) + { + this.add({ + kind: "hidden", + id: chord.id, + trackId: project.chordTrackId, + laneIndex: this.laneIndex, + action: Timeline.MouseAction.StretchTimeStart, + rect: rect.withX1(rect.x1 - prefs.timeline.hoverOuterStretchWidth), + }) + } + + if (!cutEnd) + { + this.add({ + kind: "hidden", + id: chord.id, + trackId: project.chordTrackId, + laneIndex: this.laneIndex, + action: Timeline.MouseAction.StretchTimeEnd, + rect: rect.withX2(rect.x2 + prefs.timeline.hoverOuterStretchWidth), + }) + } + + if (rect.w > prefs.timeline.hoverInnerStretchWidth * 4) + { + if (!cutStart) + { + this.add({ + kind: "hidden", + id: chord.id, + trackId: project.chordTrackId, + laneIndex: this.laneIndex, + action: Timeline.MouseAction.StretchTimeStart, + rect: rect.withX2(rect.x1 + prefs.timeline.hoverInnerStretchWidth), + priority: 2, + }) + } + + if (!cutEnd) + { + this.add({ + kind: "hidden", + id: chord.id, + trackId: project.chordTrackId, + laneIndex: this.laneIndex, + action: Timeline.MouseAction.StretchTimeEnd, + rect: rect.withX1(rect.x2 - prefs.timeline.hoverInnerStretchWidth), + priority: 2, + }) + } + } + + this.add({ + kind: "chord", + id: chord.id, + trackId: project.chordTrackId, + laneIndex: this.laneIndex, + action: Timeline.MouseAction.DragTime, + rect, + cutStart, + cutEnd, + priority: 1, + chord: chord, + key: key, + }) + } + } + + + override *iterElementsForSelection( + timeline: Timeline.State, + project: Project.ImmutableRoot, + range: Range, + verticalRegion?: Timeline.VerticalRegion) + : Generator + { + for (const chord of iterChords(timeline, project, range)) + yield chord.id + } + + + override findPreviousAnchor( + timeline: Timeline.State, + project: Project.ImmutableRoot, + time: Rational) + : Rational | null + { + const list = project.lists.get(project.chordTrackId) + if (!list) + return null + + const anchor = list.findPreviousDeletionAnchor(time) + return anchor + } + + + override click( + timeline: Timeline.State, + project: Project.ImmutableRoot, + playback: Playback.Manager, + element: Timeline.LayoutElement) + { + const chord = Project.getTypedElem(project, element.id, "chord") + if (!chord) + return + + if (element.trackId === undefined) + return + + timeline.insertion.duration = chord.range.duration + playback.playChordPreview(project, element.trackId, chord.chord) + } + + + override deleteRange( + timeline: Timeline.State, + project: Project.Mutable, + range: Range) + { + const list = project.root.lists.get(project.root.chordTrackId) + if (!list) + return + + if (range.duration.isZero()) + { + for (const elem of list.iterAtPoint(range.start)) + { + const removeElem = Project.elemModify(elem, { parentId: -1 }) + project.root = Project.upsertElement(project.root, removeElem) + } + } + else + { + for (const elem of list.iterAtRange(range)) + { + project.root = Project.splitElem(project.root, elem, range) + } + } + } + + + override insertByDegree( + timeline: Timeline.State, + project: Project.Mutable, + playback: Playback.Manager, + prefs: Prefs.Prefs, + time: Rational, + degree: number) + { + const trackId = project.root.chordTrackId + const key = Project.keyAt(project.root, time) + + const chord = Theory.Chord.fromDiatonicTriad(key, degree) + Timeline.insertChord( + timeline, + project, + playback, + trackId, + time, + chord) + } +} + + +function *iterChordsAndKeyChanges( + timeline: Timeline.State, + project: Project.ImmutableRoot) + : Generator<[Project.Chord, Timeline.KeyRegion], void, void> +{ + for (const keyRegion of timeline.layout.keyRegions) + { + const time1 = keyRegion.keyCh1.range.start.max(timeline.layout.range.start) + const time2 = keyRegion.keyCh2.range.start.min(timeline.layout.range.end) + + for (const chord of iterChords(timeline, project, new Range(time1, time2))) + yield [chord, keyRegion] + } +} + + +function *iterChords( + timeline: Timeline.State, + project: Project.ImmutableRoot, + range: Range) +{ + const list = project.lists.get(project.chordTrackId) + if (!list) + return + + for (const elem of list.iterAtRange(range)) + yield elem as Project.Chord +} + + +function rectForChord( + timeline: Timeline.State, + lane: Timeline.Lane, + chordRange: Range, + keyChXStart: number, + keyChXEnd: number) + : [Rect, boolean, boolean] +{ + const chordOrigX1 = Timeline.xAtTime(timeline, chordRange.start) + const chordOrigX2 = Timeline.xAtTime(timeline, chordRange.end) + + let chordX1 = Math.max(chordOrigX1, keyChXStart) + let chordX2 = Math.min(chordOrigX2, keyChXEnd) + + const cutStart = chordOrigX1 < chordX1 + const cutEnd = chordOrigX2 > chordX2 + + //if (!cutStart) chordX1 += 1 + //if (!cutEnd) chordX2 -= 1 + + chordX1 = 0.5 + Math.floor(chordX1) + chordX2 = 0.5 + Math.floor(chordX2) + + const chordW = Math.max(2, chordX2 - chordX1) + + return [ + new Rect(chordX1, 0, chordW, lane.rect.h), + cutStart, + cutEnd] +} + + +function *iterChordsAtRegion( + timeline: Timeline.State, + project: Project.ImmutableRoot, + lane: Timeline.Lane, + range: Range) + : Generator +{ + for (const [chord, keyChPair] of iterChordsAndKeyChanges(timeline, project)) + { + if (!chord.range.overlapsRange(range)) + continue + + yield chord.id + } +} \ No newline at end of file diff --git a/src/timeline/lane_notes.ts b/src/timeline/lane_notes.ts new file mode 100644 index 0000000..25b58af --- /dev/null +++ b/src/timeline/lane_notes.ts @@ -0,0 +1,603 @@ +import * as Project from "../project" +import * as Timeline from "./index.ts" +import * as Playback from "../playback" +import * as Prefs from "../prefs.ts" +import * as Theory from "../theory" +import Rect from "../utils/rect.ts" +import Range from "../utils/range.ts" +import Rational from "../utils/rational.ts" + + +export interface TupleIndicator +{ + denominator: number + rect: Rect + range: Range + highestMidiPitch: number + lowestMidiPitch: number +} + + +export const tupleDenominators = [13, 11, 7, 5, 3] + + +export class LaneNotes extends Timeline.Lane +{ + tupleIndicators: TupleIndicator[] = [] + + + getEditingNoteTrackId( + project: Project.ImmutableRoot) + : Project.ID | undefined + { + for (const track of project.tracks) + { + if (track.trackType === "notes" && + track.editable) + return track.id + } + + return undefined + } + + + override refreshLayout( + timeline: Timeline.State, + project: Project.ImmutableRoot, + prefs: Prefs.Prefs) + { + this.elements = [] + this.tupleIndicators = [] + + const editingNoteTrackId = this.getEditingNoteTrackId(project) + if (editingNoteTrackId !== undefined) + { + for (const [note, keyChPair] of iterNotesAndKeyChanges( + timeline, + project, + editingNoteTrackId)) + { + const key = keyChPair.keyCh1.key + const row = rowForPitch(note.midiPitch, key) + + const [rect, cutStart, cutEnd] = rectForNote( + timeline, + this, + note.range, + row, + keyChPair.x1, + keyChPair.x2, + true) + + if (!cutStart) + { + this.add({ + kind: "hidden", + id: note.id, + trackId: editingNoteTrackId, + laneIndex: this.laneIndex, + action: Timeline.MouseAction.StretchTimeStart, + rect: rect.withX1(rect.x1 - prefs.timeline.hoverOuterStretchWidth), + }) + } + + if (!cutEnd) + { + this.add({ + kind: "hidden", + id: note.id, + trackId: editingNoteTrackId, + laneIndex: this.laneIndex, + action: Timeline.MouseAction.StretchTimeEnd, + rect: rect.withX2(rect.x2 + prefs.timeline.hoverOuterStretchWidth), + }) + } + + if (rect.w > prefs.timeline.hoverInnerStretchWidth * 4) + { + if (!cutStart) + { + this.add({ + kind: "hidden", + id: note.id, + trackId: editingNoteTrackId, + laneIndex: this.laneIndex, + action: Timeline.MouseAction.StretchTimeStart, + rect: rect.withX2(rect.x1 + prefs.timeline.hoverInnerStretchWidth), + priority: 2, + }) + } + + if (!cutEnd) + { + this.add({ + kind: "hidden", + id: note.id, + trackId: editingNoteTrackId, + laneIndex: this.laneIndex, + action: Timeline.MouseAction.StretchTimeEnd, + rect: rect.withX1(rect.x2 - prefs.timeline.hoverInnerStretchWidth), + priority: 2, + }) + } + } + + this.add({ + kind: "note", + id: note.id, + trackId: editingNoteTrackId, + laneIndex: this.laneIndex, + note: note, + key: keyChPair.keyCh1.key, + action: Timeline.MouseAction.DragTimeAndRow, + rect, + cutStart, + cutEnd, + priority: 1, + }) + + for (const denom of Timeline.tupleDenominators) + { + if (note.range.duration.denominator % denom !== 0) + continue + + let tuple = this.tupleIndicators.find(t => + t.denominator === denom && + t.range.end.compare(note.range.start) === 0) + + if (tuple === undefined) + { + tuple = { + denominator: denom, + rect: rect, + range: note.range, + highestMidiPitch: note.midiPitch, + lowestMidiPitch: note.midiPitch, + } + + this.tupleIndicators.push(tuple) + } + + tuple.highestMidiPitch = Math.max(tuple.highestMidiPitch, note.midiPitch) + tuple.lowestMidiPitch = Math.min(tuple.lowestMidiPitch, note.midiPitch) + tuple.range = tuple.range.merge(note.range) + tuple.rect = tuple.rect.merge(rect) + break + } + } + } + + for (const track of project.tracks) + { + if (track.trackType !== "notes" || + track.id === editingNoteTrackId || + !track.visible) + continue + + for (const [note, keyChPair] of iterNotesAndKeyChanges( + timeline, + project, + track.id)) + { + const key = keyChPair.keyCh1.key + const row = rowForPitch(note.midiPitch, key) + + const [rect, cutStart, cutEnd] = rectForNote( + timeline, + this, + note.range, + row, + keyChPair.x1, + keyChPair.x2, + true) + + this.add({ + kind: "note", + id: note.id, + trackId: track.id, + laneIndex: this.laneIndex, + note: note, + key: keyChPair.keyCh1.key, + action: Timeline.MouseAction.None, + ghost: true, + rect, + cutStart, + cutEnd, + priority: 1, + }) + } + } + + let keyChCurrent = timeline.layout.keyRegions[0].keyCh1 + + for (const elem of iterMarkersForLayout(timeline, project, timeline.layout.range)) + { + const rect = rectForMarker( + timeline, + this, + elem.type, + elem.range.start) + + let keyCh: Project.KeyChange | undefined = undefined + let keyChPrev: Project.KeyChange | undefined = undefined + if (elem.type === "keyChange") + { + keyCh = elem + + if (keyCh.id !== keyChCurrent.id && + keyChCurrent.id >= 0) + keyChPrev = keyChCurrent + + keyChCurrent = keyCh + } + + this.add({ + kind: "marker", + id: elem.id, + laneIndex: this.laneIndex, + action: Timeline.MouseAction.DragTime, + rect, + priority: 1, + zIndex: 1, + tempoCh: elem.type === "tempoChange" ? elem : undefined, + keyCh, + keyChPrev, + meterCh: elem.type === "meterChange" ? elem : undefined, + }) + + timeline.layout.markers.push({ + time: elem.range.start, + tempoCh: elem.type === "tempoChange" ? elem : undefined, + keyCh: elem.type === "keyChange" ? elem : undefined, + meterCh: elem.type === "meterChange" ? elem : undefined, + }) + } + } + + + override *iterElementsForSelection( + timeline: Timeline.State, + project: Project.ImmutableRoot, + range: Range, + verticalRegion?: Timeline.VerticalRegion) + : Generator + { + const editingNoteTrackId = this.getEditingNoteTrackId(project) + + if (editingNoteTrackId !== undefined) + for (const note of iterNotesForSelection(timeline, project, editingNoteTrackId, this, range, verticalRegion)) + yield note + + for (const marker of iterMarkersForSelection(timeline, project, range, verticalRegion)) + yield marker + } + + + override click( + timeline: Timeline.State, + project: Project.ImmutableRoot, + playback: Playback.Manager, + element: Timeline.LayoutElement) + { + const note = Project.getTypedElem(project, element.id, "note") + if (!note) + return + + if (element.trackId === undefined) + return + + timeline.insertion.nearMidiPitch = note.midiPitch + timeline.insertion.duration = note.range.duration + playback.playNotePreview(project, element.trackId, note.midiPitch) + } + + + override findPreviousAnchor( + timeline: Timeline.State, + project: Project.ImmutableRoot, + time: Rational) + : Rational | null + { + const editingNoteTrackId = this.getEditingNoteTrackId(project) + if (editingNoteTrackId === undefined) + return null + + const list = project.lists.get(editingNoteTrackId) + if (!list) + return null + + const anchor = list.findPreviousDeletionAnchor(time) + return anchor + } + + + override allowsRectSelect(): boolean + { + return true + } + + + override deleteRange( + timeline: Timeline.State, + project: Project.Mutable, + range: Range) + { + const editingNoteTrackId = this.getEditingNoteTrackId(project.root) + if (editingNoteTrackId === undefined) + return + + const list = project.root.lists.get(editingNoteTrackId) + if (!list) + return + + if (range.duration.isZero()) + { + for (const elem of list.iterAtPoint(range.start)) + { + const removeElem = Project.elemModify(elem, { parentId: -1 }) + project.root = Project.upsertElement(project.root, removeElem) + } + } + else + { + for (const elem of list.iterAtRange(range)) + { + project.root = Project.splitElem(project.root, elem, range) + } + } + } + + + override insertByDegree( + timeline: Timeline.State, + project: Project.Mutable, + playback: Playback.Manager, + prefs: Prefs.Prefs, + time: Rational, + degree: number) + { + const trackId = this.getEditingNoteTrackId(project.root) + if (trackId === undefined) + return + + const key = Project.keyAt(project.root, time) + const chroma = key.chromaForDegree(degree) + Timeline.insertNote( + timeline, + project, + playback, + trackId, + time, + chroma) + } + + + override rowAtY( + timeline: Timeline.State, + y: number) + { + return rowAtY(timeline, this, y) + } +} + + +function *iterNotesAndKeyChanges( + timeline: Timeline.State, + project: Project.ImmutableRoot, + noteTrackId: Project.ID) + : Generator<[Project.Note, Timeline.KeyRegion], void, void> +{ + for (const keyRegion of timeline.layout.keyRegions) + { + const time1 = keyRegion.keyCh1.range.start.max(timeline.layout.range.start) + const time2 = keyRegion.keyCh2.range.start.min(timeline.layout.range.end) + + for (const note of iterNotes(timeline, project, noteTrackId, new Range(time1, time2))) + yield [note, keyRegion] + } +} + + +function *iterNotes( + timeline: Timeline.State, + project: Project.ImmutableRoot, + noteTrackId: Project.ID, + range: Range) + : Generator +{ + const list = project.lists.get(noteTrackId) + if (!list) + return + + for (const elem of list.iterAtRange(range)) + yield elem as Project.Note +} + + +export function rowForPitch( + pitch: number, + key: Theory.Key) + : number +{ + const tonicRowOffset = Theory.Utils.chromaToDegreeInCMajor(key.tonic.chroma) + return key.octavedDegreeForMidi(pitch - Theory.Utils.midiMiddleC) + tonicRowOffset +} + + +export function pitchForRow( + row: number, + key: Theory.Key) + : number +{ + const tonicRowOffset = Theory.Utils.chromaToDegreeInCMajor(key.tonic.chroma) + return key.midiForDegree(row - Math.floor(tonicRowOffset)) + Theory.Utils.midiMiddleC +} + + +export function yForRow( + timeline: Timeline.State, + lane: Timeline.Lane, + row: number) + : number +{ + return lane.rect.h / 2 - (row + 1) * timeline.noteRowH - timeline.yScroll +} + + +export function rowAtY( + timeline: Timeline.State, + lane: Timeline.Lane, + y: number) + : number +{ + return -Math.floor((y + timeline.yScroll - lane.rect.h / 2) / timeline.noteRowH) - 1 +} + + +function rectForNote( + timeline: Timeline.State, + lane: Timeline.Lane, + noteRange: Range, + noteRow: number, + keyChXStart: number, + keyChXEnd: number, + clampY: boolean) + : [Rect, boolean, boolean] +{ + const noteOrigX1 = Timeline.xAtTime(timeline, noteRange.start) + const noteOrigX2 = Timeline.xAtTime(timeline, noteRange.end) + + let noteY = 0.5 + Math.floor(yForRow(timeline, lane, noteRow)) + if (clampY) + { + noteY = + Math.max(lane.rect.y - timeline.noteRowH / 2, + Math.min(lane.rect.y + lane.rect.h - timeline.noteRowH / 2, + noteY)) + } + + let noteX1 = Math.max(noteOrigX1, keyChXStart) + let noteX2 = Math.min(noteOrigX2, keyChXEnd) + + const cutStart = noteOrigX1 < noteX1 + const cutEnd = noteOrigX2 > noteX2 + + //if (!cutStart) noteX1 += 1 + //if (!cutEnd) noteX2 -= 1 + + noteX1 = 0.5 + Math.floor(noteX1) + noteX2 = 0.5 + Math.floor(noteX2) + + const noteW = Math.max(2, noteX2 - noteX1) + + return [ + new Rect(noteX1, noteY, noteW, timeline.noteRowH), + cutStart, + cutEnd] +} + + +function *iterNotesForSelection( + timeline: Timeline.State, + project: Project.ImmutableRoot, + noteTrackId: Project.ID, + lane: Timeline.Lane, + range: Range, + verticalRegion?: Timeline.VerticalRegion) + : Generator +{ + for (const [note, keyChPair] of iterNotesAndKeyChanges(timeline, project, noteTrackId)) + { + if (!note.range.overlapsRange(range)) + continue + + if (verticalRegion !== undefined) + { + const [rect] = rectForNote( + timeline, + lane, + note.range, + rowForPitch(note.midiPitch, keyChPair.keyCh1.key), + keyChPair.x1, + keyChPair.x2, + false) + + if (verticalRegion.y1 > rect.y2 || + verticalRegion.y2 < rect.y1) + continue + } + + yield note.id + } +} + + +function *iterMarkersForLayout( + timeline: Timeline.State, + project: Project.ImmutableRoot, + range: Range) + : Generator +{ + const tempoChTrackElems = project.lists.get(project.tempoChangeTrackId) + if (tempoChTrackElems) + for (const tempoCh of tempoChTrackElems.iterAtRange(range)) + yield tempoCh as Project.TempoChange + + const keyChTrackElems = project.lists.get(project.keyChangeTrackId) + if (keyChTrackElems) + for (const keyCh of keyChTrackElems.iterAtRange(range)) + yield keyCh as Project.KeyChange + + const meterChTrackElems = project.lists.get(project.meterChangeTrackId) + if (meterChTrackElems) + for (const meterCh of meterChTrackElems.iterAtRange(range)) + yield meterCh as Project.MeterChange +} + + +function rectForMarker( + timeline: Timeline.State, + lane: Timeline.Lane, + markerType: Project.Element["type"], + time: Rational) + : Rect +{ + const row = + markerType === "tempoChange" ? 0 : + markerType === "keyChange" ? 1 : + 2 + + const x = Timeline.xAtTime(timeline, time) + const w = 16 + const h = 24 + const y = h * row + + return new Rect(x - w / 2, y, w, h) +} + + +function *iterMarkersForSelection( + timeline: Timeline.State, + project: Project.ImmutableRoot, + range: Range, + verticalRegion?: Timeline.VerticalRegion) + : Generator +{ + if (verticalRegion !== undefined) + return + + const tempoChTrackElems = project.lists.get(project.tempoChangeTrackId) + if (tempoChTrackElems) + for (const tempoCh of tempoChTrackElems.iterAtRange(range)) + yield tempoCh.id + + const keyChTrackElems = project.lists.get(project.keyChangeTrackId) + if (keyChTrackElems) + for (const keyCh of keyChTrackElems.iterAtRange(range)) + yield keyCh.id + + const meterChTrackElems = project.lists.get(project.meterChangeTrackId) + if (meterChTrackElems) + for (const meterCh of meterChTrackElems.iterAtRange(range)) + yield meterCh.id +} \ No newline at end of file diff --git a/src/timeline/layout.ts b/src/timeline/layout.ts new file mode 100644 index 0000000..27eb490 --- /dev/null +++ b/src/timeline/layout.ts @@ -0,0 +1,301 @@ +import * as Project from "../project" +import * as Timeline from "./index.ts" +import * as Prefs from "../prefs.ts" +import * as Theory from "../theory/index.ts" +import Rect from "../utils/rect.ts" +import Range from "../utils/range.ts" +import Rational from "../utils/rational.ts" + + +export interface LayoutElementCommon +{ + id?: Project.ID + trackId?: Project.ID + laneIndex?: number + action?: Timeline.MouseAction + ghost?: boolean + rect: Rect + zIndex?: number + zIndexForHover?: number + cutStart?: boolean + cutEnd?: boolean + priority?: number + subElements?: LayoutElement[] +} + + +export interface LayoutElementHidden extends LayoutElementCommon +{ + kind: "hidden" +} + + +export interface LayoutElementLaneCommon extends LayoutElementCommon +{ + laneIndex: number + iterElementsAtRegion?: ( + timeline: Timeline.State, + project: Project.ImmutableRoot, + range: Range, + verticalRegion?: { y1: number, y2: number }) + => Generator +} + + +export interface LayoutElementLaneMarkers extends LayoutElementLaneCommon +{ + kind: "laneMarkers" +} + + +export interface LayoutElementLaneNotes extends LayoutElementLaneCommon +{ + kind: "laneNotes" +} + + +export interface LayoutElementLaneChords extends LayoutElementLaneCommon +{ + kind: "laneChords" +} + + +export interface LayoutElementNote extends LayoutElementCommon +{ + kind: "note" + note: Project.Note + key: Theory.Key +} + + +export interface LayoutElementChord extends LayoutElementCommon +{ + kind: "chord" + chord: Project.Chord + key: Theory.Key +} + + +export interface LayoutElementMarker extends LayoutElementCommon +{ + kind: "marker" + tempoCh?: Project.TempoChange + keyCh?: Project.KeyChange + keyChPrev?: Project.KeyChange + meterCh?: Project.MeterChange +} + + +export type LayoutElement = + LayoutElementHidden | + LayoutElementLaneMarkers | + LayoutElementLaneNotes | + LayoutElementLaneChords | + LayoutElementNote | + LayoutElementChord | + LayoutElementMarker + + +export type LayoutLane = + LayoutElementLaneMarkers | + LayoutElementLaneNotes | + LayoutElementLaneChords + + +export interface KeyRegion +{ + keyCh1: Project.KeyChange + keyCh2: Project.KeyChange + range: Range + x1: number + x2: number +} + + +export interface ChordRegionTone +{ + row: number + degree: number +} + + +export interface ChordRegion +{ + key: Theory.Key + chord: Theory.Chord + x1: number + x2: number + tones: ChordRegionTone[] +} + + +export interface Marker +{ + time: Rational + tempoCh?: Project.TempoChange + keyCh?: Project.KeyChange + meterCh?: Project.MeterChange +} + + +export class Layout +{ + range: Range + inBoundsRange: Range + lanes: Timeline.Lane[] = [] + + measures: Project.Measure[] = [] + keyRegions: KeyRegion[] = [] + chordRegions: ChordRegion[] = [] + chordTonesVisible: boolean = true + markers: Marker[] = [] + + + constructor() + { + + } + + + addLane(lane: Timeline.Lane) + { + lane.laneIndex = this.lanes.length + this.lanes.push(lane) + } + + + getElementCount() + { + let count = 0 + for (const lane of this.lanes) + count += lane.elements.length + + return count + } +} + + +export function layout( + timeline: Timeline.State, + project: Project.ImmutableRoot, + prefs: Prefs.Prefs) +{ + const layout = new Layout() + timeline.layout = layout + + layout.range = Timeline.visibleTimeRange(timeline) + layout.inBoundsRange = project.range + layout.measures = [...Project.iterMeasuresAtRange(project, layout.range)] + layout.keyRegions = [...iterKeyChangePairsAtRange(timeline, project, layout.range)] + layout.chordRegions = [...iterChordRegions(timeline, project, layout.keyRegions)] + layout.chordTonesVisible = !!Project.getTrack(project, project.chordTrackId, "chords")?.visible + layout.markers = [] + + + const laneChordH = 60 + const laneMarginY = 8 + + const laneNotes = new Timeline.LaneNotes() + laneNotes.rect = new Rect( + 0, + 0, + timeline.renderRect.w, + timeline.renderRect.h - laneChordH - laneMarginY) + + const laneChords = new Timeline.LaneChords() + laneChords.rect = new Rect( + 0, + timeline.renderRect.h - laneChordH, + timeline.renderRect.w, + laneChordH - 1) + + layout.addLane(laneNotes) + layout.addLane(laneChords) + + laneNotes.refreshLayout(timeline, project, prefs) + laneChords.refreshLayout(timeline, project, prefs) + + //console.log(layout.getElementCount(), layout) +} + + +function *iterKeyChangePairsAtRange( + timeline: Timeline.State, + project: Project.ImmutableRoot, + range: Range) + : Generator +{ + const keyChangeTrackTimedElems = project.lists.get(project.keyChangeTrackId) + if (!keyChangeTrackTimedElems) + return + + const firstKeyCh = keyChangeTrackTimedElems.findFirst() as (Project.KeyChange | null) + const defaultKey = firstKeyCh?.key ?? Project.defaultKey() + + for (const pair of keyChangeTrackTimedElems.iterActiveAtRangePairwise(range)) + { + const keyCh1 = + pair[0] as Project.KeyChange ?? + Project.makeKeyChange(-1, range.start, defaultKey) + + const keyCh2 = + pair[1] as Project.KeyChange ?? + Project.makeKeyChange(-1, range.end, defaultKey) + + const regionRange = new Range( + keyCh1.range.start.max(range.start), + keyCh2.range.start.min(range.end)) + + const x1 = Timeline.xAtTime(timeline, keyCh1.range.start) + const x2 = Timeline.xAtTime(timeline, keyCh2.range.start) + + yield { + keyCh1, + keyCh2, + range: regionRange, + x1, + x2, + } + } +} + + +function *iterChordRegions( + timeline: Timeline.State, + project: Project.ImmutableRoot, + keyRegions: KeyRegion[]) + : Generator +{ + const chordTrackElems = project.lists.get(project.chordTrackId) + if (!chordTrackElems) + return + + for (const keyRegion of keyRegions) + { + const key = keyRegion.keyCh1.key + + for (const elem of chordTrackElems.iterAtRange(keyRegion.range)) + { + const chord = elem as Project.Chord + const x1 = Timeline.xAtTime(timeline, chord.range.start.max(keyRegion.range.start)) + const x2 = Timeline.xAtTime(timeline, chord.range.end.min(keyRegion.range.end)) + + const tones: ChordRegionTone[] = chord.chord.pitches.map(pitch => { + const row = Timeline.rowForPitch(pitch, key) + const degree = key.degreeForMidi(pitch) + return { + row, + degree, + } + }) + + yield { + key: keyRegion.keyCh1.key, + chord: chord.chord, + x1, + x2, + tones, + } + } + } +} \ No newline at end of file diff --git a/src/timeline/mouse_down.ts b/src/timeline/mouse_down.ts new file mode 100644 index 0000000..22d7644 --- /dev/null +++ b/src/timeline/mouse_down.ts @@ -0,0 +1,146 @@ +import * as Timeline from "./index.ts" +import * as Project from "../project" +import * as Playback from "../playback" +import * as Prefs from "../prefs.ts" +import Rect from "../utils/rect.ts" +import Rational from "../utils/rational.ts" +import Range from "../utils/range.ts" + + +export function mouseDown( + timeline: Timeline.State, + project: Project.Mutable, + playback: Playback.Manager, + prefs: Prefs.Prefs, + rightButton: boolean) +{ + if (timeline.mouse.down) + return + + Timeline.keyHandlePendingFinish(timeline, project) + + const prevDownDate = timeline.mouse.downDate + + timeline.mouse.down = true + timeline.mouse.downDate = new Date() + timeline.mouse.action = Timeline.MouseAction.None + + const selectMultiple = + timeline.keysDown.has(prefs.timeline.keySelectMultiple) + const forceCursorSelect = + timeline.keysDown.has(prefs.timeline.keyForceCursorSelect) + const selectClone = + timeline.keysDown.has(prefs.timeline.keyClone) + const forcePan = + timeline.keysDown.has(prefs.timeline.keyPan) + const doubleClick = + timeline.mouse.downDate.getTime() - prevDownDate.getTime() < + prefs.timeline.mouseDoubleClickThresholdMs + + timeline.drag = { + origin: { + point: { ...timeline.mouse.point }, + range: null, + timeScroll: timeline.timeScroll, + yScroll: timeline.yScroll, + project: project.root, + }, + + xLocked: true, + yLocked: true, + posDelta: { x: 0, y: 0 }, + timeDelta: new Rational(0), + rowDelta: 0, + laneDelta: 0, + trackInsertionBefore: -1, + + elemId: undefined, + trackId: undefined, + notePreviewLast: null, + } + + if (rightButton || forcePan) + { + timeline.mouse.action = Timeline.MouseAction.Pan + return + } + + + const hoverIsSelected = + timeline.hover !== undefined && + timeline.hover.id !== undefined && + timeline.selection.has(timeline.hover.id) + + + if (!selectMultiple && !hoverIsSelected) + Timeline.selectionClear(timeline) + + + if (timeline.hover === undefined || + timeline.hover.action === undefined || + forceCursorSelect) + { + timeline.mouse.action = Timeline.MouseAction.SelectCursor + timeline.cursor.visible = true + timeline.cursor.rectMode = false + Timeline.cursorSetTime(timeline, timeline.mouse.point.time, timeline.mouse.point.time) + Timeline.cursorSetLaneIndex(timeline, timeline.mouse.point.laneIndex, timeline.mouse.point.laneIndex) + + timeline.cursor.verticalRegion = { + y1: timeline.mouse.point.lanePos.y, + y2: timeline.mouse.point.lanePos.y, + } + + if (doubleClick) + { + const anchor = Timeline.findPreviousAnchor( + timeline, + project.root, + timeline.mouse.point.time, + timeline.mouse.point.laneIndex, + timeline.mouse.point.laneIndex) + + Timeline.cursorSetTime(timeline, anchor, anchor) + Timeline.scrollTimeIntoView(timeline, anchor) + + timeline.mouse.down = false + timeline.mouse.action = Timeline.MouseAction.None + } + + return + } + + if (timeline.hover !== undefined) + { + timeline.cursor.visible = false + + if (timeline.hover.laneIndex !== undefined) + Timeline.cursorSetLaneIndex( + timeline, + timeline.hover.laneIndex, + timeline.hover.laneIndex) + + if (!hoverIsSelected) + Timeline.selectionToggle( + timeline, + project.root, + timeline.hover) + + timeline.drag.elemId = timeline.hover.id + timeline.drag.trackId = timeline.hover.trackId + timeline.drag.origin.range = + Timeline.selectionRange(timeline, project.root) + + if (timeline.drag.origin.range) + Timeline.cursorSetTime( + timeline, + timeline.drag.origin.range.start, + timeline.drag.origin.range.end) + + timeline.mouse.action = timeline.hover.action + + const lane = timeline.layout.lanes[timeline.mouse.point.laneIndex] + lane?.click(timeline, project.root, playback, timeline.hover) + return + } +} \ No newline at end of file diff --git a/src/timeline/mouse_drag.ts b/src/timeline/mouse_drag.ts new file mode 100644 index 0000000..da4dd31 --- /dev/null +++ b/src/timeline/mouse_drag.ts @@ -0,0 +1,231 @@ +import * as Project from "../project" +import * as Timeline from "./index.ts" +import * as Playback from "../playback" +import * as Prefs from "../prefs.ts" +import Rect from "../utils/rect.ts" +import Range from "../utils/range.ts" + + +export function mouseDrag( + timeline: Timeline.State, + project: Project.Mutable, + playback: Playback.Manager, + prefs: Prefs.Prefs) + : boolean +{ + if (!timeline.mouse.down) + return false + + timeline.mouse.pointPrev = timeline.mouse.point + timeline.mouse.point = Timeline.pointAt( + timeline, + timeline.mouse.point.pos.x, + timeline.mouse.point.pos.y) + + timeline.drag.posDelta = { + x: timeline.mouse.point.pos.x - timeline.drag.origin.point.pos.x, + y: timeline.mouse.point.pos.y - timeline.drag.origin.point.pos.y, + } + + timeline.drag.timeDelta = + timeline.mouse.point.time.subtract(timeline.drag.origin.point.time) + + timeline.drag.rowDelta = + timeline.mouse.point.row - timeline.drag.origin.point.row + + timeline.drag.laneDelta = + timeline.mouse.point.laneIndex - timeline.drag.origin.point.laneIndex + + timeline.drag.xLocked = + timeline.drag.xLocked && + Math.abs(timeline.drag.posDelta.x) < prefs.timeline.mouseDragXLockedDistance + + timeline.drag.yLocked = + timeline.drag.yLocked && + Math.abs(timeline.drag.posDelta.y) < prefs.timeline.mouseDragYLockedDistance + + + if (timeline.mouse.action === Timeline.MouseAction.Pan) + return handlePanning(timeline, project) + + else if (timeline.mouse.action === Timeline.MouseAction.SelectCursor) + return handleSelectCursor(timeline, project) + + else + return handleDragElements(timeline, project, playback) +} + + +function handlePanning( + timeline: Timeline.State, + project: Project.Mutable) + : boolean +{ + timeline.timeScroll = + timeline.drag.origin.timeScroll - + (timeline.drag.posDelta.x / timeline.timeScale) + + timeline.yScroll = + timeline.drag.origin.yScroll - + timeline.drag.posDelta.y + + return true +} + + +function handleSelectCursor( + timeline: Timeline.State, + project: Project.Mutable) + : boolean +{ + Timeline.cursorSetTime(timeline, null, timeline.mouse.point.time) + Timeline.cursorSetLaneIndex(timeline, null, timeline.mouse.point.laneIndex) + + timeline.cursor.verticalRegion = { + y1: timeline.cursor.verticalRegion.y1, + y2: timeline.mouse.point.lanePos.y, + } + + const lane = timeline.layout.lanes[timeline.cursor.laneIndex1] + + timeline.cursor.rectMode = + timeline.cursor.laneIndex1 === timeline.cursor.laneIndex2 && + (timeline.cursor.time1.compare(timeline.cursor.time2) !== 0 || + !timeline.drag.yLocked) && + lane?.allowsRectSelect() + + Timeline.selectionClear(timeline) + Timeline.selectionAddAtCursor(timeline, project.root) + return true +} + + +function handleDragElements( + timeline: Timeline.State, + project: Project.Mutable, + playback: Playback.Manager) + : boolean +{ + let action = timeline.mouse.action + + if (timeline.drag.xLocked) + { + if (action === Timeline.MouseAction.DragTime || + action === Timeline.MouseAction.StretchTimeStart || + action === Timeline.MouseAction.StretchTimeEnd) + action = Timeline.MouseAction.None + + else if (action === Timeline.MouseAction.DragTimeAndRow) + action = Timeline.MouseAction.DragRow + } + + if (timeline.drag.yLocked) + { + if (action === Timeline.MouseAction.DragTimeAndRow) + action = Timeline.MouseAction.DragTime + + else if (action === Timeline.MouseAction.DragRow) + action = Timeline.MouseAction.None + } + + + const origProject = timeline.drag.origin.project + let newProject = origProject + + for (const id of timeline.selection) + { + const elem = origProject.elems.get(id) + if (!elem) + continue + + if (elem.type === "track") + continue + + const changes: Partial = {} + + if (action === Timeline.MouseAction.DragTime || + action === Timeline.MouseAction.DragTimeAndRow) + { + changes.range = elem.range + .displace(timeline.drag.timeDelta) + .quantize(Project.MAX_RATIONAL_DENOMINATOR) + } + + + if (action === Timeline.MouseAction.StretchTimeStart && + timeline.drag.origin.range) + { + changes.range = Project.getAbsoluteRange(origProject, elem.parentId, elem.range) + changes.range = changes.range + .stretch( + timeline.drag.timeDelta, + timeline.drag.origin.range.end, + timeline.drag.origin.range.start) + .sorted() + + if (elem.range.start.compare(timeline.drag.origin.range.start) === 0) + changes.range = new Range( + changes.range.start.snap(timeline.timeSnap), + changes.range.end) + .quantize(Project.MAX_RATIONAL_DENOMINATOR) + + changes.range = changes.range.sorted() + changes.range = Project.getRelativeRange(origProject, elem.parentId, changes.range) + } + + + if (action === Timeline.MouseAction.StretchTimeEnd && + timeline.drag.origin.range) + { + changes.range = Project.getAbsoluteRange(origProject, elem.parentId, elem.range) + changes.range = changes.range + .stretch( + timeline.drag.timeDelta, + timeline.drag.origin.range.start, + timeline.drag.origin.range.end) + .sorted() + + if (elem.range.end.compare(timeline.drag.origin.range.end) === 0) + changes.range = new Range( + changes.range.start, + changes.range.end.snap(timeline.timeSnap)) + .quantize(Project.MAX_RATIONAL_DENOMINATOR) + + changes.range = changes.range.sorted() + changes.range = Project.getRelativeRange(origProject, elem.parentId, changes.range) + } + + + if ((action === Timeline.MouseAction.DragRow || + action === Timeline.MouseAction.DragTimeAndRow) && + elem.type === "note") + { + const note = elem as Project.Note + const key = Project.keyAt(project.root, note.range.start) + const degree = key.octavedDegreeForMidi(note.midiPitch) + const newPitch = key.midiForDegree(Math.floor(degree + timeline.drag.rowDelta)) + changes.midiPitch = newPitch + + if (elem.id === timeline.drag.elemId && + timeline.drag.trackId !== undefined) + { + if ((timeline.drag.notePreviewLast && newPitch !== timeline.drag.notePreviewLast) || + (!timeline.drag.notePreviewLast && newPitch !== note.midiPitch)) + { + timeline.drag.notePreviewLast = newPitch + playback.playNotePreview( + newProject, + timeline.drag.trackId, + newPitch) + } + } + } + + newProject = Project.upsertElement( + newProject, + Project.elemModify(elem, changes)) + } + + project.root = newProject + return newProject !== origProject +} \ No newline at end of file diff --git a/src/timeline/mouse_move.ts b/src/timeline/mouse_move.ts new file mode 100644 index 0000000..a58a0ec --- /dev/null +++ b/src/timeline/mouse_move.ts @@ -0,0 +1,65 @@ +import * as Project from "../project" +import * as Timeline from "./index.ts" +import Rect from "../utils/rect.ts" +import Range from "../utils/range.ts" + + +export function mouseMove( + timeline: Timeline.State, + project: Project.ImmutableRoot, + x: number, + y: number) +{ + timeline.mouse.point = Timeline.pointAt(timeline, x, y) + + if (!timeline.mouse.down) + { + timeline.hover = undefined + hoverLanes(timeline, x, y) + } +} + + +function hoverLanes( + timeline: Timeline.State, + x: number, + y: number) +{ + for (const lane of timeline.layout.lanes) + { + if (x >= lane.rect.x && + x < lane.rect.x + lane.rect.w && + y >= lane.rect.y && + y < lane.rect.y + lane.rect.h) + { + hoverRecursive(timeline, lane.elements, x - lane.rect.x, y - lane.rect.y) + } + } +} + + +function hoverRecursive( + timeline: Timeline.State, + elements: Timeline.LayoutElement[], + x: number, + y: number) +{ + for (const elem of elements) + { + if (elem.ghost) + continue + + if (x >= elem.rect.x && + x < elem.rect.x + elem.rect.w && + y >= elem.rect.y && + y < elem.rect.y + elem.rect.h) + { + if (timeline.hover === undefined || + (elem.priority ?? 0) >= (timeline.hover.priority ?? 0)) + timeline.hover = elem + + if (elem.subElements) + hoverRecursive(timeline, elem.subElements, x - elem.rect.x, y - elem.rect.y) + } + } +} \ No newline at end of file diff --git a/src/timeline/mouse_up.ts b/src/timeline/mouse_up.ts new file mode 100644 index 0000000..80c0b68 --- /dev/null +++ b/src/timeline/mouse_up.ts @@ -0,0 +1,29 @@ +import * as Project from "../project" +import * as Timeline from "./index.ts" +import Rect from "../utils/rect.ts" +import Rational from "../utils/rational.ts" +import Range from "../utils/range.ts" + + +export function mouseUp( + timeline: Timeline.State, + project: Project.Mutable, + rightButton: boolean) +{ + if (!timeline.mouse.down) + return + + timeline.mouse.down = false + timeline.mouse.action = Timeline.MouseAction.None + + if (timeline.cursor.rectMode) + { + timeline.cursor.rectMode = false + timeline.cursor.visible = false + } + + const origProject = project.root + Timeline.selectionResolveOverlappingAndDegenerate(timeline, project) + project.root = Project.withRefreshedRange(project.root) + return project.root !== origProject +} \ No newline at end of file diff --git a/src/timeline/mouse_wheel.ts b/src/timeline/mouse_wheel.ts new file mode 100644 index 0000000..853699e --- /dev/null +++ b/src/timeline/mouse_wheel.ts @@ -0,0 +1,49 @@ +import * as Timeline from "./index.ts" +import Rational from "../utils/rational.ts" + + +export function mouseWheel( + timeline: Timeline.State, + deltaX: number, + deltaY: number) +{ + if (Math.abs(deltaX) > 0) + { + timeline.timeScroll = timeline.timeScroll + 0.01 / (timeline.timeScale / 100) * deltaX + timeline.mouse.wheelDate = new Date() + } + else if (new Date().getTime() - timeline.mouse.wheelDate.getTime() > 250) + { + const snap = new Rational(1, 1024) + const prevMouseTime = Timeline.timeAtX( + timeline, + timeline.mouse.point.pos.x, + snap) + + let newTimeScale = timeline.timeScale * (deltaY > 0 ? 0.8 : 1.25) + newTimeScale = Math.max(4, Math.min(2048, newTimeScale)) + timeline.timeScale = newTimeScale + + const newMouseTime = Timeline.timeAtX( + timeline, + timeline.mouse.point.pos.x, + snap) + + const newTimeScroll = timeline.timeScroll - newMouseTime.subtract(prevMouseTime).asFloat() + + const timeSnapAdjustThresholdUpper = 24 + const timeSnapAdjustThresholdLower = 8 + let newTimeSnap = timeline.timeSnapBase + + if (newTimeSnap.asFloat() * newTimeScale > timeSnapAdjustThresholdUpper) + while (newTimeSnap.asFloat() * newTimeScale > timeSnapAdjustThresholdUpper) + newTimeSnap = newTimeSnap.divide(new Rational(2)) + + else if (newTimeSnap.asFloat() * newTimeScale < timeSnapAdjustThresholdLower) + while (newTimeSnap.asFloat() * newTimeScale < timeSnapAdjustThresholdLower) + newTimeSnap = newTimeSnap.divide(new Rational(1, 2)) + + timeline.timeScroll = newTimeScroll + timeline.timeSnap = newTimeSnap + } +} \ No newline at end of file diff --git a/src/timeline/timeline.ts b/src/timeline/timeline.ts new file mode 100644 index 0000000..e02ebc9 --- /dev/null +++ b/src/timeline/timeline.ts @@ -0,0 +1,769 @@ +import * as Immutable from "immutable" +import * as Project from "../project" +import * as Timeline from "./index.ts" +import * as Playback from "../playback" +import * as Theory from "../theory" +import Rational from "../utils/rational.ts" +import Range from "../utils/range.ts" +import Rect from "../utils/rect.ts" +import * as MathUtils from "../utils/mathUtils.ts" + + +export interface State +{ + pixelRatio: number + renderRect: Rect + + trackMeasuresH: number + trackControlX: number + trackControlY: number + trackControlSize: number + + layout: Timeline.Layout + hover: Timeline.LayoutElement | undefined + + yScroll: number + timeScroll: number + timeScale: number + timeSnap: Rational + timeSnapBase: Rational + + noteRowH: number + + cursor: + { + visible: boolean + time1: Rational + time2: Rational + laneIndex1: number + laneIndex2: number + rectMode: boolean + verticalRegion: VerticalRegion + } + + keysDown: Set, + + mouse: + { + down: boolean + downDate: Date + + action: MouseAction + + point: Point + pointPrev: Point + + wheelDate: Date + } + + drag: + { + origin: + { + point: Point + range: Range | null + timeScroll: number + yScroll: number + project: Project.ImmutableRoot + } + + xLocked: boolean + yLocked: boolean + + posDelta: { x: number, y: number } + timeDelta: Rational + rowDelta: number + laneDelta: number + trackInsertionBefore: number + + elemId: Project.ID | undefined + trackId: Project.ID | undefined + notePreviewLast: number | null + } + + insertion: + { + nearMidiPitch: number + duration: Rational + } + + selection: Immutable.Set + + needsKeyFinish: boolean + rangeSelectOriginTrackIndex: number + + playbackStartTime: Rational +} + + +export enum MouseAction +{ + None, + Pan, + DragTime, + DragRow, + DragTimeAndRow, + StretchTimeStart, + StretchTimeEnd, + SelectCursor, +} + + +export interface Point +{ + pos: { x: number, y: number } + time: Rational + row: number + laneIndex: number + lanePos: { x: number, y: number } + originLanePos: { x: number, y: number } +} + + +export interface HoverData +{ + id: Project.ID + range: Range + action: MouseAction +} + + +export interface VerticalRegion +{ + y1: number + y2: number +} + + + +export function makeNew(): State +{ + return { + pixelRatio: 1, + renderRect: new Rect(0, 0, 0, 0), + + trackMeasuresH: 20, + trackControlX: 10, + trackControlY: 25, + trackControlSize: 20, + + layout: new Timeline.Layout(), + hover: undefined, + + yScroll: 0, + timeScroll: -2.5, + timeScale: 100, + timeSnap: new Rational(1, 8), + timeSnapBase: new Rational(1, 16), + + noteRowH: 16, + + cursor: + { + visible: true, + time1: new Rational(0), + time2: new Rational(0), + laneIndex1: 0, + laneIndex2: 0, + rectMode: false, + verticalRegion: { + y1: 0, + y2: 0, + }, + }, + + keysDown: new Set(), + + mouse: + { + down: false, + downDate: new Date(), + + action: MouseAction.None, + + point: + { + pos: { x: 0, y: 0 }, + time: new Rational(0), + row: 0, + laneIndex: 0, + lanePos: { x: 0, y: 0 }, + originLanePos: { x: 0, y: 0 }, + }, + + pointPrev: + { + pos: { x: 0, y: 0 }, + time: new Rational(0), + row: 0, + laneIndex: 0, + lanePos: { x: 0, y: 0 }, + originLanePos: { x: 0, y: 0 }, + }, + + wheelDate: new Date(), + }, + + drag: + { + origin: null!, + xLocked: true, + yLocked: true, + posDelta: { x: 0, y: 0 }, + timeDelta: new Rational(0), + rowDelta: 0, + laneDelta: 0, + trackInsertionBefore: -1, + + elemId: undefined, + trackId: undefined, + notePreviewLast: null, + }, + + insertion: + { + nearMidiPitch: 60, + duration: new Rational(1, 4), + }, + + selection: Immutable.Set(), + + needsKeyFinish: false, + rangeSelectOriginTrackIndex: -1, + + playbackStartTime: new Rational(0), + } +} + + +export function resize( + state: State, + pixelRatio: number, + rect: Rect) +{ + state.pixelRatio = pixelRatio + state.renderRect = rect +} + + +export function xAtTime( + timeline: Timeline.State, + time: Rational) + : number +{ + return (time.asFloat() - timeline.timeScroll) * timeline.timeScale +} + + +export function timeAtX( + timeline: Timeline.State, + x: number, + timeSnap?: Rational) + : Rational +{ + timeSnap = timeSnap || timeline.timeSnap + const time = x / timeline.timeScale + timeline.timeScroll + return Rational.fromFloat(time, timeSnap.denominator) +} + + +export function timeRangeAtX( + timeline: Timeline.State, + x1: number, + x2: number, + timeSnap?: Rational) + : Range +{ + timeSnap = timeSnap || timeline.timeSnap + return new Range( + timeAtX(timeline, x1, timeSnap).subtract(timeSnap), + timeAtX(timeline, x2, timeSnap).add(timeSnap)) +} + + +export function visibleTimeRange( + timeline: Timeline.State) + : Range +{ + return new Range( + timeAtX(timeline, 0).subtract(timeline.timeSnap), + timeAtX(timeline, timeline.renderRect.w).add(timeline.timeSnap)) +} + + +export function laneIndexAtY( + timeline: Timeline.State, + y: number) + : number +{ + if (y < 0) + return -1 + + for (let i = 0; i < timeline.layout.lanes.length; i++) + { + const lane = timeline.layout.lanes[i] + + if (y < lane.rect.y2) + return i + } + + return timeline.layout.lanes.length +} + + +export function pointAt( + timeline: Timeline.State, + x: number, + y: number) + : Point +{ + const time = timeAtX(timeline, x) + + const laneIndex = laneIndexAtY(timeline, y) + const lane = timeline.layout.lanes[laneIndex] + + const row = lane?.rowAtY(timeline, y) ?? 0 + + const laneY = lane?.rect.y ?? 0 + + const lanePosY = y - laneY + const lanePos = { x: x, y: lanePosY } + + let originLanePos = lanePos + if (timeline.drag.origin) + { + const originTrackPosY = y - laneY + originLanePos = { x: x, y: originTrackPosY } + } + + return { + pos: { x, y }, + time, + laneIndex, + lanePos, + row, + originLanePos, + } +} + + +export function selectionClear( + timeline: Timeline.State) +{ + timeline.selection = timeline.selection.clear() +} + + +export function selectionRange( + state: Timeline.State, + project: Project.ImmutableRoot) + : Range | null +{ + return Project.getRangeForElems(project, state.selection) +} + + +export function selectionToggle( + timeline: Timeline.State, + project: Project.ImmutableRoot, + element: Timeline.LayoutElement) +{ + if (element.id === undefined || + element.action === undefined) + return + + const alreadySelected = timeline.selection.has(element.id) + + if (!alreadySelected) + timeline.selection = timeline.selection.add(element.id) + else + timeline.selection = timeline.selection.remove(element.id) +} + + +export function selectionAdd( + timeline: Timeline.State, + id: Project.ID) +{ + timeline.selection = timeline.selection.add(id) +} + + +export function selectionAddAtCursor( + timeline: Timeline.State, + project: Project.ImmutableRoot) +{ + const time1 = timeline.cursor.time1 + const time2 = timeline.cursor.time2 + if (time1.compare(time2) === 0) + return + + const verticalRegion = + !timeline.cursor.rectMode ? undefined : + { + y1: Math.min(timeline.cursor.verticalRegion.y1, timeline.cursor.verticalRegion.y2), + y2: Math.max(timeline.cursor.verticalRegion.y1, timeline.cursor.verticalRegion.y2), + } + + const range = new Range(time1, time2, false, false).sorted() + + const laneIndexMin = cursorGetLaneIndexMin(timeline) + const laneIndexMax = cursorGetLaneIndexMax(timeline) + + for (let l = laneIndexMin; l <= laneIndexMax; l++) + { + const lane = timeline.layout.lanes[l] + if (lane.iterElementsForSelection === undefined) + continue + + for (const id of lane.iterElementsForSelection(timeline, project, range, verticalRegion)) + selectionAdd(timeline, id) + } +} + + +export function rewind( + timeline: Timeline.State, + project: Project.ImmutableRoot) +{ + timeline.cursor.visible = true + cursorSetTime(timeline, project.range.start, project.range.start) + scrollTimeIntoView(timeline, timeline.cursor.time1) +} + + +export function cursorSetTime( + timeline: Timeline.State, + time1: Rational | null, + time2?: Rational | null) +{ + timeline.cursor.time1 = time1 ?? timeline.cursor.time1 + timeline.cursor.time2 = time2 ?? timeline.cursor.time2 + + timeline.playbackStartTime = timeline.cursor.time1.min(timeline.cursor.time2) +} + + +export function cursorSetLaneIndex( + timeline: Timeline.State, + laneIndex1: number | null, + laneIndex2?: number | null) +{ + timeline.cursor.laneIndex1 = + Math.max(0, Math.min(timeline.layout.lanes.length - 1, + laneIndex1 ?? timeline.cursor.laneIndex1)) + + timeline.cursor.laneIndex2 = + Math.max(0, Math.min(timeline.layout.lanes.length - 1, + laneIndex2 ?? timeline.cursor.laneIndex2)) +} + + +export function cursorGetLaneIndexMin( + timeline: Timeline.State) +{ + return Math.max(0, Math.min( + timeline.cursor.laneIndex1, + timeline.cursor.laneIndex2)) +} + + +export function cursorGetLaneIndexMax( + timeline: Timeline.State) +{ + return Math.min(timeline.layout.lanes.length - 1, Math.max( + timeline.cursor.laneIndex1, + timeline.cursor.laneIndex2)) +} + + +export function scrollTimeIntoView( + timeline: Timeline.State, + time: Rational) +{ + const range = visibleTimeRange(timeline) + const marginPixels = 100 + const marginTime = Rational.fromFloat(marginPixels / timeline.timeScale, 10000) + + if (time.compare(range.end.subtract(marginTime)) >= 0) + { + timeline.timeScroll = + time.asFloat() - + (timeline.renderRect.w - marginPixels) / timeline.timeScale + + return true + } + else if (time.compare(range.start.add(marginTime)) <= 0) + { + timeline.timeScroll = + time.asFloat() - + marginPixels / timeline.timeScale + + return true + } + + return false +} + + +export function scrollPlayTimeIntoView( + timeline: Timeline.State, + time: Rational) +{ + if (timeline.mouse.down) + return false + + const range = visibleTimeRange(timeline) + const marginPixels = 100 + const marginTime = Rational.fromFloat(marginPixels / timeline.timeScale, 10000) + + if (time.compare(range.end.subtract(marginTime)) >= 0 || + time.compare(range.start.add(marginTime)) <= 0) + { + timeline.timeScroll = + time.asFloat() - + marginPixels / timeline.timeScale + + return true + } + + return false +} + + +export function keyHandlePendingFinish( + timeline: Timeline.State, + project: Project.Mutable) +{ + if (!timeline.needsKeyFinish) + return + + timeline.needsKeyFinish = false + + selectionResolveOverlappingAndDegenerate(timeline, project) +} + + +export function selectionResolveOverlappingAndDegenerate( + timeline: Timeline.State, + project: Project.Mutable) +{ + for (const id of timeline.selection) + { + const selectedElem = project.root.elems.get(id) + if (!selectedElem) + continue + + const list = project.root.lists.get(selectedElem.parentId) + if (!list) + continue + + const absSelectedRange = Project.getAbsoluteRange( + project.root, + selectedElem.parentId, + selectedElem.range) + + if (selectedElem.range.duration.isZero()) + { + if (selectedElem.type === "note" || + selectedElem.type === "chord") + { + const removeElem = Project.elemModify(selectedElem, { parentId: -1 }) + project.root = Project.upsertElement(project.root, removeElem) + } + else + { + for (const elem of list.iterAtPoint(selectedElem.range.start)) + { + if (timeline.selection.has(elem.id)) + continue + + const removeElem = Project.elemModify(elem, { parentId: -1 }) + project.root = Project.upsertElement(project.root, removeElem) + } + } + } + else + { + for (const elem of list.iterAtRange(selectedElem.range)) + { + if (timeline.selection.has(elem.id)) + continue + + if (elem.type === "note" && + selectedElem.type === "note" && + elem.midiPitch !== selectedElem.midiPitch) + continue + + project.root = Project.splitElem( + project.root, + elem, + absSelectedRange) + } + } + } +} + + +export function insertNote( + timeline: Timeline.State, + project: Project.Mutable, + playback: Playback.Manager, + trackId: Project.ID, + time: Rational, + chroma: number) +{ + keyHandlePendingFinish(timeline, project) + + const insertOctave = Math.floor(timeline.insertion.nearMidiPitch / 12) + const possiblePitches = [-1, 0, 1].map(offset => { + const pitch = (insertOctave + offset) * 12 + (MathUtils.mod(chroma, 12)) + const delta = Math.abs(pitch - timeline.insertion.nearMidiPitch) + return { pitch, delta } + }) + + possiblePitches.sort((a, b) => a.delta - b.delta) + const chosenPitch = possiblePitches[0].pitch + + const range = new Range(time, time.add(timeline.insertion.duration)) + + const note = Project.makeNote( + trackId, + range, + chosenPitch) + + const id = project.root.nextId + project.root = Project.upsertElement(project.root, note) + project.root = Project.withRefreshedRange(project.root) + + timeline.insertion.nearMidiPitch = chosenPitch + + timeline.cursor.visible = false + cursorSetTime(timeline, range.end, range.end) + scrollTimeIntoView(timeline, range.end) + selectionClear(timeline) + selectionAdd(timeline, id) + playback.playNotePreview(project.root, trackId, chosenPitch) + selectionResolveOverlappingAndDegenerate(timeline, project) +} + + +export function findPreviousAnchor( + timeline: Timeline.State, + project: Project.ImmutableRoot, + time: Rational, + laneIndex1: number, + laneIndex2: number) + : Rational +{ + let prevAnchor: Rational | null = null + + const laneMin = Math.min(laneIndex1, laneIndex2) + const laneMax = Math.max(laneIndex1, laneIndex2) + + for (let i = Math.max(0, laneMin); i <= Math.min(timeline.layout.lanes.length - 1, laneMax); i++) + { + const anchor = timeline.layout.lanes[i].findPreviousAnchor(timeline, project, time) + if (!anchor) + continue + + prevAnchor = Rational.max(prevAnchor, anchor) + } + + if (!prevAnchor) + return project.range.start + + return prevAnchor +} + + +export function deleteRange( + timeline: Timeline.State, + project: Project.Mutable, + range: Range, + trackIndex1: number, + trackIndex2: number) +{ + const trackMin = Math.min(trackIndex1, trackIndex2) + const trackMax = Math.max(trackIndex1, trackIndex2) + + for (let tr = Math.max(0, trackMin); tr <= Math.min(timeline.layout.lanes.length - 1, trackMax); tr++) + timeline.layout.lanes[tr].deleteRange(timeline, project, range) +} + + +export function insertChord( + timeline: Timeline.State, + project: Project.Mutable, + playback: Playback.Manager, + trackId: Project.ID, + time: Rational, + chord: Theory.Chord) +{ + keyHandlePendingFinish(timeline, project) + + const range = new Range(time, time.add(timeline.insertion.duration)) + + const projChord = Project.makeChord( + trackId, + range, + chord) + + const id = project.root.nextId + project.root = Project.upsertElement(project.root, projChord) + project.root = Project.withRefreshedRange(project.root) + + timeline.cursor.visible = false + cursorSetTime(timeline, range.end, range.end) + scrollTimeIntoView(timeline, range.end) + selectionClear(timeline) + selectionAdd(timeline, id) + playback.playChordPreview(project.root, trackId, chord) + selectionResolveOverlappingAndDegenerate(timeline, project) +} + + +export function deleteElems( + timeline: Timeline.State, + project: Project.Mutable, + elemIds: Iterable) +{ + const range = + Project.getRangeForElems(project.root, elemIds) ?? + new Range(timeline.cursor.time1, timeline.cursor.time1) + + for (const id of elemIds) + { + const elem = project.root.elems.get(id) + if (!elem) + continue + + if (elem.type == "track") + continue + + const removeElem = Project.elemModify(elem, { parentId: -1 }) + project.root = Project.upsertElement(project.root, removeElem) + } + + for (const id of elemIds) + { + const track = project.root.elems.get(id) + if (!track) + continue + + if (track.type != "track") + continue + + if (track.id === project.root.keyChangeTrackId || + track.id === project.root.meterChangeTrackId || + track.id === project.root.chordTrackId) + continue + + project.root = Project.upsertTrack(project.root, track, true) + } + + project.root = Project.withRefreshedRange(project.root) + timeline.cursor.visible = true + cursorSetTime(timeline, range.start, range.start) + scrollTimeIntoView(timeline, range.start) +} \ No newline at end of file diff --git a/src/ui/Inspector.tsx b/src/ui/Inspector.tsx new file mode 100644 index 0000000..240401e --- /dev/null +++ b/src/ui/Inspector.tsx @@ -0,0 +1,108 @@ +import * as Solid from "solid-js" +import * as Global from "../state.ts" +import * as Inspector from "./index.ts" +import * as Project from "../project" +import * as Timeline from "../timeline" +import * as Theory from "../theory" +import Rect from "../utils/rect.ts" +import Rational from "../utils/rational.ts" +import { styled } from "solid-styled-components" + + +export function InspectorRoot(props: {}) +{ + const selectedIdsRaw = Solid.createMemo(() => { + const timeline = Global.get().timeline + return timeline.selection + }) + + const selectedIds = Solid.createMemo(() => { + const ids = selectedIdsRaw() + return ids + }) + + const firstElemId = Solid.createMemo(() => { + if (selectedIds().count() !== 1) + return undefined + + const project = Global.get().project + + let firstElem = null + for (const id of selectedIds()) + { + firstElem = Project.getElem(project.root, id) + if (firstElem) + break + } + + return firstElem?.id + }) + + const project = Global.get().project + + const inspectorKind = Solid.createMemo(() => { + const upsertElem = (elem: Project.Element) => { + console.log(elem) + project.root = Project.upsertElement(project.root, elem) + Global.refresh() + } + + const elemId = firstElemId() + const elem = Project.getElem(project.root, elemId) + const key = Project.keyAt(project.root, project.root.keyChangeTrackId, elem?.range.start ?? new Rational(0)) + + if (elem?.type === "tempoChange") + return + + if (elem?.type === "keyChange") + return + + if (elem?.type === "meterChange") + return + + if (elem?.type === "chord") + return + + return + }) + + return + + +
{ `${selectedIds().count()} selected` }
+ + { inspectorKind() } +
+} + + +const Layout = styled.div` + display: grid; + grid-template: auto auto 1fr / auto auto; + contain: size; + width: 100%; + height: 100%; + min-height: 0; + padding: 0.5em; + justify-content: start; + justify-items: start; + align-content: start; + align-items: start; + column-gap: 1em; +` \ No newline at end of file diff --git a/src/ui/InspectorChord.tsx b/src/ui/InspectorChord.tsx new file mode 100644 index 0000000..08000c9 --- /dev/null +++ b/src/ui/InspectorChord.tsx @@ -0,0 +1,363 @@ +import * as Solid from "solid-js" +import * as Global from "../state.ts" +import * as Ui from "./index.ts" +import * as Project from "../project" +import * as Timeline from "../timeline" +import * as Theory from "../theory" +import Rect from "../utils/rect.ts" +import * as CanvasUtils from "../utils/canvasUtils.ts" +import { styled } from "solid-styled-components" + + +export function InspectorChord(props: { + value?: Project.Chord, + insertElem?: (newValue: Theory.Chord) => void, + upsertElem?: (newValue: Project.Chord) => void, + key: Theory.Key, +}) +{ + const [currChord, setCurrChord] = Solid.createSignal(props.value?.chord) + + if (props.value?.chord) + console.log(props.value.chord) + + const [chordOptions, setChordOptionsRaw] = Solid.createSignal( + props.value ? + Theory.ChordOptions.makeFromChord(props.key, props.value?.chord) : + Theory.ChordOptions.makeEmpty()) + + const applyChord = (chord: Theory.Chord) => { + setCurrChord(chord) + props.insertElem?.(chord) + const project = Global.get().project + const projChord = Project.getTypedElem(project.root, props.value?.id, "chord") + if (projChord) + props.upsertElem?.({...projChord, chord }) + } + + const setChordOptions = (modifyFn: (old: Theory.ChordOptions) => Theory.ChordOptions) => { + const oldOpts = chordOptions() + const newOpts = modifyFn(oldOpts) + setChordOptionsRaw(newOpts) + + let selectedDegree: number | undefined = undefined + for (let degree = 0; degree < 7; degree++) + { + const chord = Theory.ChordOptions.buildChord(props.key, degree, oldOpts) + const isSelected = !!currChord()?.isEqualForPalette(chord) + if (isSelected) + selectedDegree = degree + } + + if (props.value && + selectedDegree !== undefined) + { + const newChord = Theory.ChordOptions.buildChord(props.key, selectedDegree, newOpts) + applyChord(newChord) + } + } + + const makeChordButton = Solid.createMemo(() => { + const chOpts = chordOptions() + + return (key: Theory.Key, degree: number) => { + const chord = Theory.ChordOptions.buildChord(key, degree, chOpts) + const isSelected = props.value && currChord()?.isEqualForPalette(chord) + console.log(degree, chord) + + let canvas: HTMLCanvasElement = undefined! + + Solid.createEffect(() => { + const prefs = Global.get().prefs + const pixelRatio = window.devicePixelRatio + const canvasRect = canvas.getBoundingClientRect() + canvas.width = Math.floor(canvasRect.width * pixelRatio) + canvas.height = Math.floor(canvasRect.height * pixelRatio) + const ctx = canvas.getContext("2d")! + const rect = Rect.fromVertices(0, 0, canvas.width, canvas.height) + CanvasUtils.drawChord( + ctx, + rect, + prefs, + chord, + props.key) + + if (isSelected) + { + ctx.save() + ctx.roundRect( + rect.x, + rect.y, + rect.w, + rect.h, + 5) + ctx.clip() + ctx.lineWidth = 5 + ctx.strokeStyle = "#fffd" + ctx.stroke() + ctx.restore() + } + }) + + return applyChord(chord) } + > + + + } + }) + + const makeChordButtons = Solid.createMemo(() => { + const chOpts = chordOptions() + const scaleId = chOpts.borrowFromScaleId ?? props.key.scale.id! + const borrowedKey = new Theory.Key(props.key.tonic, Theory.Scale.fromId(scaleId)) + + return borrowedKey.chroma.map((chroma, degree) => + makeChordButton()(borrowedKey, degree) + ) + }) + + const makeScaleOption = (scaleMeta: Theory.ScaleMetadata) => { + return +
+ { scaleMeta.names[0] } + | +
+ + { (chroma, i) => { + const degree = i() + const borrowedKey = new Theory.Key(props.key.tonic, Theory.Scale.fromId(scaleMeta.id)) + const chord = Theory.Chord.fromDiatonicTriad(borrowedKey, degree) + const chordStr = chord.str(props.key) + + return
+ { chordStr.romanBase } + {chordStr.romanSup } + {chordStr.romanSub } + { " " } +
+ }} +
+
+
+ } + + return <> + + + { makeChordButtons() } + + + +
+ setChordOptions(opts => ({ ...opts, withStacking: 0 })) } + /> + setChordOptions(opts => ({ ...opts, withStacking: ev ? 7 : 0 })) } + /> + setChordOptions(opts => ({ ...opts, withStacking: ev ? 9 : 0 })) } + /> + setChordOptions(opts => ({ ...opts, withStacking: ev ? 11 : 0 })) } + /> + setChordOptions(opts => ({ ...opts, withStacking: ev ? 13 : 0 })) } + /> +
+
+ setChordOptions(opts => ({ ...opts, withSus2: ev })) } + /> +
+ setChordOptions(opts => ({ ...opts, withSus4: ev })) } + /> +
+
+ setChordOptions(opts => ({ ...opts, withAdd9: ev })) } + /> +
+ setChordOptions(opts => ({ ...opts, withAdd11: ev })) } + /> +
+ setChordOptions(opts => ({ ...opts, withAdd13: ev })) } + /> +
+
+ setChordOptions(opts => ({ ...opts, withNo3: ev })) } + /> +
+ setChordOptions(opts => ({ ...opts, withNo5: ev })) } + /> +
+
+ setChordOptions(opts => ({ ...opts, withFlat5: ev })) } + /> +
+ setChordOptions(opts => ({ ...opts, withSharp5: ev })) } + /> +
+ setChordOptions(opts => ({ ...opts, withFlat9: ev })) } + /> +
+ setChordOptions(opts => ({ ...opts, withSharp9: ev })) } + /> +
+ setChordOptions(opts => ({ ...opts, withSharp11: ev })) } + /> +
+ setChordOptions(opts => ({ ...opts, withFlat13: ev })) } + /> +
+
+ +
+ setChordOptions(opts => ({ ...opts, borrowFromScaleId: scaleId })) } + > + { Theory.Scale.list.map( + (scaleMeta, i) => makeScaleOption(scaleMeta) + )} + +
+ +
+ +} + + +const Layout = styled.div` + display: grid; + grid-template: auto / auto auto auto; + width: 100%; + height: 100%; + min-height: 0; + justify-content: center; + justify-items: center; + align-content: start; + align-items: start; + column-gap: 1em; +` + + +const LayoutChordButtons = styled.div` + display: grid; + grid-template: 1fr / repeat(7, 1fr); + width: 100%; + height: 100%; + justify-content: center; + justify-items: center; + align-content: start; + align-items: start; + column-gap: 0.5em; +` + + +const LayoutChordOptions = styled.div` + display: grid; + grid-template: repeat(6, auto) / repeat(3, auto); + justify-content: start; + justify-items: start; + align-content: start; + align-items: start; + row-gap: 0.5em; + column-gap: 1em; +` + + +const ChordButton = styled.button<{ +}>` + border: 0; + border-radius: var(--theme-buttonBorderRadius); + margin: 0; + padding: 0; + width: 5em; + height: 3.5em; + background-color: #fff; + cursor: pointer; +` + + +const ChordCanvas = styled.canvas<{ +}>` + width: 100%; + height: 100%; + + &:hover { + opacity: 0.5; + } +` + + +const OptionScale = styled.option` + margin-top: 0.5em; + margin-bottom: 0.5em; +` \ No newline at end of file diff --git a/src/ui/InspectorInsert.tsx b/src/ui/InspectorInsert.tsx new file mode 100644 index 0000000..7c0db90 --- /dev/null +++ b/src/ui/InspectorInsert.tsx @@ -0,0 +1,137 @@ +import * as Solid from "solid-js" +import * as Global from "../state.ts" +import * as Ui from "./index.ts" +import * as Project from "../project" +import * as Timeline from "../timeline" +import * as Theory from "../theory" +import Rect from "../utils/rect.ts" +import { styled } from "solid-styled-components" + + +export function InspectorInsert(props: { +}) +{ + const time = Solid.createMemo(() => { + const timeline = Global.get().timeline + return timeline.cursor.time1 + }) + + const key = Solid.createMemo(() => { + const project = Global.get().project + return Project.keyAt(project.root, time()) + }) + + const insertionKind = Solid.createMemo(() => { + const timeline = Global.get().timeline + if (timeline.selection.size !== 0) + return null + + if (timeline.cursor.laneIndex1 !== timeline.cursor.laneIndex2) + return null + + const lane = timeline.layout.lanes[timeline.cursor.laneIndex1] + + if (lane instanceof Timeline.LaneChords) + return "chord" + + if (lane instanceof Timeline.LaneNotes) + return "note" + + return null + }) + + const insertTempoChange = () => { + const project = Global.get().project + const timeline = Global.get().timeline + const time = timeline.cursor.time1 + const id = project.root.nextId + const tempoCh = Project.makeTempoChange(project.root.tempoChangeTrackId, time, 120) + project.root = Project.upsertElement(project.root, tempoCh) + Timeline.selectionClear(timeline) + Timeline.selectionAdd(timeline, id) + Timeline.selectionResolveOverlappingAndDegenerate(timeline, project) + timeline.cursor.visible = false + Global.refresh() + } + + const insertKeyChange = () => { + const project = Global.get().project + const timeline = Global.get().timeline + const time = timeline.cursor.time1 + const id = project.root.nextId + const key = Theory.Key.parse("C Major") + const keyCh = Project.makeKeyChange(project.root.keyChangeTrackId, time, key) + project.root = Project.upsertElement(project.root, keyCh) + Timeline.selectionClear(timeline) + Timeline.selectionAdd(timeline, id) + Timeline.selectionResolveOverlappingAndDegenerate(timeline, project) + timeline.cursor.visible = false + Global.refresh() + } + + const insertMeterChange = () => { + const project = Global.get().project + const timeline = Global.get().timeline + const time = timeline.cursor.time1 + const id = project.root.nextId + const meter = Theory.Meter.parse("4 / 4") + const meterCh = Project.makeMeterChange(project.root.meterChangeTrackId, time, meter) + project.root = Project.upsertElement(project.root, meterCh) + Timeline.selectionClear(timeline) + Timeline.selectionAdd(timeline, id) + Timeline.selectionResolveOverlappingAndDegenerate(timeline, project) + timeline.cursor.visible = false + Global.refresh() + } + + const insertChord = (chord: Theory.Chord) => { + const project = Global.get().project + const timeline = Global.get().timeline + const time = timeline.cursor.time1 + const id = project.root.nextId + const trackId = project.root.chordTrackId + Timeline.insertChord(timeline, project, trackId, time, chord) + Timeline.selectionClear(timeline) + Timeline.selectionAdd(timeline, id) + Timeline.selectionResolveOverlappingAndDegenerate(timeline, project) + Timeline.selectionClear(timeline) + timeline.cursor.visible = true + Global.refresh() + } + + return + +
+ + + Tempo Change + + + + Key Change + + + + Meter Change + +
+
+ + + +
+} + + +const Layout = styled.div` + display: grid; + grid-template: auto 1fr / auto; + width: 100%; + height: 100%; + min-height: 0; + justify-content: center; + justify-items: center; + align-content: center; + align-items: center; + column-gap: 1em; +` \ No newline at end of file diff --git a/src/ui/InspectorKeyChange.tsx b/src/ui/InspectorKeyChange.tsx new file mode 100644 index 0000000..7b1a216 --- /dev/null +++ b/src/ui/InspectorKeyChange.tsx @@ -0,0 +1,296 @@ +import * as Solid from "solid-js" +import * as Global from "../state.ts" +import * as Ui from "./index.ts" +import * as Project from "../project" +import * as Timeline from "../timeline" +import * as Theory from "../theory" +import Rect from "../utils/rect.ts" +import { styled } from "solid-styled-components" + + +export function InspectorKeyChange(props: { + value: Project.KeyChange, + upsertElem: (newValue: Project.KeyChange) => void, +}) +{ + const [currKey, setCurrKey] = Solid.createSignal(props.value.key) + const circleOfFifthsOffset = Solid.createMemo(() => currKey().scale.metadata?.circleOfFifthsOffset ?? 0) + let needsScaleScroll = true + + + const applyKey = (key: Theory.Key) => { + setCurrKey(key) + const project = Global.get().project + const keyCh = Project.getTypedElem(project.root, props.value.id, "keyChange") + if (keyCh) + props.upsertElem({...keyCh, key }) + } + + + const applyTonic = (tonic: Theory.PitchName) => { + const project = Global.get().project + const keyCh = Project.getTypedElem(project.root, props.value.id, "keyChange") + if (keyCh) + { + const key = new Theory.Key(tonic, keyCh.key.scale) + applyKey(key) + } + } + + + const applyScale = (scaleId: string) => { + const project = Global.get().project + const scale = Theory.Scale.fromId(scaleId) + const keyCh = Project.getTypedElem(project.root, props.value.id, "keyChange") + if (keyCh) + { + const key = new Theory.Key(keyCh.key.tonic, scale) + applyKey(key) + } + } + + + const makeTonicButton = Solid.createMemo(() => (circleOfFifthsIndex: number) => { + const letter = Theory.Utils.circleOfFifthsToLetter(circleOfFifthsIndex + circleOfFifthsOffset()) + const accidental = Theory.Utils.circleOfFifthsToAccidental(circleOfFifthsIndex + circleOfFifthsOffset()) + const letterStr = Theory.Utils.letterToStr(letter) + const accidentalStr = Theory.Utils.accidentalToStr(accidental) + const pitchName = new Theory.PitchName(letter, accidental) + + const angleArc = Math.PI * 2 / 12 + const angle1 = (circleOfFifthsIndex - 0.5) * angleArc + const angle2 = (circleOfFifthsIndex + 0.5) * angleArc + const trigX1 = Math.sin(angle1) + const trigX2 = Math.sin(angle2) + const trigY1 = -Math.cos(angle1) + const trigY2 = -Math.cos(angle2) + const radius1 = 50 + const radius2 = 100 + const angleMargin1 = 0.025 + const angleMargin2 = angleMargin1 * (radius1 / radius2) + const subdiv = 4 + + let path = "" + for (let i = 0; i <= subdiv; i++) + { + const t = i / subdiv + const angle = angle1 + (angle2 - angle1) * t + (angleMargin1 - angleMargin1 * 2 * t) + path += `${ i == 0 ? "M" : "L"} ${Math.sin(angle) * radius1} ${-Math.cos(angle) * radius1} ` + } + for (let i = 0; i <= subdiv; i++) + { + const t = 1 - i / subdiv + const angle = angle1 + (angle2 - angle1) * t + (angleMargin2 - angleMargin2 * 2 * t) + path += `L ${Math.sin(angle) * radius2} ${-Math.cos(angle) * radius2} ` + } + path += "Z" + + return + applyTonic(pitchName) } + fill="#444" + $selected={ currKey().tonic.chroma === pitchName.chroma } + /> + + { letterStr }{ accidentalStr } + + + }) + + + const makeScaleOption = (scaleMeta: Theory.ScaleMetadata) => { + return +
+ { scaleMeta.names[0] } + { " " } +
+ + { (chroma, i) => { + const degree = i() + const chromaCMajor = Theory.Utils.degreeToChromaInCMajor(degree) + const accidental = chroma - chromaCMajor + const accidentalStr = Theory.Utils.accidentalToStr(accidental) + + return
0 ? "red" : + accidental < 0 ? "blue" : + undefined, + }}> + { `${accidentalStr}${(degree + 1).toString()} ` } +
+ }} +
+
+
+ } + + + const makeScaleOption2 = Solid.createMemo(() => (scaleMeta: Theory.ScaleMetadata) => { + const isSelected = scaleMeta.id === currKey().scale.id + let button: HTMLButtonElement | undefined = undefined + + Solid.createEffect(() => { + if (needsScaleScroll && isSelected && button) + { + (button as HTMLButtonElement).scrollIntoView({ behavior: "instant", block: "center" }) + needsScaleScroll = false + } + }) + + return applyScale(scaleMeta.id) } + > + { scaleMeta.names[0] } + + + { (chroma, i) => { + const degree = i() + const chromaCMajor = Theory.Utils.degreeToChromaInCMajor(degree) + const accidental = chroma - chromaCMajor + const accidentalStr = Theory.Utils.accidentalToStr(accidental) + + return
0 ? "#f88" : + accidental < 0 ? "#88f" : + undefined, + }}> + { `${accidentalStr}${(degree + 1).toString()} ` } +
+ }} +
+
+
+ }) + + + return <> + +

Key Change

+ + + { [0, 1, 2, 3, 4, 5, 6, -5, -4, -3, -2, -1].map( + (index, i) => makeTonicButton()(index) + )} + + + + { Theory.Scale.list.map( + (scaleMeta, i) => makeScaleOption2()(scaleMeta) + )} + + + {/* applyScale(ev.target.value) } + > + + + { (scaleMeta, i) => makeScaleOption(scaleMeta) } + + */} + +
+ +} + + +const Layout = styled.div` + display: grid; + grid-template: auto 1fr / auto auto; + width: 100%; + height: 100%; + min-height: 0; + justify-content: center; + justify-items: center; + align-content: center; + align-items: center; + column-gap: 1em; +` + + +const CircleOfFifthsPath = styled.path<{ + $selected: boolean, +}>` + cursor: pointer; + fill: var(--theme-buttonBkg); + + &:hover { + fill: var(--theme-buttonBkgHover); + } + + &:active { + fill: var(--theme-buttonBkgPress); + } + + ${ props => props.$selected ? "fill: var(--theme-buttonBkgSelected);" : "" } +` + + +const ScaleList = styled.div` + width: 20em; + height: 100%; + min-height: 0; + overflow-x: hidden; + overflow-y: scroll; + scrollbar-gutter: stable; + border-radius: 0.25em; +` + + +const ScaleButton = styled.div<{ + "data-selected": boolean, +}>` + display: grid; + grid-template: auto auto / auto; + justify-items: start; + justify-content: start; + user-select: none; + cursor: pointer; + + width: 90%; + margin: 0.25em; + padding: 0.25em 0.5em; + border-radius: var(--theme-buttonBorderRadius); + background-color: var(--theme-buttonBkg); + + &:hover { + background-color: var(--theme-buttonBkgHover); + } + + &:active { + background-color: var(--theme-buttonBkgPress); + } + + &:[data-selected=true] { + background-color: var(--theme-buttonBkgSelected); + } +` + + +const OptionScale = styled.option` + margin-top: 0.5em; + margin-bottom: 0.5em; +` \ No newline at end of file diff --git a/src/ui/InspectorMeterChange.tsx b/src/ui/InspectorMeterChange.tsx new file mode 100644 index 0000000..53ec1c8 --- /dev/null +++ b/src/ui/InspectorMeterChange.tsx @@ -0,0 +1,187 @@ +import * as Solid from "solid-js" +import * as Global from "../state.ts" +import * as Ui from "./index.ts" +import * as Project from "../project" +import * as Timeline from "../timeline" +import * as Theory from "../theory" +import Rect from "../utils/rect.ts" +import { styled } from "solid-styled-components" + + +export function InspectorMeterChange(props: { + value: Project.MeterChange, + upsertElem: (newValue: Project.MeterChange) => void, +}) +{ + const [currMeter, setCurrMeter] = Solid.createSignal(props.value.meter) + + + const applyMeter = (meter: Theory.Meter) => { + setCurrMeter(meter) + const project = Global.get().project + const tempoCh = Project.getTypedElem(project.root, props.value.id, "meterChange") + if (tempoCh) + props.upsertElem({...tempoCh, meter }) + } + + + const parseNumerator = (str: string, index: number) => { + let numerator = parseInt(str) + if (!isFinite(numerator)) + return + + numerator = Math.max(1, Math.min(64, numerator)) + applyMeter(currMeter().withNumerator(index, numerator)) + } + + + const parseDenominator = (str: string, index: number) => { + let denominator = parseInt(str) + if (!isFinite(denominator)) + return + + denominator = Math.max(1, Math.min(64, denominator)) + applyMeter(currMeter().withDenominator(index, denominator)) + } + + + const addAlternating = () => { + applyMeter(currMeter().withAddedRatio()) + } + + + const removeAlternating = (index: number) => { + applyMeter(currMeter().withRemovedRatio(index)) + } + + + return <> + +

Meter Change

+ + + { (ratio, index) => +
+ parseNumerator(str, index) } + width="4em" + /> + { " / " } + parseDenominator(str, index) } + width="4em" + > + + + + + + + + + removeAlternating(index) } + disabled={ index === 0 } + /> +
+ } +
+ + + + +
+ +} + + +const Layout = styled.div` + display: grid; + grid-template: repeat(5, auto) / auto; + width: 100%; + height: 100%; + min-height: 0; + justify-content: center; + justify-items: center; + align-content: start; + align-items: start; + column-gap: 1em; +` + + +const CircleOfFifthsPath = styled.path<{ + $selected: boolean, +}>` + cursor: pointer; + fill: var(--theme-buttonBkg); + + &:hover { + fill: var(--theme-buttonBkgHover); + } + + &:active { + fill: var(--theme-buttonBkgPress); + } + + ${ props => props.$selected ? "fill: var(--theme-buttonBkgSelected);" : "" } +` + + +const ScaleList = styled.div` + width: 20em; + height: 100%; + min-height: 0; + overflow-x: hidden; + overflow-y: scroll; + scrollbar-gutter: stable; + border-radius: 0.25em; +` + + +const ScaleButton = styled.div<{ + $selected: boolean, +}>` + display: grid; + grid-template: auto auto / auto; + justify-items: start; + justify-content: start; + user-select: none; + cursor: pointer; + + width: 90%; + margin: 0.25em; + padding: 0.25em 0.5em; + border-radius: var(--theme-buttonBorderRadius); + background-color: var(--theme-buttonBkg); + + &:hover { + background-color: var(--theme-buttonBkgHover); + } + + &:active { + background-color: var(--theme-buttonBkgPress); + } + + ${ props => props.$selected ? "background-color: var(--theme-buttonBkgSelected);" : "" } +` + + +const StyledSelect = styled.select` + /*appearance: base-select; + + &::picker(select) { + appearance: base-select; + }*/ +` + + +const OptionScale = styled.option` + margin-top: 0.5em; + margin-bottom: 0.5em; +` \ No newline at end of file diff --git a/src/ui/InspectorTempoChange.tsx b/src/ui/InspectorTempoChange.tsx new file mode 100644 index 0000000..b94c172 --- /dev/null +++ b/src/ui/InspectorTempoChange.tsx @@ -0,0 +1,137 @@ +import * as Solid from "solid-js" +import * as Global from "../state.ts" +import * as Ui from "./index.ts" +import * as Project from "../project" +import * as Timeline from "../timeline" +import * as Theory from "../theory" +import Rect from "../utils/rect.ts" +import { styled } from "solid-styled-components" + + +export function InspectorTempoChange(props: { + value: Project.TempoChange, + upsertElem: (newValue: Project.TempoChange) => void, +}) +{ + const [currTempo, setCurrTempo] = Solid.createSignal(props.value.bpm) + + + const applyTempo = (bpm: number) => { + setCurrTempo(bpm) + const project = Global.get().project + const tempoCh = Project.getTypedElem(project.root, props.value.id, "tempoChange") + if (tempoCh) + props.upsertElem({...tempoCh, bpm }) + } + + + const parseTempo = (bpmStr: string) => { + let bpm = parseInt(bpmStr) + if (!isFinite(bpm)) + return + + bpm = Math.max(1, Math.min(999, bpm)) + applyTempo(bpm) + } + + + return <> + +

Tempo Change

+ + + +
+ +} + + +const Layout = styled.div` + display: grid; + grid-template: auto auto / auto; + width: 100%; + height: 100%; + min-height: 0; + justify-content: center; + justify-items: center; + align-content: start; + align-items: start; + column-gap: 1em; +` + + +const CircleOfFifthsPath = styled.path<{ + $selected: boolean, +}>` + cursor: pointer; + fill: var(--theme-buttonBkg); + + &:hover { + fill: var(--theme-buttonBkgHover); + } + + &:active { + fill: var(--theme-buttonBkgPress); + } + + ${ props => props.$selected ? "fill: var(--theme-buttonBkgSelected);" : "" } +` + + +const ScaleList = styled.div` + width: 20em; + height: 100%; + min-height: 0; + overflow-x: hidden; + overflow-y: scroll; + scrollbar-gutter: stable; + border-radius: 0.25em; +` + + +const ScaleButton = styled.div<{ + $selected: boolean, +}>` + display: grid; + grid-template: auto auto / auto; + justify-items: start; + justify-content: start; + user-select: none; + cursor: pointer; + + width: 90%; + margin: 0.25em; + padding: 0.25em 0.5em; + border-radius: var(--theme-buttonBorderRadius); + background-color: var(--theme-buttonBkg); + + &:hover { + background-color: var(--theme-buttonBkgHover); + } + + &:active { + background-color: var(--theme-buttonBkgPress); + } + + ${ props => props.$selected ? "background-color: var(--theme-buttonBkgSelected);" : "" } +` + + +const StyledSelect = styled.select` + /*appearance: base-select; + + &::picker(select) { + appearance: base-select; + }*/ +` + + +const OptionScale = styled.option` + margin-top: 0.5em; + margin-bottom: 0.5em; +` \ No newline at end of file diff --git a/src/ui/TrackList.tsx b/src/ui/TrackList.tsx new file mode 100644 index 0000000..ec6be01 --- /dev/null +++ b/src/ui/TrackList.tsx @@ -0,0 +1,206 @@ +import * as Solid from "solid-js" +import * as Global from "../state.ts" +import * as Ui from "./index.ts" +import * as Project from "../project" +import * as Timeline from "../timeline" +import * as Theory from "../theory" +import Rect from "../utils/rect.ts" +import Rational from "../utils/rational.ts" +import { styled } from "solid-styled-components" + + +export function TrackList(props: { + style?: Solid.JSX.CSSProperties, +}) +{ + const modifyTrack = (newTrack: Project.Track) => { + console.log("modifyTrack", newTrack) + let project = Global.get().project + project.root = Project.upsertTrack(project.root, newTrack) + Global.refresh() + } + + const onClickTrack = (clickedTrack: Project.Track) => { + const project = Global.get().project + const timeline = Global.get().timeline + const prefs = Global.get().prefs + const selectMultiple = timeline.keysDown.has(prefs.timeline.keySelectMultiple) + + if (!selectMultiple) + Timeline.selectionClear(timeline) + + Timeline.selectionAdd(timeline, clickedTrack.id) + timeline.cursor.visible = false + + if (clickedTrack.trackType === "notes") + { + for (const track of project.root.tracks) + { + if (track.trackType !== "notes" && + track.trackType !== "chords") + continue + + const newTrack = { ...track } + newTrack.editable = (track.id === clickedTrack.id) + project.root = Project.upsertTrack(project.root, newTrack) + } + } + + Global.refresh() + } + + const onAddTrack = () => { + const project = Global.get().project + const timeline = Global.get().timeline + const id = project.root.nextId + const track = Project.makeTrackNotes() + project.root = Project.upsertTrack(project.root, track) + Timeline.selectionClear(timeline) + Timeline.selectionAdd(timeline, id) + timeline.cursor.visible = false + + for (const track of project.root.tracks) + { + if (track.trackType !== "notes" && + track.trackType !== "chords") + continue + + const newTrack = { ...track } + newTrack.editable = (track.id === id) + project.root = Project.upsertTrack(project.root, newTrack) + } + + Global.refresh() + } + + const tracks = Solid.createMemo(() => Global.get().project.root.tracks) + const selection = Solid.createMemo(() => Global.get().timeline.selection) + + const trackList = Solid.createMemo(() => { + return tracks().map((track, i) => { + const trackWithAttrb = + track.trackType === "notes" || track.trackType === "chords" ? + track : + undefined + + if (!trackWithAttrb) + return undefined + + const isEditable = !!trackWithAttrb?.editable + const isSelected = selection().has(track.id) + + return onClickTrack(track) } + > +
+ { track.name } +
+ modifyTrack({ ...trackWithAttrb, visible: value })) + } + /> + modifyTrack({ ...trackWithAttrb, mute: !value })) + } + /> + modifyTrack({ ...trackWithAttrb, solo: value })) + } + /> +
+ }) + }) + + return +
+ + { trackList() } + +
+ +
+ +} + + +const Layout = styled.div` + display: grid; + grid-template: auto 1fr auto / 1fr; + width: 20em; + height: 100%; + justify-items: center; +` + + +const ScrollList = styled.div` + width: 100%; + height: 100%; + min-height: 0; + overflow-x: hidden; + overflow-y: scroll; + border-radius: 0.25em; +` + + +const TrackSlot = styled.button<{ + "data-editable": boolean, + "data-selected": boolean, +}>` + display: grid; + grid-template: auto / 1fr repeat(5, auto); + justify-items: start; + justify-content: start; + user-select: none; + cursor: pointer; + + width: 90%; + margin: 0.25em; + padding: 0.25em 0.5em; + border: 2px solid transparent; + border-radius: var(--theme-buttonBorderRadius); + background-color: var(--theme-buttonBkg); + color: inherit; + font-family: inherit; + font-size: inherit; + + &:hover { + background-color: var(--theme-buttonBkgHover); + } + + &:active { + background-color: var(--theme-buttonBkgPress); + } + + &[data-editable=true] { + background-color: var(--theme-buttonBkgSelected); + } + + &[data-selected=true] { + border: 2px solid #fff8; + } +` \ No newline at end of file diff --git a/src/ui/components/Button.tsx b/src/ui/components/Button.tsx new file mode 100644 index 0000000..dc7e572 --- /dev/null +++ b/src/ui/components/Button.tsx @@ -0,0 +1,89 @@ +import * as Solid from "solid-js" +import { styled } from "solid-styled-components" + + +export function Button(props: { + children?: Solid.JSXElement, + style?: Solid.JSX.CSSProperties, + label?: Solid.JSXElement, + disabled?: boolean, + checked?: boolean, + onClick?: () => void, + groupStart?: boolean, + groupMiddle?: boolean, + groupEnd?: boolean, +}) +{ + return + { props.label ?? props.children } + +} + + +const StyledButton = styled.button<{ + "data-checked": boolean, + "data-group-start": boolean, + "data-group-middle": boolean, + "data-group-end": boolean, +}>` + display: inline-block; + justify-self: stretch; + align-self: center; + user-select: none; + cursor: pointer; + + margin: 0.25em; + padding: 0.25em 0.5em; + border: 0; + border-radius: var(--theme-buttonBorderRadius); + background-color: var(--theme-buttonBkg); + color: inherit; + font-family: inherit; + font-size: inherit; + + &:hover { + background-color: var(--theme-buttonBkgHover); + } + + &:active { + background-color: var(--theme-buttonBkgPress); + } + + &:disabled { + background-color: var(--theme-buttonBkgPress); + opacity: 0.5; + cursor: inherit; + } + + &[data-checked=true] { + background-color: var(--theme-buttonBkgSelected); + } + + &[data-group-start=true] { + margin-left: 0.25em; + margin-right: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + &[data-group-middle=true] { + margin-left: 0; + margin-right: 0; + border-radius: 0; + } + + &[data-group-end=true] { + margin-left: 0; + margin-right: 0.25em; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } +` \ No newline at end of file diff --git a/src/ui/components/Checkbox.tsx b/src/ui/components/Checkbox.tsx new file mode 100644 index 0000000..f535cc1 --- /dev/null +++ b/src/ui/components/Checkbox.tsx @@ -0,0 +1,79 @@ +import * as Solid from "solid-js" +import { styled } from "solid-styled-components" + + +export function Checkbox(props: { + children?: Solid.JSXElement, + label?: Solid.JSXElement, + checked?: boolean, + onChange?: (newValue: boolean) => void, + groupStart?: boolean, + groupMiddle?: boolean, + groupEnd?: boolean, +}) +{ + return props.onChange?.(!props.checked) } + > + { props.label ?? props.children } + +} + + +const StyledButton = styled.button<{ + "data-checked": boolean, + "data-group-start": boolean, + "data-group-middle": boolean, + "data-group-end": boolean, +}>` + display: inline-block; + justify-self: stretch; + align-self: center; + user-select: none; + cursor: pointer; + + margin: 0.25em; + padding: 0.25em 0.5em; + border: 0; + border-radius: var(--theme-buttonBorderRadius); + background-color: var(--theme-buttonBkg); + color: inherit; + font-family: inherit; + font-size: inherit; + + &:hover { + background-color: var(--theme-buttonBkgHover); + } + + &:active { + background-color: var(--theme-buttonBkgPress); + } + + &[data-checked=true] { + background-color: var(--theme-buttonBkgSelected); + } + + &[data-group-start=true] { + margin-left: 0.25em; + margin-right: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + &[data-group-middle=true] { + margin-left: 0; + margin-right: 0; + border-radius: 0; + } + + &[data-group-end=true] { + margin-left: 0; + margin-right: 0.25em; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } +` \ No newline at end of file diff --git a/src/ui/components/Select.tsx b/src/ui/components/Select.tsx new file mode 100644 index 0000000..0fba68b --- /dev/null +++ b/src/ui/components/Select.tsx @@ -0,0 +1,73 @@ +import * as Solid from "solid-js" +import { styled } from "solid-styled-components" + + +declare module "solid-js" +{ + namespace JSX + { + interface IntrinsicElements + { + "selectedcontent": HTMLAttributes + } + } +} + + +export function Select(props: { + children?: Solid.JSXElement, + label?: Solid.JSXElement, + value?: string, + onChange?: (newValue: string) => void, + width?: string, +}) +{ + return props.onChange?.(ev.target.value) } + style={{ + width: props.width, + }}> + + { props.children } + +} + + +const StyledSelect = styled.select<{ +}>` + appearance: base-select; + + &::picker(select) { + /*appearance: base-select;*/ + border: 0; + border-radius: var(--theme-buttonBorderRadius); + background-color: var(--theme-buttonBkg); + color: inherit; + font-family: inherit; + font-size: inherit; + } + + display: inline-block; + justify-self: stretch; + align-self: center; + user-select: none; + cursor: pointer; + + margin: 0.25em; + padding: 0.25em 0.5em; + border: 0; + border-radius: var(--theme-buttonBorderRadius); + background-color: var(--theme-buttonBkg); + color: inherit; + font-family: inherit; + font-size: inherit; + + &:hover { + background-color: var(--theme-buttonBkgHover); + } + + &:active { + background-color: var(--theme-buttonBkgPress); + } +` \ No newline at end of file diff --git a/src/ui/components/TextInput.tsx b/src/ui/components/TextInput.tsx new file mode 100644 index 0000000..fe498b2 --- /dev/null +++ b/src/ui/components/TextInput.tsx @@ -0,0 +1,65 @@ +import * as Solid from "solid-js" +import { styled } from "solid-styled-components" + + +export function TextInput(props: { + children?: Solid.JSXElement, + labelBefore?: Solid.JSXElement, + labelAfter?: Solid.JSXElement, + value?: string, + onChange?: (newValue: string) => void, + width?: string, +}) +{ + return + { props.labelBefore } + props.onChange?.(ev.target.value) } + onBlur={ ev => props.onChange?.(ev.target.value) } + style={{ + width: props.width, + }}/> + { props.labelAfter } + +} + + +const Layout = styled.div<{ +}>` + display: inline-block; + justify-self: stretch; + align-self: center; + user-select: none; + + margin: 0.25em; + padding: 0.25em 0.5em; + border: 0; + border-radius: var(--theme-buttonBorderRadius); + background-color: var(--theme-buttonBkg); + color: inherit; + font-family: inherit; + font-size: inherit; + + &:hover { + background-color: var(--theme-buttonBkgHover); + } +` + + +const StyledInput = styled.input<{ +}>` + display: inline-block; + justify-self: stretch; + align-self: stretch; + + margin: 0 0.05em; + padding: 0.25em 0.5em; + border: 0; + border-radius: var(--theme-buttonBorderRadius); + background-color: var(--theme-buttonBkgPress); + color: inherit; + font-family: inherit; + font-size: inherit; + outline: 0; +` \ No newline at end of file diff --git a/src/ui/components/TrackButton.tsx b/src/ui/components/TrackButton.tsx new file mode 100644 index 0000000..c198e27 --- /dev/null +++ b/src/ui/components/TrackButton.tsx @@ -0,0 +1,104 @@ +import * as Solid from "solid-js" +import { styled } from "solid-styled-components" + + +let currentDraggedGroupId: string | undefined = undefined +let currentDraggedValue: boolean = false + + +Solid.createRoot(() => { + const onMouseUp = () => { + currentDraggedGroupId = undefined + } + + window.addEventListener("mouseup", onMouseUp) + Solid.onCleanup(() => { + window.removeEventListener("mouseup", onMouseUp) + }) +}) + + +export function TrackButton(props: { + children?: Solid.JSXElement, + style?: Solid.JSX.CSSProperties, + label?: Solid.JSXElement, + disabled?: boolean, + checked?: boolean, + groupId?: string, + onSet?: (newValue: boolean) => void, +}) +{ + const onMouseDown = Solid.createMemo(() => (ev: MouseEvent) => { + const newValue = !props.checked + props.onSet?.(newValue) + currentDraggedGroupId = props.groupId + currentDraggedValue = newValue + ev.stopPropagation() + }) + + const onMouseEnter = Solid.createMemo(() => (ev: MouseEvent) => { + if (currentDraggedGroupId === undefined || + currentDraggedGroupId !== props.groupId) + return + + if (props.checked === currentDraggedValue) + return + + props.onSet?.(currentDraggedValue) + ev.stopPropagation() + }) + + const onMouseUp = (ev: MouseEvent) => { + currentDraggedGroupId = undefined + ev.stopPropagation() + } + + return + { props.label ?? props.children } + +} + + +const StyledButton = styled.button<{ + "data-checked": boolean, +}>` + display: inline-block; + justify-self: stretch; + align-self: center; + user-select: none; + cursor: pointer; + + margin: 0; + padding: 0.05em 0.05em; + border: 1px solid transparent; + border-radius: var(--theme-buttonBorderRadius); + background-color: var(--theme-buttonBkg); + color: inherit; + font-family: inherit; + font-size: inherit; + + &:hover { + background-color: var(--theme-buttonBkgHover); + } + + &:active { + background-color: var(--theme-buttonBkgPress); + } + + &:disabled { + background-color: var(--theme-buttonBkgPress); + opacity: 0; + cursor: inherit; + } + + &[data-checked=true] { + background-color: var(--theme-buttonBkgSelected2); + } +` \ No newline at end of file diff --git a/src/ui/components/index.ts b/src/ui/components/index.ts new file mode 100644 index 0000000..ce3d30f --- /dev/null +++ b/src/ui/components/index.ts @@ -0,0 +1,5 @@ +export * from "./Button.tsx" +export * from "./Checkbox.tsx" +export * from "./Select.tsx" +export * from "./TextInput.tsx" +export * from "./TrackButton.tsx" \ No newline at end of file diff --git a/src/ui/index.ts b/src/ui/index.ts index 7a475af..0c531cb 100644 --- a/src/ui/index.ts +++ b/src/ui/index.ts @@ -1,8 +1,8 @@ -export * from "./Button" -export * from "./ButtonList" -export * from "./ChordButton" -export * from "./Input" -export * from "./ListBox" -export * from "./Dial" -export * from "./DropdownMenu" -export * from "./LoadingBar" \ No newline at end of file +export * from "./components" +export { InspectorRoot } from "./Inspector.tsx" +export { InspectorInsert } from "./InspectorInsert.tsx" +export { InspectorTempoChange } from "./InspectorTempoChange.tsx" +export { InspectorKeyChange } from "./InspectorKeyChange.tsx" +export { InspectorMeterChange } from "./InspectorMeterChange.tsx" +export { InspectorChord } from "./InspectorChord.tsx" +export { TrackList } from "./TrackList.tsx" \ No newline at end of file diff --git a/src/util/binarySearch.ts b/src/utils/binarySearch.ts similarity index 100% rename from src/util/binarySearch.ts rename to src/utils/binarySearch.ts diff --git a/src/utils/canvasUtils.ts b/src/utils/canvasUtils.ts new file mode 100644 index 0000000..089fae7 --- /dev/null +++ b/src/utils/canvasUtils.ts @@ -0,0 +1,234 @@ +import * as Theory from "../theory" +import * as Prefs from "../prefs" +import * as MathUtils from "./mathUtils" +import Rect from "./rect" + + +const fillPatterns = new Map() + + +function createStripedPattern( + cacheKey: string, + ctx: CanvasRenderingContext2D, + color1: string, + color2: string) +{ + if (fillPatterns.has(cacheKey)) + return fillPatterns.get(cacheKey) + + const canvas = document.createElement("canvas") + canvas.width = 24 + canvas.height = 24 + canvas.style.display = "none" + document.body.appendChild(canvas) + + let ctxPatt = canvas.getContext("2d")! + ctxPatt.fillStyle = color1 + ctxPatt.fillRect(0, 0, 24, 24) + + ctxPatt.fillStyle = color2 + ctxPatt.beginPath() + ctxPatt.moveTo(12, 0) + ctxPatt.lineTo(24, 0) + ctxPatt.lineTo(0, 24) + ctxPatt.lineTo(-12, 24) + + ctxPatt.moveTo(24 + 12, 0) + ctxPatt.lineTo(24 + 24, 0) + ctxPatt.lineTo(24 + 0, 24) + ctxPatt.lineTo(24 - 12, 24) + ctxPatt.fill() + + const pattern = ctx.createPattern(canvas, "repeat")! + fillPatterns.set(cacheKey, pattern) + return pattern +} + + +export function fillStyleForDegree(ctx: CanvasRenderingContext2D, degree: number, external: boolean): any +{ + degree = MathUtils.mod(degree, 7) + + const colorFn = external ? + Theory.Utils.degreeToColorFaded : + Theory.Utils.degreeToColor + + if (Math.floor(degree) === degree) + return colorFn(degree) + + const cacheKey = degree.toString() + (external ? ".ext" : 0) + + if (fillPatterns.has(cacheKey)) + return fillPatterns.get(cacheKey) + + const color1 = colorFn(MathUtils.mod(Math.floor(degree), 7)) + const color2 = colorFn(MathUtils.mod(Math.ceil(degree), 7)) + + return createStripedPattern( + cacheKey, + ctx, + color1, + color2) +} + + +export function fillStyleForOutOfBounds( + ctx: CanvasRenderingContext2D, + prefs: Prefs.Prefs) + : any +{ + return createStripedPattern( + "oob", + ctx, + prefs.timeline.outOfBoundsColor1, + prefs.timeline.outOfBoundsColor2) +} + + +export function drawChord( + ctx: CanvasRenderingContext2D, + rect: Rect, + prefs: Prefs.Prefs, + chord: Theory.Chord, + key: Theory.Key) +{ + const mode = key.scale.metadata!.mode + const fillStyle = fillStyleForDegree( + ctx, + key.degreeForMidi(chord.rootChroma) + mode, + false) + + const ornamentH = 6 + + ctx.fillStyle = fillStyle + + /*ctx.save() + ctx.beginPath() + ctx.roundRect( + rect.x, + rect.y, + rect.w, + rect.h, + 5) + ctx.clip() + ctx.fillRect( + rect.x, + rect.y, + rect.w, + ornamentH) + ctx.fillRect( + rect.x, + rect.y2 - ornamentH, + rect.w, + ornamentH) + ctx.restore()*/ + + ctx.beginPath() + ctx.roundRect( + rect.x, + rect.y, + rect.w, + rect.h, + 5) + ctx.fill() + + ctx.fillStyle = "#fffe" + ctx.fillRect( + rect.x, + rect.y + ornamentH, + rect.w, + rect.h - ornamentH * 2) + + const maxWidth = rect.w * 0.9 + if (maxWidth < 8) + return + + const chordStr = chord.str(key) + + ctx.fillStyle = "#000" + drawChordName( + ctx, + `${prefs.timeline.fontWeightChord} ${rect.h * 0.5}px ${prefs.timeline.fontNameChord}`, + `${prefs.timeline.fontWeightChord} ${rect.h * 0.25}px ${prefs.timeline.fontNameChord}`, + chordStr.romanBase, + chordStr.romanSup, + chordStr.romanSub, + rect.xCenter, + rect.yCenter + rect.h * (0.05 - 0.1), + rect.h * 0.5, + rect.h * -0.15, + rect.h * 0.075, + maxWidth) + + drawChordName( + ctx, + `${prefs.timeline.fontWeightChord} ${rect.h * 0.2}px ${prefs.timeline.fontNameChord}`, + `${prefs.timeline.fontWeightChord} ${rect.h * 0.15}px ${prefs.timeline.fontNameChord}`, + chordStr.nameBase, + chordStr.nameSup, + chordStr.nameSub, + rect.xCenter, + rect.yCenter + rect.h * (0.05 + 0.22), + rect.h * 0.5, + rect.h * -0.05, + rect.h * 0.05, + maxWidth) +} + + +function drawChordName( + ctx: CanvasRenderingContext2D, + fontBase: string, + fontSupSub: string, + strBase: string, + strSup: string, + strSub: string, + xCenter: number, + yCenter: number, + height: number, + supHeightOffset: number, + subHeightOffset: number, + maxWidth: number) +{ + ctx.textAlign = "left" + ctx.textBaseline = "middle" + + ctx.font = fontBase + const baseMetrics = ctx.measureText(strBase) + ctx.font = fontSupSub + const supMetrics = ctx.measureText(strSup) + const subMetrics = ctx.measureText(strSub) + + let baseWidth = baseMetrics.width + let supSubWidth = Math.max(supMetrics.width, subMetrics.width) + if (baseWidth + supSubWidth > maxWidth * 4) + return + + if (baseWidth + supSubWidth > maxWidth) + { + const supSubWidthProportion = supSubWidth / (baseWidth + supSubWidth) + baseWidth = maxWidth * (1 - supSubWidthProportion) + supSubWidth = maxWidth * supSubWidthProportion + } + + const strTotalWidth = baseWidth + supSubWidth + + ctx.fillText( + strSup, + xCenter - strTotalWidth / 2 + baseWidth, + yCenter + supHeightOffset, + supSubWidth) + + ctx.fillText( + strSub, + xCenter - strTotalWidth / 2 + baseWidth, + yCenter + subHeightOffset, + supSubWidth) + + ctx.font = fontBase + ctx.fillText( + strBase, + xCenter - strTotalWidth / 2, + yCenter, + baseWidth) +} \ No newline at end of file diff --git a/src/util/listOfPoints.ts b/src/utils/listOfPoints.ts similarity index 98% rename from src/util/listOfPoints.ts rename to src/utils/listOfPoints.ts index 0b82697..1b0e3a5 100644 --- a/src/util/listOfPoints.ts +++ b/src/utils/listOfPoints.ts @@ -1,8 +1,8 @@ -import Rational from "./rational" -import Range from "./range" -import BinarySearch from "./binarySearch" -import Immutable from "immutable" import assert from "assert" +import Immutable from "immutable" +import Rational from "./rational.ts" +import Range from "./range.ts" +import BinarySearch from "./binarySearch.ts" interface Element diff --git a/src/util/listOfRanges.ts b/src/utils/listOfRanges.ts similarity index 98% rename from src/util/listOfRanges.ts rename to src/utils/listOfRanges.ts index d322036..ccc21be 100644 --- a/src/util/listOfRanges.ts +++ b/src/utils/listOfRanges.ts @@ -1,8 +1,8 @@ -import Rational from "./rational" -import Range from "./range" -import BinarySearch from "./binarySearch" -import Immutable from "immutable" import assert from "assert" +import * as Immutable from "immutable" +import Rational from "./rational.ts" +import Range from "./range.ts" +import BinarySearch from "./binarySearch.ts" interface Element diff --git a/src/util/mathUtils.ts b/src/utils/mathUtils.ts similarity index 100% rename from src/util/mathUtils.ts rename to src/utils/mathUtils.ts diff --git a/src/util/midi.ts b/src/utils/midi.ts similarity index 98% rename from src/util/midi.ts rename to src/utils/midi.ts index ad32bb6..79d6fc4 100644 --- a/src/util/midi.ts +++ b/src/utils/midi.ts @@ -1,5 +1,5 @@ -import { BinaryReader } from "./binaryReader" -import { BinaryWriter } from "./binaryWriter" +import { BinaryReader } from "../../src_old/util/binaryReader" +import { BinaryWriter } from "../../src_old/util/binaryWriter" import Rational from "./rational" diff --git a/src/util/range.ts b/src/utils/range.ts similarity index 99% rename from src/util/range.ts rename to src/utils/range.ts index c74700c..303d257 100644 --- a/src/util/range.ts +++ b/src/utils/range.ts @@ -1,4 +1,4 @@ -import Rational from "./rational" +import Rational from "./rational.ts" export default class Range diff --git a/src/util/rational.ts b/src/utils/rational.ts similarity index 86% rename from src/util/rational.ts rename to src/utils/rational.ts index 5db5f08..b18c222 100644 --- a/src/util/rational.ts +++ b/src/utils/rational.ts @@ -12,7 +12,7 @@ export default class Rational constructor(numerator: number = 0, denominator: number = 1) { - if (denominator == 0) + if (denominator === 0) throw "denominator zero" if (!isFinite(numerator) || !isFinite(denominator)) @@ -145,12 +145,12 @@ export default class Rational stretch(offset: Rational, pivot: Rational, origin: Rational): Rational { - let dist = origin.subtract(pivot) - if (dist.numerator == 0) + const dist = origin.subtract(pivot) + if (dist.numerator === 0) return this - let p = this.subtract(pivot).divide(dist) - let move = origin.add(offset).subtract(pivot).divide(dist) + const p = this.subtract(pivot).divide(dist) + const move = origin.add(offset).subtract(pivot).divide(dist) return pivot.add(dist.multiply(p).multiply(move)) } @@ -158,14 +158,14 @@ export default class Rational isZero(): boolean { - return this.numerator == 0 + return this.numerator === 0 } compare(other: Rational): number { - let thisNumerator = this.numerator * other.denominator - let otherNumerator = other.numerator * this.denominator + const thisNumerator = this.numerator * other.denominator + const otherNumerator = other.numerator * this.denominator if (thisNumerator < otherNumerator) return -1 @@ -178,13 +178,13 @@ export default class Rational equalTo(other: Rational): boolean { - return this.compare(other) == 0 + return this.compare(other) === 0 } notEqualTo(other: Rational): boolean { - return this.compare(other) != 0 + return this.compare(other) !== 0 } @@ -223,7 +223,7 @@ export default class Rational if (b === null) return a - if (a.compare(b!) > 0) + if (a.compare(b) > 0) return a else return b @@ -241,20 +241,20 @@ export default class Rational if (b === null) return a - if (a.compare(b!) < 0) + if (a.compare(b) < 0) return a else return b } - max(other: Rational | null): Rational + max(other: Rational | null): Rational { return Rational.max(this, other)! } - min(other: Rational | null): Rational + min(other: Rational | null): Rational { return Rational.min(this, other)! } @@ -279,7 +279,7 @@ export default class Rational trySimplifyInPlaceBy(divider: number) { - while ((this.numerator % divider) == 0 && (this.denominator % divider) == 0) + while ((this.numerator % divider) === 0 && (this.denominator % divider) === 0) { this.numerator /= divider this.denominator /= divider @@ -289,10 +289,10 @@ export default class Rational toString(): string { - let integer = Math.floor(this.numerator / this.denominator) - let numerator = this.numerator % this.denominator + const integer = Math.floor(this.numerator / this.denominator) + const numerator = this.numerator % this.denominator - if (numerator == 0) + if (numerator === 0) return integer.toString() else return integer.toString() + " + " + numerator.toString() + "/" + this.denominator.toString() diff --git a/src/util/rect.ts b/src/utils/rect.ts similarity index 92% rename from src/util/rect.ts rename to src/utils/rect.ts index cade3a1..624a353 100644 --- a/src/util/rect.ts +++ b/src/utils/rect.ts @@ -168,4 +168,14 @@ export default class Rect this.y2 >= other.y && this.y < other.y2 } + + + merge(other: Rect): Rect + { + return Rect.fromVertices( + Math.min(this.x1, other.x1), + Math.min(this.y1, other.y1), + Math.max(this.x2, other.x2), + Math.max(this.y2, other.y2)) + } } \ No newline at end of file diff --git a/src_old/App.tsx b/src_old/App.tsx index 889d9bd..d5a96c4 100644 --- a/src_old/App.tsx +++ b/src_old/App.tsx @@ -1,153 +1,140 @@ import React from "react" -import Dockable from "./dockable/Dockable" -import * as DockableData from "./dockable/state" -import { EditorContent } from "./editor2/EditorContent" -import { AppState, AppReducer, ContentManager, AppManager } from "./AppState" -import { AppContext } from "./AppContext" -import EditorState from "./editor2/editor" -import Project from "./project/project2" -import Immutable from "immutable" -import Rect from "./util/rect" -import MenuBar from "./popup/MenuBar" -import Popup from "./popup/Popup" -import InspectorContent from "./editor2/InspectorContent" -import TrackInstrumentContent from "./editor2/TrackInstrumentContent" -import { usePlaybackController } from "./playback/PlaybackController" -import { useSoundfontLibrary } from "./playback/library" - - -const initialAppState = AppReducer.makeNew() - - -export default function App(props: {}) +import * as Dockable from "./dockable" +import * as Command from "./command" +import * as Project from "./project" +import * as Playback from "./playback" +import * as Prefs from "./prefs" +import * as Popup from "./popup" +import * as Menubar from "./menubar" +import * as UI from "./ui" +import { useRefState } from "./util/refState" +import PlaybackToolbar from "./PlaybackToolbar" +import MenuFile from "./MenuFile" +import MenuEdit from "./MenuEdit" +import MenuWindow from "./MenuWindow" +import "./types" + + +let initializedGlobals = false + + +export default function App() { - const rootRef = React.useRef(null) - - const [, setDummy] = React.useState(0) - const appStateRef = React.useRef(initialAppState) - const appDispatch = React.useCallback((newState: AppState) => + if (!initializedGlobals) { - //console.log(newState) - appStateRef.current = newState - setDummy(dummy => dummy + 1) + initializedGlobals = true + Prefs.initGlobal() + Project.initGlobal() + Playback.initGlobal() + Dockable.initGlobal() + Popup.initGlobal() + } - }, []) + Project.useGlobal() + Playback.useGlobal() + Dockable.useGlobal() + Popup.useGlobal() - const setDockableRoot = (newRoot: DockableData.State) => appDispatch({ - ...appStateRef.current, - dockableRoot: newRoot, - }) + const [version, setVersion] = React.useState("") + + React.useEffect(() => + { + ;(async () => + { + const versionFile = await fetch("build/build_version.txt") + const versionTxt = await versionFile.text() + if (versionTxt.startsWith("v0-")) + setVersion("v0." + versionTxt.match(".*?\-(.*?)\-")![1]) + })() - const appManager = new AppManager( - () => appStateRef.current, - (newState) => appStateRef.current = newState, - (newState) => appDispatch(newState)) + }, []) - const playbackController = usePlaybackController(appManager) - const soundfontLibrary = useSoundfontLibrary(appManager) React.useEffect(() => { - if (!rootRef.current) - return - - const onResize = () => + window.addEventListener("keydown", (ev: KeyboardEvent) => { - const elemRect = rootRef.current!.getBoundingClientRect() - - appDispatch({ - ...appStateRef.current, - dockableRect: new Rect( - elemRect.x, - elemRect.y, - elemRect.width, - elemRect.height), - }) - } + if (document.activeElement && document.activeElement.tagName == "INPUT") + return - onResize() - - window.addEventListener("resize", onResize) + const key = ev.key.toLowerCase() - return () => - { - window.removeEventListener("resize", onResize) - } + for (const command of Command.allCommands) + { + if (!command.shortcut) + continue - }, [rootRef.current]) + if (command.isShortcutAvailable && !command.isShortcutAvailable()) + continue + if (command.isAvailable && !command.isAvailable({})) + continue - return -
- - -
- - - - { !appStateRef.current.popup ? null : - + for (const shortcut of command.shortcut) + { + if (!!shortcut.ctrl !== ev.ctrlKey) + continue + + if (!!shortcut.shift !== ev.shiftKey) + continue + + if (key !== shortcut.key) + continue + + //console.log("handled keyboard command: ", command.name) + command.func({}) + ev.preventDefault() + ev.stopPropagation() + return + } } + }) - -
- -} + }, []) -function contentTypeToComponent(type: string): any -{ - switch (type) - { - case "editor": return EditorContent - case "editorNotes": return EditorContent - case "inspector": return InspectorContent - case "trackInstrument": return TrackInstrumentContent + return <> +
- default: - throw "invalid content type" - } -} + + + + + + + How to use the app + + + { version } + + + + { !Playback.global.synthLoading ? null : + + } + -function contentTypeToTitle(type: string): any -{ - switch (type) - { - case "editor": return "Project" - case "editorNotes": return "Note Track" - case "inspector": return "Inspector" - case "trackInstrument": return "Instrument Select" +
- default: - throw "invalid content type" - } + { !Popup.global.elem ? null : + + } + } \ No newline at end of file diff --git a/src_old/AppContext.ts b/src_old/AppContext.ts deleted file mode 100644 index fc1b498..0000000 --- a/src_old/AppContext.ts +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react" -import { AppManager } from "./AppState" - - -interface AppContextProps -{ - appManager: AppManager -} - - -export const AppContext = React.createContext(null) - - -export function useAppManager(): AppManager -{ - return React.useContext(AppContext)!.appManager -} \ No newline at end of file diff --git a/src_old/AppState.ts b/src_old/AppState.ts deleted file mode 100644 index f322793..0000000 --- a/src_old/AppState.ts +++ /dev/null @@ -1,429 +0,0 @@ -import DockableData, * as Dock from "./dockable/state" -import Project from "./project/project2" -import Immutable from "immutable" -import Rect from "./util/rect" -import Editor from "./editor2/editor" -import Dockable from "./dockable/Dockable" -import Rational from "./util/rational" -import { SflibMeta } from "./playback/library" - - -export interface AppState -{ - contentIdNext: number - dockableRect: Rect - dockableRoot: Dock.State - dockableContents: Immutable.Map - - project: Project - selection: Immutable.Set - playback: - { - playing: boolean - time: Rational - timeAsFloat: number - timeStart: Rational - } - - sflib: SflibMeta - - popup: null | - { - rect: Rect - elem: any - props: any - } - - prefs: - { - editor: - { - bkgColor: string - trackVBorderColor: string - trackHBorderColor: string - - selectionCursorColor: string - selectionBkgColor: string - playbackCursorColor: string - trackSeparatorColor: string - - measureColor: string - submeasureColor: string - halfSubmeasureColor: string - measureAlternateBkgColor: string - - octaveDividerColor: string - noteRowAlternateBkgColor: string - - meterChangeColor: string - keyChangeColor: string - - keyPan: string - keyDraw: string - keySelectMultiple: string - keySelectRect: string - - mouseDragXLockedDistance: number - mouseDragYLockedDistance: number - - mouseEdgeScrollThreshold: number - mouseEdgeScrollSpeed: number - } - } -} - - -export class AppReducer -{ - static makeNew(): AppState - { - let root = DockableData.makeRoot() - root = DockableData.addPanel(root, 1, Dock.DockMode.Full, 1) - - return { - contentIdNext: 2, - dockableRect: new Rect(0, 0, 0, 0), - dockableRoot: root, - dockableContents: Immutable.Map() - .set(1, { - type: "editor", - state: Editor.makeNewFull(), - }), - - popup: null, - - project: Project.getDefault(), - selection: Immutable.Set(), - playback: { - playing: false, - time: new Rational(0), - timeAsFloat: 0, - timeStart: new Rational(0), - }, - - sflib: { - ready: false, - collections: [], - }, - - prefs: { - editor: { - bkgColor: "#000",//"#29242e", - trackVBorderColor: "#888", - trackHBorderColor: "#888", - - selectionCursorColor: "#0af", - selectionBkgColor: "#024", - playbackCursorColor: "#f00", - trackSeparatorColor: "#aaa", - - measureColor: "#444", - submeasureColor: "#222", - halfSubmeasureColor: "#111", - measureAlternateBkgColor: "#fff1", - - octaveDividerColor: "#444", - noteRowAlternateBkgColor: "#222",//"#19141e", - - meterChangeColor: "#0cf", - keyChangeColor: "#f0c", - - keyPan: " ", - keyDraw: "a", - keySelectMultiple: "control", - keySelectRect: "shift", - - mouseDragXLockedDistance: 10, - mouseDragYLockedDistance: 10, - - mouseEdgeScrollThreshold: 10, - mouseEdgeScrollSpeed: 1, - }, - }, - } - } - - - static merge(state: AppState, newState: Partial): AppState - { - return { - ...state, - ...newState, - } - } - - - static getContent(state: AppState, contentId: number): any - { - return state.dockableContents.get(contentId)!.state - } - - - static setContent(state: AppState, contentId: number, newState: any): AppState - { - return { - ...state, - dockableContents: state.dockableContents.set(contentId, - { - ...state.dockableContents.get(contentId)!, - state: newState, - }), - } - } - - - static mergeContent(state: AppState, contentId: number, newState: any): AppState - { - return AppReducer.setContent(state, contentId, { - ...AppReducer.getContent(state, contentId), - ...newState, - }) - } - - - static createTab(appState: AppState, nextToContentId: number, type: string, state: any): AppState - { - const panel = DockableData.findPanelWithContent(appState.dockableRoot.rootPanel, nextToContentId) - if (!panel) - return appState - - return { - ...appState, - contentIdNext: appState.contentIdNext + 1, - dockableRoot: DockableData.addPanel( - appState.dockableRoot, - panel.id, - Dock.DockMode.Full, - appState.contentIdNext), - dockableContents: appState.dockableContents.set(appState.contentIdNext, - { - type, - state, - }), - } - } - - - static createOrUpdateTab(appState: AppState, nextToContentId: number, type: string, state: any): AppState - { - const updatablePanel = DockableData.findPanelWithType(appState.dockableRoot.rootPanel, type, appState.dockableContents) - if (updatablePanel) - { - const contentId = updatablePanel.contentIds.find(c => appState.dockableContents.get(c)!.type == type)! - const contentIndex = updatablePanel.contentIds.findIndex(c => c == contentId) - - return { - ...appState, - dockableRoot: DockableData.modifyPanelFromRoot( - appState.dockableRoot, updatablePanel.id, (oldPanel) => - { - return { ...oldPanel, - curContent: contentIndex, - } - }), - dockableContents: appState.dockableContents.set(contentId, - { - type, - state, - }) - } - } - - const panel = DockableData.findPanelWithContent(appState.dockableRoot.rootPanel, nextToContentId) - if (!panel) - return appState - - return { - ...appState, - contentIdNext: appState.contentIdNext + 1, - dockableRoot: DockableData.addPanel( - appState.dockableRoot, - panel.id, - Dock.DockMode.Full, - appState.contentIdNext), - dockableContents: appState.dockableContents.set(appState.contentIdNext, - { - type, - state, - }), - } - } - - - static createFloating(appState: AppState, type: string, state: any, rect: Rect): AppState - { - return { - ...appState, - contentIdNext: appState.contentIdNext + 1, - dockableRoot: DockableData.addFloatingPanel( - appState.dockableRoot, - rect, - appState.contentIdNext), - dockableContents: appState.dockableContents.set(appState.contentIdNext, - { - type, - state, - }), - } - } - - - static removeFloating(appState: AppState, type: string): AppState - { - let root = appState.dockableRoot - for (const [key, value] of appState.dockableContents) - { - if (value.type === type) - root = DockableData.removeFloatingContent(root, key) - } - - return { - ...appState, - contentIdNext: appState.contentIdNext + 1, - dockableRoot: root, - } - } - - - static createPopup(appState: AppState, rect: Rect, elem: any, props: any): AppState - { - return { - ...appState, - popup: { - rect, - elem, - props, - } - } - } - - - static removePopup(appState: AppState): AppState - { - return { - ...appState, - popup: null, - } - } -} - - -export class AppManager -{ - getAppStateFn: () => AppState - setAppStateFn: (newState: AppState) => void - dispatchFn: (state: AppState) => void - - - constructor( - getAppState: () => AppState, - setAppState: (newState: AppState) => void, - dispatch: (state: AppState) => void) - { - this.getAppStateFn = getAppState - this.setAppStateFn = setAppState - this.dispatchFn = dispatch - } - - - get appState(): AppState - { - return this.getAppStateFn() - } - - - set appState(newState: AppState) - { - this.setAppStateFn(newState) - } - - - dispatch() - { - this.dispatchFn(this.appState) - } - - - mergeAppState(newState: Partial) - { - this.appState = { - ...this.appState, - ...newState, - } - } - - - makeContentManager(contentId: number) - { - return new ContentManager(this, contentId) - } -} - - -export class ContentManager -{ - appManager: AppManager - contentId: number - - - constructor(appManager: AppManager, contentId: number) - { - this.appManager = appManager - this.contentId = contentId - } - - - get appState(): AppState - { - return this.appManager.appState - } - - - set appState(newState: AppState) - { - this.appManager.appState = newState - } - - - dispatch() - { - this.appManager.dispatch() - } - - - mergeAppState(newState: Partial) - { - this.appManager.appState = { - ...this.appManager.appState, - ...newState, - } - } - - - get contentState(): T - { - return this.appManager.appState.dockableContents.get(this.contentId)!.state - } - - - set contentState(newState: T) - { - this.appManager.appState = { - ...this.appManager.appState, - dockableContents: this.appManager.appState.dockableContents.set(this.contentId, - { - ...this.appManager.appState.dockableContents.get(this.contentId)!, - state: newState, - }), - } - } - - - mergeContentState(newState: Partial) - { - this.contentState = { - ...this.contentState, - ...newState, - } - } -} \ No newline at end of file diff --git a/src/MenuEdit.tsx b/src_old/MenuEdit.tsx similarity index 100% rename from src/MenuEdit.tsx rename to src_old/MenuEdit.tsx diff --git a/src/MenuFile.tsx b/src_old/MenuFile.tsx similarity index 100% rename from src/MenuFile.tsx rename to src_old/MenuFile.tsx diff --git a/src/MenuWindow.tsx b/src_old/MenuWindow.tsx similarity index 100% rename from src/MenuWindow.tsx rename to src_old/MenuWindow.tsx diff --git a/src/PlaybackToolbar.tsx b/src_old/PlaybackToolbar.tsx similarity index 100% rename from src/PlaybackToolbar.tsx rename to src_old/PlaybackToolbar.tsx diff --git a/src_old/command/command.ts b/src_old/command/command.ts new file mode 100644 index 0000000..a04dc08 --- /dev/null +++ b/src_old/command/command.ts @@ -0,0 +1,23 @@ +export interface Command +{ + name: string + icon?: string + shortcut?: CommandShortcut[] + isShortcutAvailable?: () => boolean + isAvailable?: (args: CommandArguments) => boolean + func: (args: CommandArguments) => Promise +} + + +export interface CommandShortcut +{ + ctrl?: boolean + shift?: boolean + key: string +} + + +export interface CommandArguments +{ + +} \ No newline at end of file diff --git a/src/command/convertNotesToChords.ts b/src_old/command/convertNotesToChords.ts similarity index 100% rename from src/command/convertNotesToChords.ts rename to src_old/command/convertNotesToChords.ts diff --git a/src_old/command/file.ts b/src_old/command/file.ts new file mode 100644 index 0000000..417c71f --- /dev/null +++ b/src_old/command/file.ts @@ -0,0 +1,227 @@ +import * as Command from "../command" +import * as Project from "../project" +import * as Playback from "../playback" + + +function confirmDiscard() +{ + if (!Project.isUnsaved()) + return true + + return window.confirm("Discard current song?") +} + + +function isFileSystemAccessApiAvailable(showAlert?: boolean) +{ + if ("showOpenFilePicker" in window) + return true; + + if (showAlert) + { + (window as Window).alert( + "Your browser doesn't support the File System Access API.\n\n" + + "Use the other commands labeled as \"Browser\", or " + + "switch to a browser with support to that API, like Chrome.") + } + + return false; +} + + +export function openFromFile(filename: string, fileHandle: FileSystemFileHandle | null, bytes: Uint8Array): boolean +{ + if (filename.endsWith(".ttproj") || filename.endsWith(".json")) + { + const text = new TextDecoder("utf-8").decode(bytes) + const json = JSON.parse(text) + Project.open(Project.jsonImport(json)) + Project.setFileHandleForSave(fileHandle) + } + else if (filename.endsWith(".mid")) + { + Project.open(Project.midiImport(bytes)) + Project.setFileHandleForSave(null) + } + else + { + window.alert("Unrecognized file format!") + return false + } + + return true +} + + +export const newProject: Command.Command = +{ + name: "New Project", + icon: "📄", + func: async () => + { + if (!confirmDiscard()) + return + + Project.setNew() + Playback.setPlaying(false) + } +} + + +export const openFile: Command.Command = +{ + name: "Open...", + icon: "📂", + shortcut: [{ ctrl: true, key: "o" }], + isShortcutAvailable: () => isFileSystemAccessApiAvailable(), + func: async () => + { + if (!confirmDiscard()) + return + + if (!isFileSystemAccessApiAvailable(true)) + return + + const handles = await window.showOpenFilePicker({ + multiple: false, + types: [{ + description: "Supported files", + accept: { + "application/json": [".ttproj", ".json"], + "audio/midi": [".mid"], + }, + }] + }) + const handle = handles[0] + const file = await handle.getFile() + const bytes = await file.arrayBuffer() + openFromFile(file.name, handle, new Uint8Array(bytes)) + } +} + + +export const saveProjectAs: Command.Command = +{ + name: "Save Project As...", + icon: "💾", + shortcut: [{ ctrl: true, shift: true, key: "s" }], + isShortcutAvailable: () => isFileSystemAccessApiAvailable(), + func: async () => + { + if (!isFileSystemAccessApiAvailable(true)) + return + + const handle = await window.showSaveFilePicker({ + types: [{ + description: "Project file", + accept: { "application/json": [".ttproj", ".json"] }, + }], + }) + const jsonStr = Project.jsonExport(Project.global.project) + const writer = await handle.createWritable() + await writer.write(jsonStr) + await writer.close() + Project.setFileHandleForSave(handle) + Project.markAsSaved() + } +} + + +export const saveProject: Command.Command = +{ + name: "Save Project", + icon: "💾", + shortcut: [{ ctrl: true, key: "s" }], + isShortcutAvailable: () => isFileSystemAccessApiAvailable(), + func: async (args) => + { + if (!isFileSystemAccessApiAvailable(true)) + return + + if (!Project.global.curFileHandleForSave) + { + saveProjectAs.func(args) + return + } + + const jsonStr = Project.jsonExport(Project.global.project) + const writer = await (Project.global.curFileHandleForSave as any).createWritable() + await writer.write(jsonStr) + await writer.close() + Project.markAsSaved() + } +} + + +export const openFileBrowser: Command.Command = +{ + name: "[Browser] Open...", + icon: "📂", + shortcut: [{ ctrl: true, key: "o" }], + isShortcutAvailable: () => !isFileSystemAccessApiAvailable(), + func: async () => + { + if (!confirmDiscard()) + return + + document.getElementById("inputOpenFile")!.click() + } +} + + +export const downloadProjectBrowser: Command.Command = +{ + name: "[Browser] Download Project", + icon: "📥", + shortcut: [{ ctrl: true, key: "s" }], + isShortcutAvailable: () => !isFileSystemAccessApiAvailable(), + func: async () => + { + const jsonStr = Project.jsonExport(Project.global.project) + + const element = document.createElement("a") + element.setAttribute("href", "data:text/json;charset=utf-8," + encodeURIComponent(jsonStr)) + element.setAttribute("download", "song.ttproj") + + element.style.display = "none" + document.body.appendChild(element) + element.click() + document.body.removeChild(element) + } +} + + +export const previewProjectBrowser: Command.Command = +{ + name: "[Browser] Preview Project", + icon: "📥", + func: async () => + { + const jsonStr = Project.jsonExport(Project.global.project) + + const newWindow = window.open()! + newWindow.document.write("") + newWindow.document.write(jsonStr) + newWindow.document.write("") + } +} + + +export const downloadMidiBrowser: Command.Command = +{ + name: "[Browser] Download MIDI", + icon: "📥", + func: async () => + { + const bytes = Project.midiExport(Project.global.project) + + const element = document.createElement("a") + element.setAttribute("href", "data:audio/midi;base64," + btoa(String.fromCharCode(...bytes))) + element.setAttribute("download", "song.mid") + + element.style.display = "none" + document.body.appendChild(element) + element.click() + document.body.removeChild(element) + } +} \ No newline at end of file diff --git a/src/command/history.ts b/src_old/command/history.ts similarity index 100% rename from src/command/history.ts rename to src_old/command/history.ts diff --git a/src_old/command/index.ts b/src_old/command/index.ts new file mode 100644 index 0000000..d13a27b --- /dev/null +++ b/src_old/command/index.ts @@ -0,0 +1,31 @@ +export * from "./command" +export * from "./file" +export * from "./history" +export * from "./playback" +export * from "./convertNotesToChords" + + +import * as File from "./file" +import * as History from "./history" +import * as Playback from "./playback" +import { convertNotesToChords } from "./convertNotesToChords" + + +export const allCommands = +[ + File.newProject, + File.openFile, + File.saveProject, + File.saveProjectAs, + File.openFileBrowser, + File.downloadProjectBrowser, + File.previewProjectBrowser, + File.downloadMidiBrowser, + + History.undo, + History.redo, + + Playback.togglePlayback, + + convertNotesToChords, +] \ No newline at end of file diff --git a/src/command/playback.ts b/src_old/command/playback.ts similarity index 100% rename from src/command/playback.ts rename to src_old/command/playback.ts diff --git a/src/dockable/Container.tsx b/src_old/dockable/Container.tsx similarity index 100% rename from src/dockable/Container.tsx rename to src_old/dockable/Container.tsx diff --git a/src/dockable/_global.ts b/src_old/dockable/_global.ts similarity index 100% rename from src/dockable/_global.ts rename to src_old/dockable/_global.ts diff --git a/src/dockable/index.ts b/src_old/dockable/index.ts similarity index 100% rename from src/dockable/index.ts rename to src_old/dockable/index.ts diff --git a/src/dockable/state.ts b/src_old/dockable/state.ts similarity index 100% rename from src/dockable/state.ts rename to src_old/dockable/state.ts diff --git a/src_old/editor/editor.js b/src_old/editor/editor.js deleted file mode 100644 index 47904df..0000000 --- a/src_old/editor/editor.js +++ /dev/null @@ -1,1896 +0,0 @@ -import { default as Immutable } from "immutable" -import Track from "./track.js" -import Theory from "../theory.ts" -import Rational from "../util/rational.js" -import Range from "../util/range.js" -import Rect from "../util/rect.js" -import Project from "../project/project.js" -import MathUtils from "../util/math.js" - - -export default class Editor -{ - static actionPan = 0x01 - static actionSelectCursor = 0x02 - static actionSelectRect = 0x04 - static actionDraw = 0x08 - static actionDragTime = 0x10 - static actionDragPitchRow = 0x20 - static actionStretchTimeStart = 0x40 - static actionStretchTimeEnd = 0x80 - - static keyPan = " " - static keyDraw = "a" - static keySelectMultiple = "control" - static keySelectRect = "shift" - - - static reduce(state, action) - { - const shouldLog = ( - action.type !== "playbackStep" && - action.type !== "mouseMove" && - action.type !== "keyDown" && - action.type !== "keyUp" && - action.type !== "keyCommand" - ) - - if (shouldLog) - { - console.log("Editor.oldState", state) - console.log("Editor.action", action) - } - - const reducer = Editor["reduce_" + action.type] - - if (reducer) - { - const newState = reducer(state, action) - - if (shouldLog) - { - console.log("Editor.newState", newState) - console.log("") - } - - return newState - } - else - { - console.error("unhandled Editor.action", action) - return state - } - } - - - static reduce_init(state, action) - { - state = { - w: 0, - h: 0, - - tracks: [], - - timeSnap: new Rational(1, 16), - timeSnapBase: new Rational(1, 16), - timeScale: 200, - timeScroll: -1, - - insert: { - nearPitch: 60, - duration: new Rational(1, 4), - }, - - undoData: { - stack: [], - pointer: -1, - }, - - clipboard: { - elems: new Immutable.Map(), - }, - - playback: { - playing: false, - timeAsFloat: 0, - time: new Rational(0), - startTime: null, - }, - - cursor: { - visible: true, - time1: new Rational(0), - time2: new Rational(0), - track1: 0, - track2: 0, - }, - - rectSelection: null, - - mouse: { - down: false, - action: 0, - dragOrig: null, - drag: null, - x: 0, - y: 0, - xPrev: 0, - yPrev: 0, - hover: null, - draw: null, - lastDownDate: new Date(), - lastWheelDate: new Date(), - }, - - keys: {}, - pendingKeyCommandFinish: false, - - soundPreview: null, - soundPreviewPrev: null, - - prefs: - { - doubleClickThresholdMs: 250, - mouseEdgeScrollThreshold: 60, - mouseEdgeScrollSpeed: 1, - cursorEdgeScrollThreshold: new Rational(4), - cursorEdgeScrollSpeed: 16, - - backgroundColor: "#29242e", - selectionCursorColor: "#0af", - playbackCursorColor: "#f00", - trackSeparatorColor: "#aaa", - submeasureColor: "#0008", - halfSubmeasureColor: "#0002", - meterChangeColor: "#0cf", - keyChangeColor: "#f0c", - } - } - - state = Editor.refreshProjectRange(state) - return Editor.undoPointAdd(state) - } - - - static reduce_set(state, action) - { - state = { ...state, - ...action.state, - } - - return state - } - - - static reduce_resize(state, action) - { - state = { ...state, - w: action.w, - h: action.h, - tracks: [ ...state.tracks ], - } - - let trackY = 0 - for (let tr = 0; tr < state.tracks.length; tr++) - { - if (state.tracks[tr].kind == "notes") - state.tracks[tr] = { ...state.tracks[tr], rect: new Rect(0, trackY, state.w, state.h - trackY) } - else if (state.tracks[tr].kind == "chords") - state.tracks[tr] = { ...state.tracks[tr], rect: new Rect(0, trackY, state.w, 54) } - else - state.tracks[tr] = { ...state.tracks[tr], rect: new Rect(0, trackY, state.w, 44) } - - trackY += state.tracks[tr].rect.h - } - - return state - } - - - static reduce_projectSet(state, action) - { - state = Editor.undoPointAdd(state) - - state = { ...state, - project: action.project, - } - - return state - } - - - static reduce_projectLoad(state, action) - { - state = { ...state, - project: action.project, - } - - state = Editor.selectionClear(state) - state = Editor.reduce_clearUndoStack(state, {}) - state = Editor.reduce_rewind(state, {}) - state = Editor.reduce_playbackSet(state, { playing: false }) - return state - } - - - static reduce_rewind(state, action) - { - if (state.playback.playing) - { - state = Editor.reduce_playbackSet(state, { playing: true, time: state.project.range.start }) - } - else - { - state = Editor.Cursor.setVisible(state, true) - state = Editor.Cursor.place(state, state.project.range.start, null) - state = Editor.scrollTimeIntoView(state, state.project.range.start) - } - - return state - } - - - static reduce_trackAdd(state, action) - { - state = Track.handlerForTrackKind(action.kind).init(state, action) - state = Editor.Cursor.place(state, null, state.tracks.length - 1) - - return Editor.reduce_resize(state, { type: "resize", w: state.w, h: state.h }) - } - - - static reduce_playbackSet(state, action) - { - if (state.mouse.down) - state = Editor.reduce_mouseUp(state, {}) - - const time = action.time || state.playback.time - - state = { ...state, - playback: { ...state.playback, - playing: action.playing, - time, - timeAsFloat: time.asFloat(), - startTime: (action.playing ? time : null), - }, - } - - if (action.playing) - state = Editor.scrollPlaybackIntoView(state, time) - - return state - } - - - static reduce_playbackStep(state, action) - { - const timeAsFloat = action.timeAsFloat === null ? state.playback.timeAsFloat : action.timeAsFloat - const time = Rational.fromFloat(timeAsFloat, 1024) - - state = { ...state, - playback: { ...state.playback, - timeAsFloat, - time, - }, - } - - state = Editor.scrollPlaybackIntoView(state, time) - return state - } - - - static reduce_clearSoundPreview(state, action = {}) - { - let soundPreviewPrev = action.clearPrev ? null : (state.soundPreview || state.soundPreviewPrev) - - if (!state.soundPreview && state.soundPreviewPrev === soundPreviewPrev) - return state - - return { ...state, soundPreview: null, soundPreviewPrev } - } - - - static reduce_insertNote(state, action) - { - state = Editor.handlePendingKeyCommandFinish(state) - - const insertOctave = Math.floor(state.insert.nearPitch / 12) - const possiblePitches = [-1, 0, 1].map(offset => - { - const pitch = (insertOctave + offset) * 12 + (MathUtils.mod(action.chroma, 12)) - const delta = Math.abs(pitch - state.insert.nearPitch) - return { pitch, delta } - }) - - possiblePitches.sort((a, b) => a.delta - b.delta) - const chosenPitch = possiblePitches[0].pitch - - const track = state.tracks.findIndex(t => t.kind === "notes") - - const range = new Range(action.time, action.time.add(state.insert.duration)) - const note = new Project.Note(range, chosenPitch) - const id = state.project.nextId - const project = state.project.upsertNote(note).withRefreshedRange() - - state = { ...state, - project, - insert: { ...state.insert, - nearPitch: chosenPitch, - }, - } - - state = Editor.Cursor.place(state, range.end, track) - state = Editor.Cursor.setVisible(state, false) - state = Editor.scrollTimeIntoView(state, range.end) - state = Editor.soundPreviewSet(state, { kind: "note", pitch: chosenPitch }) - state = Editor.selectionClear(state) - state = Track.selectionAdd(state, track, id) - state = Track.selectionRemoveConflictingBehind(state, track) - state = Editor.undoPointAdd(state) - return state - } - - - static reduce_insertChord(state, action) - { - state = Editor.handlePendingKeyCommandFinish(state) - - const track = state.tracks.findIndex(t => t.kind === "chords") - - const range = new Range(action.time, action.time.add(state.insert.duration)) - const chord = new Project.Chord(range, action.chord) - const id = state.project.nextId - const project = state.project.upsertChord(chord).withRefreshedRange() - - state = { ...state, project } - state = Editor.Cursor.place(state, range.end, track) - state = Editor.Cursor.setVisible(state, false) - state = Editor.scrollTimeIntoView(state, range.end) - state = Editor.soundPreviewSet(state, { kind: "chord", chord: action.chord }) - state = Editor.selectionClear(state) - state = Track.selectionAdd(state, track, id) - state = Track.selectionRemoveConflictingBehind(state, track) - state = Editor.undoPointAdd(state) - return state - } - - - static reduce_insertKeyChange(state, action) - { - state = Editor.handlePendingKeyCommandFinish(state) - - const track = state.tracks.findIndex(t => t.kind === "markers") - - const keyCh = new Project.KeyChange(action.time, action.key) - const id = state.project.nextId - const project = state.project.upsertKeyChange(keyCh).withRefreshedRange() - - state = { ...state, project } - state = Editor.Cursor.place(state, action.time, track) - state = Editor.Cursor.setVisible(state, false) - state = Editor.scrollTimeIntoView(state, action.time) - state = Editor.selectionClear(state) - state = Track.selectionAdd(state, track, id) - state = Track.selectionRemoveConflictingBehind(state, track) - state = Editor.undoPointAdd(state) - return state - } - - - static reduce_insertMeterChange(state, action) - { - state = Editor.handlePendingKeyCommandFinish(state) - - const track = state.tracks.findIndex(t => t.kind === "markers") - - const meterCh = new Project.MeterChange(action.time, action.meter) - const id = state.project.nextId - const project = state.project.upsertMeterChange(meterCh).withRefreshedRange() - - state = { ...state, project } - state = Editor.Cursor.place(state, action.time, track) - state = Editor.Cursor.setVisible(state, false) - state = Editor.scrollTimeIntoView(state, action.time) - state = Editor.selectionClear(state) - state = Track.selectionAdd(state, track, id) - state = Track.selectionRemoveConflictingBehind(state, track) - state = Editor.undoPointAdd(state) - return state - } - - - static reduce_keyDown(state, action) - { - return { ...state, - keys: { ...state.keys, [action.key]: true }, - } - } - - - static reduce_keyUp(state, action) - { - let keys = { ...state.keys } - delete keys[action.key] - - return { ...state, keys } - } - - - static reduce_keyCommand(state, action) - { - let needsUndoPointAfter = true - - state = Editor.reduce_clearSoundPreview(state, { clearPrev: true }) - - switch (action.key) - { - case "z": - { - if (state.playback.playing) - break - - if (!action.ctrlKey) - break - - if (action.shiftKey) - state = Editor.reduce_redo(state, {}) - else - state = Editor.reduce_undo(state, {}) - - needsUndoPointAfter = false - break - } - case "y": - { - if (state.playback.playing) - break - - if (!action.ctrlKey) - break - - state = Editor.reduce_redo(state, {}) - needsUndoPointAfter = false - break - } - case "c": - { - if (state.playback.playing) - break - - if (!action.ctrlKey) - break - - state = Editor.reduce_copy(state, {}) - break - } - case "x": - { - if (state.playback.playing) - break - - if (!action.ctrlKey) - break - - state = Editor.reduce_cut(state, {}) - break - } - case "v": - { - if (state.playback.playing) - break - - if (!action.ctrlKey) - break - - state = Editor.reduce_paste(state, {}) - break - } - case " ": - { - const time = state.cursor.time1.min(state.cursor.time2) - state = Editor.reduce_playbackSet(state, { playing: !state.playback.playing, time }) - break - } - case "delete": - { - if (state.playback.playing) - break - - state = Editor.selectionDelete(state) - break - } - case "backspace": - { - if (state.playback.playing) - break - - if (!state.cursor.visible) - { - state = Editor.selectionDelete(state) - break - } - - const track1 = Math.min(state.cursor.track1, state.cursor.track2) - const track2 = Math.max(state.cursor.track1, state.cursor.track2) - - if (state.cursor.time1.compare(state.cursor.time2) == 0) - { - const time = state.cursor.time1.min(state.cursor.time2) - const prevAnchor = Editor.previousAnchor(state, time, track1, track2) - - for (let tr = track1; tr <= track2; tr++) - state = Track.deleteRange(state, tr, new Range(prevAnchor, time, false, false)) - - state = Editor.Cursor.place(state, prevAnchor, null) - state = Editor.Cursor.setVisible(state, true) - state = Editor.scrollTimeIntoView(state, prevAnchor) - } - else - { - const time1 = state.cursor.time1.min(state.cursor.time2) - const time2 = state.cursor.time1.max(state.cursor.time2) - - for (let tr = track1; tr <= track2; tr++) - state = Track.deleteRange(state, tr, new Range(time1, time2, false, false)) - - state = Editor.Cursor.place(state, time1, null) - state = Editor.Cursor.setVisible(state, true) - state = Editor.scrollTimeIntoView(state, time1) - } - break - } - case "escape": - { - state = Editor.reduce_rewind(state) - break - } - case "enter": - { - if (state.playback.playing) - break - - state = Editor.Cursor.setVisible(state, true) - - const range = Editor.selectionRange(state) - if (range) - { - let track = 0 - for (let tr = 0; tr < state.tracks.length; tr++) - if (Track.selectionHasAny(state, tr)) - track = tr - - state = Editor.Cursor.place(state, range.end, track) - state = Editor.scrollTimeIntoView(state, range.end) - } - - state = Editor.handlePendingKeyCommandFinish(state) - state = Editor.selectionClear(state) - break - } - case "arrowright": - case "arrowleft": - { - const invert = (action.key === "arrowleft") - - if (state.playback.playing) - { - const timeDelta = state.timeSnap.multiplyByFloat((action.ctrlKey ? 64 : 16) * (invert ? -1 : 1)) - const newTime = state.playback.time.add(timeDelta) - state = Editor.reduce_playbackSet(state, { playing: true, time: newTime }) - } - else if (state.cursor.visible && (!Editor.selectionHasAny(state) || action.shiftKey)) - { - const timeDelta = state.timeSnap.multiplyByFloat((action.ctrlKey ? 16 : 1) * (invert ? -1 : 1)) - - state = Editor.handlePendingKeyCommandFinish(state) - - if (action.shiftKey) - { - const newTime = state.cursor.time2.add(timeDelta) - state = Editor.Cursor.drag(state, newTime, null) - state = Editor.selectAtCursor(state) - state = Editor.scrollTimeIntoView(state, newTime) - } - else - { - const timeMin = state.cursor.time1.min(state.cursor.time2) - const timeMax = state.cursor.time1.max(state.cursor.time2) - - const newTime = (invert ? timeMin : timeMax).add(timeDelta) - state = Editor.Cursor.place(state, newTime, null) - state = Editor.scrollTimeIntoView(state, newTime) - } - } - else - { - const commandData = - { - timeDelta: state.timeSnap.multiplyByFloat((action.ctrlKey ? 16 : 1) * (invert ? -1 : 1)), - } - - state = Editor.executeKeyCommand(state, "TimeShift", commandData) - - const range = Editor.selectionRange(state) - const newTime = (invert ? range.start : range.end) - state = Editor.Cursor.setVisible(state, false) - state = Editor.Cursor.place(state, newTime, null) - state = Editor.scrollTimeIntoView(state, newTime) - - state = { ...state, pendingKeyCommandFinish: true } - needsUndoPointAfter = false - } - - break - } - case "arrowup": - case "arrowdown": - { - if (state.playback.playing) - break - - const invert = (action.key === "arrowdown") - if (state.cursor.visible && (!Editor.selectionHasAny(state) || action.shiftKey)) - { - const trackDelta = (invert ? 1 : -1) - - state = Editor.handlePendingKeyCommandFinish(state) - - if (action.shiftKey) - { - const newTrack = state.cursor.track2 + trackDelta - state = Editor.Cursor.drag(state, null, newTrack) - state = Editor.selectAtCursor(state) - } - else - { - const trackMin = Math.min(state.cursor.track1, state.cursor.track2) - const trackMax = Math.max(state.cursor.track1, state.cursor.track2) - - const newTrack = (invert ? trackMax : trackMin) + trackDelta - state = Editor.Cursor.place(state, null, newTrack) - } - } - else - { - const commandData = - { - diatonic: true, - degreeDelta: (action.ctrlKey ? 7 : 1) * (invert ? -1 : 1), - } - - state = Editor.Cursor.setVisible(state, false) - state = Editor.executeKeyCommand(state, "PitchShift", commandData) - state = { ...state, pendingKeyCommandFinish: true } - needsUndoPointAfter = false - } - - break - } - case ".": - case ">": - case ",": - case "<": - { - if (state.playback.playing) - break - - const invert = (action.key === "," || action.key === "<") - const commandData = - { - diatonic: false, - pitchDelta: (action.ctrlKey ? 12 : 1) * (invert ? -1 : 1), - } - - state = Editor.Cursor.setVisible(state, false) - state = Editor.executeKeyCommand(state, "PitchShift", commandData) - state = { ...state, pendingKeyCommandFinish: true } - needsUndoPointAfter = false - break - } - case "1": - case "2": - case "3": - case "4": - case "5": - case "6": - case "7": - { - if (state.playback.playing) - break - - const degree = (action.key.charCodeAt(0) - "1".charCodeAt(0)) - const time = state.cursor.time1.min(state.cursor.time2) - const keyCh = state.project.keyChanges.findActiveAt(time) - const key = keyCh ? keyCh.key : Editor.defaultKey() - - if (state.tracks[state.cursor.track1].kind === "chords") - { - const root = key.midiForDegree(degree) - - let pitches = [0] - pitches.push(key.midiForDegree(degree + 2) - root) - pitches.push(key.midiForDegree(degree + 4) - root) - - const kind = Theory.Chord.kindFromPitches(pitches) - const chord = new Theory.Chord(root, 0, kind, 0, []) - state = Editor.reduce_insertChord(state, { time, chord }) - } - else - { - const chroma = key.midiForDegree(degree) - state = Editor.reduce_insertNote(state, { time, chroma }) - } - } - default: - return state - } - - if (action.ev) - action.ev.preventDefault() - - state = Editor.refreshProjectRange(state) - - if (needsUndoPointAfter) - { - state = { ...state, pendingKeyCommandFinish: false } - state = Editor.undoPointAdd(state) - } - - return state - } - - - static reduce_copy(state, action) - { - if (!Editor.selectionHasAny(state)) - return state - - state = Editor.clipboardClear(state) - state = Editor.selectionAddToClipboard(state) - state = Editor.undoPointAdd(state) - return state - } - - - static reduce_cut(state, action) - { - if (!Editor.selectionHasAny(state)) - return state - - state = Editor.clipboardClear(state) - state = Editor.selectionAddToClipboard(state) - state = Editor.selectionDelete(state) - state = Editor.undoPointAdd(state) - return state - } - - - static reduce_paste(state, action) - { - state = Editor.clipboardPaste(state) - state = Editor.undoPointAdd(state) - return state - } - - - static reduce_mouseMove(state, action) - { - state = Editor.reduce_clearSoundPreview(state) - - state = { ...state, - mouse: { ...state.mouse, - x: action.x, - y: action.y, - xPrev: state.mouse.x, - yPrev: state.mouse.y, - hover: null, - draw: null, - time: Editor.timeAtX(state, action.x), - track: Editor.trackAtY(state, action.y), - } - } - - if (!state.mouse.down) - { - for (let tr = 0; tr < state.tracks.length; tr++) - { - if (state.tracks[tr].rect.contains(state.mouse)) - { - const input = { mouse: { ...state.mouse } } - input.mouse.x -= state.tracks[tr].rect.x - input.mouse.y -= state.tracks[tr].rect.y - - state = Track.updateHover(state, tr, input) - if (state.tracks[tr].hover || state.tracks[tr].draw) - { - state = { ...state, - mouse: { ...state.mouse, - hover: state.tracks[tr].hover, - draw: state.tracks[tr].draw, - }, - } - } - } - else - state = Track.update(state, tr, { hover: null, draw: null }) - } - } - else - { - state = { ...state, - mouse: { ...state.mouse, - drag: { - xDelta: state.mouse.x - state.mouse.dragOrig.x, - yDelta: state.mouse.y - state.mouse.dragOrig.y, - timeDelta: state.mouse.time.subtract(state.mouse.dragOrig.time), - }, - }, - } - - const handleMouseEdgeScroll = (state) => - { - let timeScroll = state.timeScroll - - if (state.mouse.x > state.w - state.prefs.mouseEdgeScrollThreshold) - timeScroll += state.timeSnap.asFloat() * state.prefs.mouseEdgeScrollSpeed - - else if (state.mouse.x < state.prefs.mouseEdgeScrollThreshold) - timeScroll -= state.timeSnap.asFloat() * state.prefs.mouseEdgeScrollSpeed - - return { ...state, timeScroll } - } - - if (state.mouse.action == Editor.actionPan) - { - state = { ...state, - timeScroll: state.mouse.dragOrig.timeScroll - state.mouse.drag.xDelta / state.timeScale, - } - - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.pan(state, tr) - } - else if (state.mouse.action == Editor.actionSelectCursor) - { - state = Editor.Cursor.drag(state, state.mouse.time, state.mouse.track) - state = Editor.selectAtCursor(state) - state = handleMouseEdgeScroll(state) - } - else if (state.mouse.action == Editor.actionSelectRect) - { - state = { ...state, - rectSelection: { ...state.rectSelection, - time2: state.mouse.time, - y2: state.mouse.y - state.tracks[state.rectSelection.track].rect.y, - }, - } - - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.selectionClear(state, tr) - - state = Track.selectionAddAtRect(state, state.rectSelection.track) - state = handleMouseEdgeScroll(state) - } - else if (state.mouse.action == Editor.actionDraw) - { - for (let tr = 0; tr < state.tracks.length; tr++) - { - const input = { mouse: { ...state.mouse } } - input.mouse.x -= state.tracks[tr].rect.x - input.mouse.y -= state.tracks[tr].rect.y - - state = Track.drawMove(state, tr, input) - } - - state = handleMouseEdgeScroll(state) - } - else - { - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.drag(state, tr) - - state = handleMouseEdgeScroll(state) - } - } - - return state - } - - - static reduce_mouseDown(state, action) - { - if (state.mouse.down) - return state - - const timeSinceLastDown = new Date().getTime() - state.mouse.lastDownDate.getTime() - - state = Editor.reduce_clearSoundPreview(state, { clearPrev: true }) - state = Editor.handlePendingKeyCommandFinish(state) - - state = { ...state, - mouse: { ...state.mouse, - down: true, - action: 0, - lastDownDate: new Date(), - dragOrig: { - x: state.mouse.x, - y: state.mouse.y, - time: state.mouse.time, - timeScroll: state.timeScroll, - track: state.mouse.track, - }, - }, - } - - if (action.rightButton || state.keys[Editor.keyPan]) - { - state = { ...state, - mouse: { ...state.mouse, - action: Editor.actionPan, - }, - } - - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.dragStart(state, tr) - } - else if (state.keys[Editor.keySelectRect]) - { - state = Editor.Cursor.setVisible(state, false) - - state = { ...state, - mouse: { ...state.mouse, - action: Editor.actionSelectRect, - }, - rectSelection: { - track: state.mouse.track, - time1: state.mouse.time, - time2: state.mouse.time, - y1: state.mouse.y - state.tracks[state.mouse.track].rect.y, - y2: state.mouse.y - state.tracks[state.mouse.track].rect.y, - }, - } - } - else if (!state.playback.playing) - { - if (state.mouse.draw) - { - state = Editor.selectionClear(state) - state = Editor.Cursor.setVisible(state, false) - - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.drawStart(state, tr) - - state = { ...state, - mouse: { ...state.mouse, - action: Editor.actionDraw, - }, - } - } - else - { - if (!state.mouse.hover) - { - if (timeSinceLastDown < state.prefs.doubleClickThresholdMs) - { - const time = Editor.previousAnchor(state, state.mouse.time, state.mouse.track, state.mouse.track) - state = Editor.Cursor.place(state, time, state.mouse.track) - state = Editor.scrollTimeIntoView(state, time) - } - else - { - state = Editor.Cursor.place(state, state.mouse.time, state.mouse.track) - state = { ...state, - mouse: { ...state.mouse, - action: Editor.actionSelectCursor, - }, - } - } - - state = Editor.Cursor.setVisible(state, true) - } - else - { - state = Editor.Cursor.setVisible(state, false) - - if (state.keys[Editor.keySelectMultiple]) - { - for (let tr = 0; tr < state.tracks.length; tr++) - { - state = Track.hoveredPlaySoundPreview(state, tr) - state = Track.hoveredToggleSelection(state, tr) - } - } - else - { - let isHoveringSelected = false - for (let tr = 0; tr < state.tracks.length; tr++) - isHoveringSelected |= Track.hoveredIsSelected(state, tr) - - if (!isHoveringSelected) - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.selectionClear(state, tr) - - for (let tr = 0; tr < state.tracks.length; tr++) - { - state = Track.hoveredPlaySoundPreview(state, tr) - state = Track.hoveredSelect(state, tr) - } - } - - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.dragStart(state, tr) - - state = { ...state, - mouse: { ...state.mouse, - action: state.mouse.hover && state.mouse.hover.action, - dragOrig: { ...state.mouse.dragOrig, - range: Editor.selectionRange(state), - }, - }, - } - } - } - } - - return state - } - - - static reduce_mouseUp(state, action) - { - if (!state.mouse.down) - return state - - state = Editor.reduce_clearSoundPreview(state) - - if (state.mouse.action == Editor.actionDraw) - { - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.drawEnd(state, tr) - } - - state = { ...state, - mouse: { ...state.mouse, down: false, action: 0, dragOrig: null, drag: null }, - rectSelection: null, - } - - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.selectionRemoveConflictingBehind(state, tr) - - state = Editor.refreshProjectRange(state) - state = Editor.undoPointAdd(state) - - return state - } - - - static reduce_mouseWheel(state, action) - { - if (Math.abs(action.deltaX) > 0) - { - let timeScroll = state.timeScroll + 0.01 / (state.timeScale / 100) * action.deltaX - let lastWheelDate = new Date() - - return { ...state, timeScroll, mouse: { ...state.mouse, lastWheelDate } } - } - else if (new Date().getTime() - state.mouse.lastWheelDate.getTime() > 250) - { - const snap = new Rational(1, 1024) - const prevMouseTime = Editor.timeAtX(state, state.mouse.x, snap) - - let timeScale = state.timeScale * (action.deltaY > 0 ? 0.8 : 1.25) - timeScale = Math.max(4, Math.min(2048, timeScale)) - state = { ...state, timeScale } - - const newMouseTime = Editor.timeAtX(state, state.mouse.x, snap) - - const timeScroll = state.timeScroll - newMouseTime.subtract(prevMouseTime).asFloat() - - const timeSnapAdjustThresholdUpper = 24 - const timeSnapAdjustThresholdLower = 8 - let timeSnap = state.timeSnapBase - - if (timeSnap.asFloat() * timeScale > timeSnapAdjustThresholdUpper) - while (timeSnap.asFloat() * timeScale > timeSnapAdjustThresholdUpper) - timeSnap = timeSnap.divide(new Rational(2)) - - else if (timeSnap.asFloat() * timeScale < timeSnapAdjustThresholdLower) - while (timeSnap.asFloat() * timeScale < timeSnapAdjustThresholdLower) - timeSnap = timeSnap.divide(new Rational(1, 2)) - - return { ...state, timeScroll, timeSnap } - } - - return state - } - - - static reduce_clearUndoStack(state, action) - { - state = { ...state, - undoData: { - stack: [], - pointer: -1, - }, - } - - return Editor.undoPointAdd(state) - } - - - static reduce_undo(state, action) - { - if (state.undoData.pointer <= 0) - return state - - state = Editor.handlePendingKeyCommandFinish(state) - - const undoData = { ...state.undoData, - pointer: state.undoData.pointer - 1, - } - - state = { - ...state, - ...undoData.stack[undoData.pointer].state, - undoData, - } - - return Editor.reduce_resize(state, { w: state.w, h: state.h }) - } - - - static reduce_redo(state, action) - { - if (state.undoData.pointer >= state.undoData.stack.length - 1) - return state - - state = Editor.handlePendingKeyCommandFinish(state) - - const undoData = { ...state.undoData, - pointer: state.undoData.pointer + 1, - } - - state = { - ...state, - ...undoData.stack[undoData.pointer].state, - undoData, - } - - return Editor.reduce_resize(state, { w: state.w, h: state.h }) - } - - - static handlePendingKeyCommandFinish(state) - { - if (!state.pendingKeyCommandFinish) - return state - - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.selectionRemoveConflictingBehind(state, tr) - - state = { ...state, pendingKeyCommandFinish: false } - state = Editor.undoPointAdd(state) - return state - } - - - static undoPointAdd(state) - { - let stackCutIndex = state.undoData.pointer + 1 - - if (state.undoData.stack.length > 0 && - state.project === state.undoData.stack[stackCutIndex - 1].state.project) - { - //if (state.undoData.pointer != state.undoData.stack.length - 1) - return state - - //stackCutIndex = state.undoData.pointer - } - - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.selectionRemoveConflictingBehind(state, tr) - - const newUndoPoint = - { - state: { - project: state.project, - cursor: state.cursor, - tracks: state.tracks, - timeScale: state.timeScale, - timeScroll: state.timeScroll, - pendingKeyCommandFinish: state.pendingKeyCommandFinish, - }, - } - - const newStack = - [ - ...state.undoData.stack.slice(0, stackCutIndex), - newUndoPoint, - ] - - //console.log("undoPointAdd", newStack) - - return { ...state, - undoData: { ...state.undoData, - stack: newStack, - pointer: newStack.length - 1, - }, - } - } - - - static canUndo(state) - { - return (state.undoData.pointer > 0) - } - - - static canRedo(state) - { - return (state.undoData.pointer < state.undoData.stack.length - 1) - } - - - static refreshProjectRange(state) - { - const project = state.project.withRefreshedRange() - if (project === state.project) - return state - - return { ...state, project } - } - - - static xAtTime(state, time) - { - return (time.asFloat() - state.timeScroll) * state.timeScale - } - - - static timeAtX(state, x, timeSnap = null) - { - timeSnap = timeSnap || state.timeSnap - const time = x / state.timeScale + state.timeScroll - return Rational.fromFloat(time, timeSnap.denominator) - } - - - static timeRangeAtX(state, x1, x2, timeSnap = null) - { - timeSnap = timeSnap || state.timeSnap - return new Range(Editor.timeAtX(state, x1, timeSnap).subtract(timeSnap), Editor.timeAtX(state, x2, timeSnap).add(timeSnap)) - } - - - static trackAtY(state, y) - { - for (let tr = 0; tr < state.tracks.length; tr++) - if (y < state.tracks[tr].rect.y2) - return tr - - return state.tracks.length - 1 - } - - - static visibleTimeRange(state) - { - return new Range(Editor.timeAtX(state, 0).subtract(state.timeSnap), Editor.timeAtX(state, state.w).add(state.timeSnap)) - } - - - static defaultKey() - { - return Theory.Key.parse("C Major") - } - - - static defaultMeter() - { - return new Theory.Meter(4, 4) - } - - - static insertionTime(state) - { - return state.cursor.time1 - } - - - static insertionTrack(state) - { - return state.cursor.track1 - } - - - static scrollTimeIntoView(state, time) - { - const visibleTimeRange = Editor.visibleTimeRange(state) - - let timeScroll = state.timeScroll - - if (time.compare(visibleTimeRange.end.subtract(state.timeSnap.multiply(state.prefs.cursorEdgeScrollThreshold))) > 0) - timeScroll = time.subtract(visibleTimeRange.duration).subtract(state.timeSnap).asFloat() + state.timeSnap.asFloat() * state.prefs.cursorEdgeScrollSpeed - - else if (time.compare(visibleTimeRange.start.add(state.timeSnap.multiply(state.prefs.cursorEdgeScrollThreshold))) < 0) - timeScroll = time.asFloat() - state.timeSnap.asFloat() * state.prefs.cursorEdgeScrollSpeed - - return { ...state, timeScroll } - } - - - static scrollPlaybackIntoView(state, time) - { - const margin = state.timeSnap.multiplyByFloat(16) - const visibleTimeRange = Editor.visibleTimeRange(state) - - let timeScroll = state.timeScroll - - if (time.compare(visibleTimeRange.end.subtract(margin)) > 0) - timeScroll = time.subtract(margin).asFloat() - - else if (time.compare(visibleTimeRange.start.add(margin)) < 0) - timeScroll = time.subtract(margin).asFloat() - - return { ...state, timeScroll } - } - - - static selectAtCursor(state) - { - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.selectionClear(state, tr) - - const trackMin = Math.min(state.cursor.track1, state.cursor.track2) - const trackMax = Math.max(state.cursor.track1, state.cursor.track2) - - if (state.cursor.time1.compare(state.cursor.time2) != 0) - for (let tr = trackMin; tr <= trackMax; tr++) - state = Track.selectionAddAtCursor(state, tr) - - return state - } - - - static selectionHasAny(state) - { - for (let tr = 0; tr < state.tracks.length; tr++) - if (Track.selectionHasAny(state, tr)) - return true - - return false - } - - - static selectionClear(state) - { - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.selectionClear(state, tr) - - return state - } - - - static selectionRange(state) - { - let range = null - - for (let tr = 0; tr < state.tracks.length; tr++) - range = Range.merge(range, Track.selectionRange(state, tr)) - - return range - } - - - static selectionDelete(state) - { - if (!Editor.selectionHasAny(state)) - return state - - const range = Editor.selectionRange(state) - - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.selectionDelete(state, tr) - - state = Editor.Cursor.place(state, range.start, null) - state = Editor.Cursor.setVisible(state, true) - state = Editor.scrollTimeIntoView(state, range.start) - - return state - } - - - static selectionAddToClipboard(state) - { - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track.selectionAddToClipboard(state, tr) - - return state - } - - - static clipboardClear(state) - { - return { ...state, - clipboard: { - elems: new Immutable.Map(), - range: null, - }, - } - } - - - static clipboardAdd(state, elem) - { - if (state.clipboard.elems.has(elem.id)) - return state - - return { ...state, - clipboard: { ...state.clipboard, - elems: state.clipboard.elems.set(elem.id, elem), - range: Range.merge(state.clipboard.range, elem.range || Range.fromPoint(elem.time)), - }, - } - } - - - static clipboardPaste(state, elem) - { - state = Editor.selectionClear(state) - - if (!state.clipboard.range) - return state - - const trackMin = Math.min(state.cursor.track1, state.cursor.track2) - const trackMax = Math.max(state.cursor.track1, state.cursor.track2) - - const pasteData = - { - time: state.cursor.time1.min(state.cursor.time2), - } - - const projectBefore = state.project - - for (let tr = trackMin; tr <= trackMax; tr++) - state = Track.clipboardPaste(state, tr, pasteData) - - if (state.project === projectBefore) - return state - - const endTime = pasteData.time.add(state.clipboard.range.duration) - state = Editor.Cursor.setVisible(state, false) - state = Editor.Cursor.place(state, endTime, null) - state = Editor.scrollTimeIntoView(state, endTime) - state = Editor.refreshProjectRange(state) - - return state - } - - - static soundPreviewSet(state, soundPreview) - { - if (!soundPreview) - return state - - if (state.soundPreviewPrev && - state.soundPreviewPrev.kind == soundPreview.kind) - { - if (soundPreview.kind == "note" && - state.soundPreviewPrev.pitch == soundPreview.pitch) - return state - } - - return { ...state, soundPreview } - } - - - static executeKeyCommand(state, command, args) - { - for (let tr = 0; tr < state.tracks.length; tr++) - state = Track["keyCommand" + command](state, tr, args) - - return state - } - - - static previousAnchor(state, time, track1, track2) - { - let prevAnchor = null - - const trackMin = Math.min(track1, track2) - const trackMax = Math.max(track1, track2) - - for (let tr = trackMin; tr <= trackMax; tr++) - prevAnchor = Rational.max(prevAnchor, Track.previousAnchor(state, tr, time)) - - if (!prevAnchor) - return state.project.range.start - - return prevAnchor - } - - - static render(state, ctx) - { - ctx.save() - - ctx.fillStyle = state.prefs.backgroundColor - ctx.fillRect(0, 0, state.w, state.h) - - Editor.renderBackgroundMeasures(state, ctx) - Editor.renderCursorHighlight(state, ctx) - - for (let tr = 0; tr < state.tracks.length; tr++) - { - const track = state.tracks[tr] - - ctx.save() - ctx.translate(track.rect.x, track.rect.y) - - ctx.strokeStyle = state.prefs.trackSeparatorColor - ctx.lineCap = "square" - ctx.lineWidth = 1 - - ctx.beginPath() - ctx.moveTo(0, 0) - ctx.lineTo(state.w, 0) - ctx.stroke() - - ctx.beginPath() - ctx.rect(0, 0, track.rect.w, track.rect.h) - ctx.clip() - - Editor.renderRectSelectionHighlight(state, ctx, tr) - Track.render(state, tr, ctx) - Editor.renderRectSelectionContour(state, ctx, tr) - - ctx.restore() - } - - if (state.cursor.visible) - { - const timeMin = state.cursor.time1.min(state.cursor.time2) - const timeMax = state.cursor.time1.max(state.cursor.time2) - Editor.renderCursorBeam(state, ctx, timeMin, false) - Editor.renderCursorBeam(state, ctx, timeMax, true) - } - - Editor.renderPlaybackBeam(state, ctx, state.playback.time) - - ctx.restore() - } - - - static renderBackgroundMeasures(state, ctx) - { - const visibleTimeRange = Editor.visibleTimeRange(state) - - ctx.fillStyle = "#1b191c" - - const songXMin = Editor.xAtTime(state, state.project.range.start) - const songXMax = Editor.xAtTime(state, state.project.range.end) - - if (songXMin > 0) - ctx.fillRect(0, 0, songXMin, state.h) - - if (songXMax < state.w) - ctx.fillRect(songXMax, 0, state.w - songXMax, state.h) - - for (let [meterCh1, meterCh2] of state.project.meterChanges.iterActiveAtRangePairwise(visibleTimeRange)) - { - if (!meterCh1 && !meterCh2) - continue - - let timeMin = (meterCh1 ? meterCh1.time : null) - let timeMax = (meterCh2 ? meterCh2.time : visibleTimeRange.end) - - let measureAlternate = true - - if (!meterCh1) - { - meterCh1 = meterCh2 - timeMin = meterCh2.time - while (timeMin.compare(visibleTimeRange.start) > 0) - { - timeMin = timeMin.subtract(meterCh2.meter.fullCycleDuration) - - if (meterCh2.meter.alternatingMeasureCount % 2 != 0) - measureAlternate = !measureAlternate - } - } - - const meterCh1X = Editor.xAtTime(state, timeMin) - const meterCh2X = Editor.xAtTime(state, timeMax) - - ctx.strokeStyle = state.prefs.meterChangeColor - ctx.lineCap = "square" - ctx.lineWidth = 1 - - ctx.beginPath() - ctx.moveTo(meterCh1X, 0) - ctx.lineTo(meterCh1X, state.h) - ctx.stroke() - - for (const [measureN, measureD, time1, time2] of meterCh1.meter.iterMeasuresPairwise(timeMin)) - { - measureAlternate = !measureAlternate - - if (time2.compare(visibleTimeRange.start) < 0) - continue - - if (time1.compare(timeMax) > 0 || time1.compare(visibleTimeRange.end) > 0) - break - - const measureX1 = Editor.xAtTime(state, time1) - const measureX2 = Editor.xAtTime(state, time2) - - if (measureAlternate) - { - const x1 = Math.max(songXMin, Math.min(songXMax, Math.min(meterCh2X, measureX1))) - const x2 = Math.max(songXMin, Math.min(songXMax, Math.min(meterCh2X, measureX2))) - - ctx.fillStyle = "#cdf" - ctx.globalAlpha = 0.1 - ctx.fillRect(x1, 0, x2 - x1, state.h) - ctx.globalAlpha = 1 - } - - const halfSubmeasureSize = Editor.xAtTime(state, new Rational(1, measureD * 2)) - Editor.xAtTime(state, new Rational(0)) - if (halfSubmeasureSize > 16) - { - let halfSubmeasureTime = time1.add(new Rational(-1, measureD * 2)) - for (let sub = 1; sub <= measureN; sub++) - { - halfSubmeasureTime = halfSubmeasureTime.add(new Rational(2, measureD * 2)) - - const halfSubmeasureX = Editor.xAtTime(state, halfSubmeasureTime) - if (halfSubmeasureX >= meterCh1X && halfSubmeasureX <= meterCh2X) - { - ctx.strokeStyle = state.prefs.halfSubmeasureColor - ctx.beginPath() - ctx.moveTo(halfSubmeasureX, 0) - ctx.lineTo(halfSubmeasureX, state.h) - ctx.stroke() - } - } - } - - const submeasureSize = Editor.xAtTime(state, new Rational(1, measureD)) - Editor.xAtTime(state, new Rational(0)) - if (submeasureSize > 8) - { - let submeasureTime = time1 - for (let sub = 1; sub <= measureN; sub++) - { - submeasureTime = submeasureTime.add(new Rational(1, measureD)) - - const submeasureX = Editor.xAtTime(state, submeasureTime) - if (submeasureX >= meterCh1X && submeasureX <= meterCh2X) - { - ctx.strokeStyle = state.prefs.submeasureColor - ctx.beginPath() - ctx.moveTo(submeasureX, 0) - ctx.lineTo(submeasureX, state.h) - ctx.stroke() - } - } - } - } - } - - for (const keyCh of state.project.keyChanges.iterAtRange(visibleTimeRange)) - { - const keyChX = Editor.xAtTime(state, keyCh.time) - - ctx.strokeStyle = state.prefs.keyChangeColor - ctx.lineCap = "square" - ctx.lineWidth = 1 - - ctx.beginPath() - ctx.moveTo(keyChX, 0) - ctx.lineTo(keyChX, state.h) - ctx.stroke() - } - } - - - static renderPlaybackBeam(state, ctx, time) - { - if (!state.playback.playing) - return - - const trackMin = 0 - const trackMax = state.tracks.length - 1 - - if (trackMin < 0 || trackMax < 0) - return - - ctx.save() - - const x = Editor.xAtTime(state, time) - - ctx.strokeStyle = state.prefs.playbackCursorColor - ctx.lineCap = "round" - ctx.lineWidth = 2 - - const trackMinRect = state.tracks[trackMin].rect - const trackMaxRect = state.tracks[trackMax].rect - - ctx.beginPath() - ctx.moveTo(trackMinRect.x1 + x, trackMinRect.y1) - ctx.lineTo(trackMinRect.x1 + x, trackMinRect.y1) - ctx.lineTo(trackMaxRect.x1 + x, trackMaxRect.y2) - ctx.lineTo(trackMaxRect.x1 + x, trackMaxRect.y2) - ctx.stroke() - - ctx.restore() - } - - - static renderCursorBeam(state, ctx, time, tipOffsetSide) - { - if (state.playback.playing) - return - - if (state.rectSelection) - return - - const trackMin = Math.min(state.cursor.track1, state.cursor.track2) - const trackMax = Math.max(state.cursor.track1, state.cursor.track2) - - if (trackMin < 0 || trackMax < 0) - return - - ctx.save() - - const x = Editor.xAtTime(state, time) - - ctx.strokeStyle = state.prefs.selectionCursorColor - ctx.lineCap = "round" - ctx.lineWidth = 2 - - const headSize = 7 * (tipOffsetSide ? -1 : 1) - - const trackMinRect = state.tracks[trackMin].rect - const trackMaxRect = state.tracks[trackMax].rect - - ctx.beginPath() - ctx.moveTo(trackMinRect.x1 + x + headSize, trackMinRect.y1) - ctx.lineTo(trackMinRect.x1 + x, trackMinRect.y1) - ctx.lineTo(trackMaxRect.x1 + x, trackMaxRect.y2) - ctx.lineTo(trackMaxRect.x1 + x + headSize, trackMaxRect.y2) - ctx.stroke() - - ctx.restore() - } - - - static renderCursorHighlight(state, ctx) - { - if (state.playback.playing) - return - - if (state.rectSelection) - return - - if (!state.cursor.visible) - return - - const timeMin = state.cursor.time1.min(state.cursor.time2) - const timeMax = state.cursor.time1.max(state.cursor.time2) - const trackMin = Math.min(state.cursor.track1, state.cursor.track2) - const trackMax = Math.max(state.cursor.track1, state.cursor.track2) - const trackMinRect = state.tracks[trackMin].rect - const trackMaxRect = state.tracks[trackMax].rect - - ctx.save() - - const x1 = Editor.xAtTime(state, timeMin) - const x2 = Editor.xAtTime(state, timeMax) - - ctx.fillStyle = state.prefs.selectionCursorColor - ctx.globalAlpha = 0.3 - ctx.fillRect(x1, trackMinRect.y1, x2 - x1, trackMaxRect.y2 - trackMinRect.y1) - - ctx.restore() - } - - - static renderRectSelectionContour(state, ctx, trackIndex) - { - if (state.playback.playing) - return - - if (!state.rectSelection) - return - - if (trackIndex != state.rectSelection.track) - return - - const timeMin = state.rectSelection.time1.min(state.rectSelection.time2) - const timeMax = state.rectSelection.time1.max(state.rectSelection.time2) - const y1 = Math.min(state.rectSelection.y1, state.rectSelection.y2) - const y2 = Math.max(state.rectSelection.y1, state.rectSelection.y2) + 1 - - ctx.save() - - const x1 = Editor.xAtTime(state, timeMin) - const x2 = Editor.xAtTime(state, timeMax) - - ctx.strokeStyle = state.prefs.selectionCursorColor - ctx.lineCap = "square" - ctx.lineWidth = 2 - ctx.strokeRect(x1, y1, x2 - x1, y2 - y1) - - ctx.restore() - } - - - static renderRectSelectionHighlight(state, ctx, trackIndex) - { - if (state.playback.playing) - return - - if (!state.rectSelection) - return - - if (trackIndex != state.rectSelection.track) - return - - const timeMin = state.rectSelection.time1.min(state.rectSelection.time2) - const timeMax = state.rectSelection.time1.max(state.rectSelection.time2) - const y1 = Math.min(state.rectSelection.y1, state.rectSelection.y2) - const y2 = Math.max(state.rectSelection.y1, state.rectSelection.y2) + 1 - - ctx.save() - - const x1 = Editor.xAtTime(state, timeMin) - const x2 = Editor.xAtTime(state, timeMax) - - ctx.fillStyle = state.prefs.selectionCursorColor - ctx.globalAlpha = 0.3 - ctx.fillRect(x1, y1, x2 - x1, y2 - y1) - - ctx.restore() - } - - - static Cursor = class EditorCursor - { - static setVisible(state, visible) - { - return { ...state, - cursor: { ...state.cursor, - visible, - }, - } - } - - - static place(state, time, trackIndex) - { - if (trackIndex !== null) - trackIndex = Math.max(0, Math.min(state.tracks.length - 1, trackIndex)) - - return { ...state, - cursor: { ...state.cursor, - time1: time === null ? state.cursor.time1 : time, - time2: time === null ? state.cursor.time2 : time, - track1: trackIndex === null ? state.cursor.track1 : trackIndex, - track2: trackIndex === null ? state.cursor.track2 : trackIndex, - }, - } - } - - - static drag(state, time, trackIndex) - { - if (trackIndex !== null) - trackIndex = Math.max(0, Math.min(state.tracks.length - 1, trackIndex)) - - return { ...state, - cursor: { ...state.cursor, - time2: time === null ? state.cursor.time2 : time, - track2: trackIndex === null ? state.cursor.track2 : trackIndex, - }, - } - } - } -} \ No newline at end of file diff --git a/src_old/editor/track.js b/src_old/editor/track.js deleted file mode 100644 index 225d9fd..0000000 --- a/src_old/editor/track.js +++ /dev/null @@ -1,310 +0,0 @@ -import { default as Immutable } from "immutable" -import Editor from "./editor.js" -import TrackMarkers from "./trackMarkers.js" -import TrackChords from "./trackChords.js" -import TrackNotes from "./trackNotes.js" -import Rational from "../util/rational.js" -import Range from "../util/range.js" -import Rect from "../util/rect.js" - - -export default class Track -{ - static init(state, action) - { - return { - rect: new Rect(0, 0, 0, 0), - yScroll: 0, - hover: null, - draw: null, - selection: new Immutable.Set(), - dragData: null, - } - } - - - static handlerForTrackKind(kind) - { - switch (kind) - { - case "markers": return TrackMarkers - case "chords": return TrackChords - case "notes": return TrackNotes - default: throw "invalid track kind" - } - } - - - static dispatchDerived(fnName, state, trackIndex, args) - { - const handler = Track.handlerForTrackKind(state.tracks[trackIndex].kind) - const fn = handler[fnName] - if (!fn) - return null - - return fn(state, trackIndex, args) - } - - - static update(state, trackIndex, newData) - { - const track = - { - ...state.tracks[trackIndex], - ...newData - } - - return { - ...state, - tracks: [ ...state.tracks.slice(0, trackIndex), track, ...state.tracks.slice(trackIndex + 1) ], - } - } - - - static hoveredIsSelected(state, trackIndex, args) - { - if (!state.tracks[trackIndex].hover) - return false - - return state.tracks[trackIndex].selection.has(state.tracks[trackIndex].hover.id) - } - - - static hoveredSelect(state, trackIndex, args) - { - if (!state.tracks[trackIndex].hover) - return state - - const id = state.tracks[trackIndex].hover.id - - state = Track.cursorSetAtElem(state, trackIndex, state.project.findById(id)) - return Track.selectionAdd(state, trackIndex, id) - } - - - static hoveredToggleSelection(state, trackIndex, args) - { - if (!state.tracks[trackIndex].hover) - return state - - const id = state.tracks[trackIndex].hover.id - - state = Track.cursorSetAtElem(state, trackIndex, state.project.findById(id)) - - if (state.tracks[trackIndex].selection.has(id)) - return Track.selectionRemove(state, trackIndex, id) - else - return Track.selectionAdd(state, trackIndex, id) - } - - - static hoveredPlaySoundPreview(state, trackIndex, args) - { - return Track.dispatchDerived("hoveredPlaySoundPreview", state, trackIndex, args) || state - } - - - static cursorSetAtElem(state, trackIndex, elem) - { - if (!elem) - return state - - if (elem.time) - return Editor.Cursor.place(state, elem.time, trackIndex) - - if (elem.range) - return Editor.Cursor.place(state, elem.range.start, trackIndex) - - return state - } - - - static selectionClear(state, trackIndex, args) - { - return Track.update(state, trackIndex, { selection: new Immutable.Set() }) - } - - - static selectionAdd(state, trackIndex, id) - { - return Track.update(state, trackIndex, { selection: state.tracks[trackIndex].selection.add(id) }) - } - - - static selectionRemove(state, trackIndex, id) - { - return Track.update(state, trackIndex, { selection: state.tracks[trackIndex].selection.delete(id) }) - } - - - static selectionRange(state, trackIndex) - { - let range = null - - for (const id of state.tracks[trackIndex].selection) - { - const elem = state.project.findById(id) - if (!elem) - continue - - if (elem.range) - range = Range.merge(range, elem.range) - else - range = Range.merge(range, Range.fromPoint(elem.time)) - } - - return range - } - - - static selectionHasAny(state, trackIndex, args) - { - const res = Track.dispatchDerived("selectionHasAny", state, trackIndex, args) - if (res !== null) - return res - - for (const id of state.tracks[trackIndex].selection) - if (state.project.findById(id)) - return true - - return false - } - - - static selectionDelete(state, trackIndex, args) - { - const res = Track.dispatchDerived("selectionDelete", state, trackIndex, args) - if (res !== null) - return res - - for (const id of state.tracks[trackIndex].selection) - state = { ...state, project: state.project.removeById(id) } - - return state - } - - - static selectionAddAtCursor(state, trackIndex, args) - { - return Track.dispatchDerived("selectionAddAtCursor", state, trackIndex, args) || state - } - - - static selectionAddAtRect(state, trackIndex, args) - { - return Track.dispatchDerived("selectionAddAtRect", state, trackIndex, args) || state - } - - - static selectionAddToClipboard(state, trackIndex, args) - { - const res = Track.dispatchDerived("selectionAddToClipboard", state, trackIndex, args) - if (res !== null) - return res - - for (const id of state.tracks[trackIndex].selection) - { - const elem = state.project.findById(id) - if (!elem) - continue - - state = Editor.clipboardAdd(state, elem) - } - - return state - } - - - static selectionRemoveConflictingBehind(state, trackIndex, args) - { - return Track.dispatchDerived("selectionRemoveConflictingBehind", state, trackIndex, args) || state - } - - - static clipboardPaste(state, trackIndex, args) - { - return Track.dispatchDerived("clipboardPaste", state, trackIndex, args) || state - } - - - static updateHover(state, trackIndex, args) - { - return Track.dispatchDerived("updateHover", state, trackIndex, args) || state - } - - - static previousAnchor(state, trackIndex, time) - { - return Track.dispatchDerived("previousAnchor", state, trackIndex, time) || null - } - - - static deleteRange(state, trackIndex, range) - { - return Track.dispatchDerived("deleteRange", state, trackIndex, range) || state - } - - - static pan(state, trackIndex, args) - { - return Track.dispatchDerived("pan", state, trackIndex, args) || state - } - - - static dragStart(state, trackIndex, args) - { - let elems = new Immutable.Map() - for (const id of state.tracks[trackIndex].selection) - elems = elems.set(id, state.project.findById(id)) - - let dragData = - { - yScroll: state.tracks[trackIndex].yScroll, - elems, - } - - return Track.update(state, trackIndex, { dragData }) - } - - - static drag(state, trackIndex, args) - { - return Track.dispatchDerived("drag", state, trackIndex, args) || state - } - - - static drawStart(state, trackIndex, args) - { - return Track.dispatchDerived("drawStart", state, trackIndex, args) || state - } - - - static drawMove(state, trackIndex, args) - { - return Track.dispatchDerived("drawMove", state, trackIndex, args) || state - } - - - static drawEnd(state, trackIndex, args) - { - return Track.dispatchDerived("drawEnd", state, trackIndex, args) || state - } - - - static keyCommandTimeShift(state, trackIndex, args) - { - return Track.dispatchDerived("keyCommandTimeShift", state, trackIndex, args) || state - } - - - static keyCommandPitchShift(state, trackIndex, args) - { - return Track.dispatchDerived("keyCommandPitchShift", state, trackIndex, args) || state - } - - - static render(state, trackIndex, args) - { - return Track.dispatchDerived("render", state, trackIndex, args) || state - } -} \ No newline at end of file diff --git a/src_old/editor/trackChords.js b/src_old/editor/trackChords.js deleted file mode 100644 index 075bb6f..0000000 --- a/src_old/editor/trackChords.js +++ /dev/null @@ -1,456 +0,0 @@ -import Project from "../project/project.js" -import Editor from "./editor.js" -import Track from "./track.js" -import Rational from "../util/rational.js" -import Range from "../util/range.js" -import Rect from "../util/rect.js" -import Theory from "../theory.ts" -import CanvasUtils from "../util/canvas.js" - - -export default class TrackChords -{ - static init(state, trackIndex, action) - { - const newTrack = - { - ...Track.init(state, action), - kind: "chords", - } - - return { ...state, - tracks: [ ...state.tracks, newTrack ], - } - } - - - static updateHover(state, trackIndex, input) - { - let hover = null - let draw = null - - if (state.keys[Editor.keyDraw]) - { - const keyCh = state.project.keyChanges.findActiveAt(input.mouse.time) - const key = keyCh ? keyCh.key : Editor.defaultKey() - draw = - { - kind: "chord", - time1: input.mouse.time, - time2: input.mouse.time.add(state.timeSnap.multiply(new Rational(4))), - chord: new Theory.Chord(key.chromaForDegree(0), 0, 0, 0, []) - } - } - else - { - const margin = 10 - const checkRange = Editor.timeRangeAtX(state, input.mouse.x - margin, input.mouse.x + margin) - - let hoverPrimary = null - let hoverSecondary = null - - for (const chord of state.project.chords.iterAtRange(checkRange)) - { - const margin = 2 - - const x1 = Editor.xAtTime(state, chord.range.start) - const x2 = Editor.xAtTime(state, chord.range.end) - const rect = new Rect(x1 - margin, 0, x2 - x1 + margin * 2, state.tracks[trackIndex].rect.h) - - const dragMarginOut = Math.abs(Math.min(0, rect.w - 16)) - const stretchMarginOut = 16 - const stretchMarginIn = Math.max(0, Math.min(10, rect.w / 4)) - const rectDrag = new Rect(rect.x - dragMarginOut, rect.y, rect.w + dragMarginOut * 2, rect.h) - const rectStretchStart = new Rect(rect.x - stretchMarginOut, rect.y, stretchMarginOut + stretchMarginIn, rect.h) - const rectStretchEnd = new Rect(rect.x2 - stretchMarginIn, rect.y, stretchMarginOut + stretchMarginIn, rect.h) - - const exactHover = rect.contains(input.mouse) - - let action = 0 - if (dragMarginOut > 0 && rectDrag.contains(input.mouse)) - action = Editor.actionDragTime - else if (rectStretchStart.contains(input.mouse)) - action = Editor.actionStretchTimeStart - else if (rectStretchEnd.contains(input.mouse)) - action = Editor.actionStretchTimeEnd - else if (exactHover) - action = Editor.actionDragTime - - const hoverChord = - { - id: chord.id, - range: chord.range, - action, - } - - if (action != 0) - { - if (exactHover) - hoverPrimary = hoverChord - else - hoverSecondary = hoverChord - } - - if (hoverPrimary) - break - } - - hover = hoverPrimary || hoverSecondary - } - - return Track.update(state, trackIndex, { hover, draw }) - } - - - static hoveredPlaySoundPreview(state, trackIndex) - { - const hover = state.tracks[trackIndex].hover - if (!hover) - return state - - const id = hover.id - const chord = state.project.chords.findById(id) - - if (!chord) - return state - - return Editor.soundPreviewSet(state, TrackChords.mergeSoundPreview(null, chord.chord)) - } - - - static selectionAddAtCursor(state, trackIndex) - { - const timeRange = new Range(state.cursor.time1, state.cursor.time2, false, false).sorted() - - for (const chord of state.project.chords.iterAtRange(timeRange)) - state = Track.selectionAdd(state, trackIndex, chord.id) - - return state - } - - - static selectionAddAtRect(state, trackIndex) - { - const timeRange = new Range(state.rectSelection.time1, state.rectSelection.time2, false, false).sorted() - - for (const chord of state.project.chords.iterAtRange(timeRange)) - state = Track.selectionAdd(state, trackIndex, chord.id) - - return state - } - - - static selectionRemoveConflictingBehind(state, trackIndex) - { - const selection = state.tracks[trackIndex].selection - - for (const id of selection) - { - const elem = state.project.findById(id) - if (!elem) - continue - - for (const chord of state.project.chords.iterAtRange(elem.range)) - { - if (selection.has(chord.id)) - continue - - state = { ...state, project: state.project.removeById(chord.id) } - - for (const slice of chord.range.iterSlices(elem.range)) - { - const newChord = new Project.Chord(slice, chord.chord) - state = { ...state, project: state.project.upsertChord(newChord) } - } - } - } - - return state - } - - - static clipboardPaste(state, trackIndex, pasteData) - { - for (const [clipboardId, elem] of state.clipboard.elems) - { - if (elem instanceof Project.Chord) - { - const range = elem.range.displace(pasteData.time.subtract(state.clipboard.range.start)) - const chord = new Project.Chord(range, elem.chord) - const id = state.project.nextId - state = { ...state, project: state.project.upsertChord(chord) } - state = Track.selectionAdd(state, trackIndex, id) - } - } - - return state - } - - - static previousAnchor(state, trackIndex, time) - { - return state.project.chords.findPreviousAnchor(time) - } - - - static deleteRange(state, trackIndex, range) - { - for (const chord of state.project.chords.iterAtRange(range)) - { - state = { ...state, project: state.project.removeById(chord.id) } - - for (const slice of chord.range.iterSlices(range)) - { - const newChord = new Project.Chord(slice, chord.chord) - state = { ...state, project: state.project.upsertChord(newChord) } - } - } - - return state - } - - - static drag(state, trackIndex) - { - for (const id of state.tracks[trackIndex].selection) - { - const elem = state.tracks[trackIndex].dragData.elems.get(id) - if (!elem) - continue - - let changes = {} - - if (state.mouse.action & Editor.actionDragTime) - changes.range = elem.range.displace(state.mouse.drag.timeDelta) - - if (state.mouse.action & Editor.actionStretchTimeStart) - { - changes.range = elem.range.stretch(state.mouse.drag.timeDelta, state.mouse.dragOrig.range.end, state.mouse.dragOrig.range.start) - if (elem.range.start.compare(state.mouse.dragOrig.range.start) == 0) - changes.range = new Range(changes.range.start.snap(state.timeSnap), changes.range.end) - - changes.range = changes.range.sorted() - } - - if (state.mouse.action & Editor.actionStretchTimeEnd) - { - changes.range = elem.range.stretch(state.mouse.drag.timeDelta, state.mouse.dragOrig.range.start, state.mouse.dragOrig.range.end) - if (elem.range.end.compare(state.mouse.dragOrig.range.end) == 0) - changes.range = new Range(changes.range.start, changes.range.end.snap(state.timeSnap)) - - changes.range = changes.range.sorted() - } - - state = { ...state, project: state.project.update(elem.withChanges(changes)) } - } - - return state - } - - - static drawMove(state, trackIndex, input) - { - let draw = state.tracks[trackIndex].draw - - if (draw) - { - let time2 = input.mouse.time - if (Math.abs(Editor.xAtTime(state, draw.time1) - Editor.xAtTime(state, time2)) < 5) - time2 = draw.time1.add(state.timeSnap.multiply(new Rational(4))) - - draw = { ...draw, time2 } - } - - return Track.update(state, trackIndex, { draw }) - } - - - static drawEnd(state, trackIndex, input) - { - const draw = state.tracks[trackIndex].draw - if (draw) - { - if (draw.kind == "chord") - { - const chord = new Project.Chord(new Range(draw.time1, draw.time2).sorted(), draw.chord) - const id = state.project.nextId - state = { ...state, project: state.project.upsertChord(chord) } - state = Track.selectionAdd(state, trackIndex, id) - state = Editor.soundPreviewSet(state, TrackChords.mergeSoundPreview(null, draw.chord)) - } - } - - return state - } - - - static keyCommandTimeShift(state, trackIndex, commandData) - { - for (const id of state.tracks[trackIndex].selection) - { - const elem = state.project.findById(id) - if (!elem) - continue - - let changes = {} - changes.range = elem.range.displace(commandData.timeDelta) - - state = { ...state, project: state.project.update(elem.withChanges(changes)) } - } - - return state - } - - - static keyCommandPitchShift(state, trackIndex, commandData) - { - let soundPreview = null - - for (const id of state.tracks[trackIndex].selection) - { - const elem = state.project.findById(id) - if (!elem) - continue - - let changes = {} - if (commandData.diatonic) - { - const keyCh = state.project.keyChanges.findActiveAt(elem.range.start) - const key = keyCh ? keyCh.key : Editor.defaultKey() - const degree = key.octavedDegreeForMidi(elem.chord.rootMidi) - const newDegree = degree + commandData.degreeDelta - const newRoot = key.midiForDegree(commandData.degreeDelta >= 0 ? Math.floor(newDegree) : Math.ceil(newDegree)) - changes.chord = elem.chord.withChanges({ rootMidi: newRoot }) - } - else - changes.chord = elem.chord.withChanges({ rootMidi: elem.chord.rootMidi + commandData.pitchDelta }) - - soundPreview = TrackChords.mergeSoundPreview(soundPreview, changes.chord) - state = { ...state, project: state.project.update(elem.withChanges(changes)) } - } - - state = Editor.soundPreviewSet(state, soundPreview) - return state - } - - - static mergeSoundPreview(soundPreview, chord) - { - return { kind: "chord", chord } - } - - - static *iterKeyChangesAtRange(state, trackIndex, range) - { - const defaultKey = Editor.defaultKey() - - for (const pair of state.project.keyChanges.iterActiveAtRangePairwise(range)) - { - const keyCh1 = pair[0] || new Project.KeyChange(range.start, defaultKey) - const keyCh2 = pair[1] || new Project.KeyChange(range.end, defaultKey) - - const keyCh1X = Editor.xAtTime(state, keyCh1.time) - const keyCh2X = Editor.xAtTime(state, keyCh2.time) - - yield [keyCh1, keyCh1X, keyCh2X] - } - } - - - static *iterChordsAndKeyChangesAtRange(state, trackIndex, range) - { - const defaultKey = Editor.defaultKey() - - for (const pair of state.project.keyChanges.iterActiveAtRangePairwise(range)) - { - const keyCh1 = pair[0] || new Project.KeyChange(range.start, defaultKey) - const keyCh2 = pair[1] || new Project.KeyChange(range.end, defaultKey) - - const keyCh1X = Editor.xAtTime(state, keyCh1.time) - const keyCh2X = Editor.xAtTime(state, keyCh2.time) - - const time1 = keyCh1.time.max(range.start) - const time2 = keyCh2.time.min(range.end) - - for (const chord of state.project.chords.iterAtRange(new Range(time1, time2))) - yield [chord, keyCh1, keyCh1X, keyCh2X] - } - } - - - static render(state, trackIndex, ctx) - { - const track = state.tracks[trackIndex] - const visibleTimeRange = Editor.visibleTimeRange(state) - - for (const [chord, keyCh, xMin, xMax] of TrackChords.iterChordsAndKeyChangesAtRange(state, trackIndex, visibleTimeRange)) - { - if (!state.playback.playing && track.selection.has(chord.id)) - continue - - const hovering = track.hover && track.hover.id == chord.id - const playing = state.playback.playing && chord.range.overlapsPoint(state.playback.time) - TrackChords.renderChord(state, trackIndex, ctx, chord.range, keyCh.key, chord.chord, xMin, xMax, hovering, false, playing) - } - - if (!state.playback.playing) - { - for (const [chord, keyCh, xMin, xMax] of TrackChords.iterChordsAndKeyChangesAtRange(state, trackIndex, visibleTimeRange)) - { - if (!track.selection.has(chord.id)) - continue - - const hovering = track.hover && track.hover.id == chord.id - TrackChords.renderChord(state, trackIndex, ctx, chord.range, keyCh.key, chord.chord, xMin, xMax, hovering, true, false) - } - } - - const draw = state.tracks[trackIndex].draw - if (draw) - { - ctx.globalAlpha = 0.6 - - const keyCh = state.project.keyChanges.findActiveAt(draw.time1) - const key = keyCh ? keyCh.key : Editor.defaultKey() - TrackChords.renderChord(state, trackIndex, ctx, new Range(draw.time1, draw.time2).sorted(), key, draw.chord, -Infinity, Infinity, false, false) - - ctx.globalAlpha = 1 - } - } - - - static renderChord(state, trackIndex, ctx, range, key, chord, xMin, xMax, hovering, selected, playing) - { - const h = state.tracks[trackIndex].rect.h - const chordX1 = Editor.xAtTime(state, range.start) - const chordX2 = Editor.xAtTime(state, range.end) - - let x1 = Math.max(xMin, Math.min(xMax, chordX1)) - let x2 = Math.max(xMin, Math.min(xMax, chordX2)) - - if (x1 == chordX1) - x1 += 1 - - if (x2 == chordX2) - x2 -= 1 - - CanvasUtils.drawChord(ctx, x1, 0, x2, h, chord, key) - - if (hovering) - { - ctx.globalAlpha = 0.4 - ctx.fillStyle = "#fff" - ctx.fillRect(x1, 0, x2 - x1, h) - ctx.globalAlpha = 1 - } - - if (selected || playing) - { - const margin = 3 - ctx.globalAlpha = 0.6 - ctx.fillStyle = "#fff" - ctx.fillRect(x1, margin, x2 - x1, h - margin * 2) - ctx.globalAlpha = 1 - } - } -} \ No newline at end of file diff --git a/src_old/editor/trackMarkers.js b/src_old/editor/trackMarkers.js deleted file mode 100644 index 5c565d9..0000000 --- a/src_old/editor/trackMarkers.js +++ /dev/null @@ -1,477 +0,0 @@ -import Project from "../project/project.js" -import Editor from "./editor.js" -import Track from "./track.js" -import Rational from "../util/rational.js" -import Range from "../util/range.js" -import Rect from "../util/rect.js" -import Theory from "../theory.ts" - - -const markerWidth = 22 -const keyMarkerY = 0 -const keyMarkerHeight = 22 -const meterMarkerY = 22 -const meterMarkerHeight = 22 - - -export default class TrackMarkers -{ - static init(state, trackIndex, action) - { - const newTrack = - { - ...Track.init(state, action), - kind: "markers", - } - - return { ...state, - tracks: [ ...state.tracks, newTrack ], - } - } - - - static updateHover(state, trackIndex, input) - { - let hover = null - let draw = null - - if (state.keys[Editor.keyDraw]) - { - if (input.mouse.y >= meterMarkerY && input.mouse.y <= meterMarkerY + meterMarkerHeight) - draw = { kind: "meter", time: input.mouse.time } - - if (input.mouse.y >= keyMarkerY && input.mouse.y <= keyMarkerY + keyMarkerHeight) - draw = { kind: "key", time: input.mouse.time } - } - else - { - const checkRange = Editor.timeRangeAtX(state, input.mouse.x - markerWidth, input.mouse.x + markerWidth) - - for (const keyCh of state.project.keyChanges.iterAtRange(checkRange)) - { - const rect = TrackMarkers.knobRectForKeyChange(state, keyCh.time) - if (rect.contains(input.mouse)) - { - hover = - { - id: keyCh.id, - range: Range.fromPoint(keyCh.time), - action: Editor.actionDragTime, - } - } - } - - for (const meterCh of state.project.meterChanges.iterAtRange(checkRange)) - { - const rect = TrackMarkers.knobRectForMeterChange(state, meterCh.time) - if (rect.contains(input.mouse)) - { - hover = - { - id: meterCh.id, - range: Range.fromPoint(meterCh.time), - action: Editor.actionDragTime, - } - } - } - } - - return Track.update(state, trackIndex, { hover, draw }) - } - - - static selectionAddAtCursor(state, trackIndex) - { - const timeRange = new Range(state.cursor.time1, state.cursor.time2, false, false).sorted() - - for (const keyCh of state.project.keyChanges.iterAtRange(timeRange)) - state = Track.selectionAdd(state, trackIndex, keyCh.id) - - for (const meterCh of state.project.meterChanges.iterAtRange(timeRange)) - state = Track.selectionAdd(state, trackIndex, meterCh.id) - - return state - } - - - static selectionAddAtRect(state, trackIndex) - { - const timeRange = new Range(state.rectSelection.time1, state.rectSelection.time2, false, false).sorted() - const y1 = Math.min(state.rectSelection.y1, state.rectSelection.y2) - const y2 = Math.max(state.rectSelection.y1, state.rectSelection.y2) - - if (y2 > keyMarkerY && y1 < keyMarkerY + keyMarkerHeight) - for (const keyCh of state.project.keyChanges.iterAtRange(timeRange)) - state = Track.selectionAdd(state, trackIndex, keyCh.id) - - if (y2 > meterMarkerY && y1 < meterMarkerY + meterMarkerHeight) - for (const meterCh of state.project.meterChanges.iterAtRange(timeRange)) - state = Track.selectionAdd(state, trackIndex, meterCh.id) - - return state - } - - - static selectionRemoveConflictingBehind(state, trackIndex) - { - const selection = state.tracks[trackIndex].selection - - for (const id of selection) - { - const elem = state.project.findById(id) - - if (elem instanceof Project.KeyChange) - { - for (const keyCh of state.project.keyChanges.iterAt(elem.time)) - { - if (selection.has(keyCh.id)) - continue - - state = { ...state, project: state.project.removeById(keyCh.id) } - } - } - - if (elem instanceof Project.MeterChange) - { - for (const meterCh of state.project.meterChanges.iterAt(elem.time)) - { - if (selection.has(meterCh.id)) - continue - - state = { ...state, project: state.project.removeById(meterCh.id) } - } - } - } - - return state - } - - - static clipboardPaste(state, trackIndex, pasteData) - { - for (const [clipboardId, elem] of state.clipboard.elems) - { - if (elem instanceof Project.KeyChange) - { - const time = elem.time.add(pasteData.time.subtract(state.clipboard.range.start)) - const keyCh = new Project.KeyChange(time, elem.key) - const id = state.project.nextId - state = { ...state, project: state.project.upsertKeyChange(keyCh) } - state = Track.selectionAdd(state, trackIndex, id) - } - - if (elem instanceof Project.MeterChange) - { - const time = elem.time.add(pasteData.time.subtract(state.clipboard.range.start)) - const meterCh = new Project.MeterChange(time, elem.meter) - const id = state.project.nextId - state = { ...state, project: state.project.upsertMeterChange(meterCh) } - state = Track.selectionAdd(state, trackIndex, id) - } - } - - return state - } - - - static previousAnchor(state, trackIndex, time) - { - const prevAnchorKey = state.project.keyChanges.findPreviousDeletionAnchor(time) - const prevAnchorMeter = state.project.meterChanges.findPreviousDeletionAnchor(time) - - return Rational.max(prevAnchorKey, prevAnchorMeter) - } - - - static deleteRange(state, trackIndex, range) - { - if (range.start.compare(range.end) == 0) - { - for (const keyCh of state.project.keyChanges.iterAt(range.start)) - state = { ...state, project: state.project.removeById(keyCh.id) } - - for (const meterCh of state.project.meterChanges.iterAt(range.start)) - state = { ...state, project: state.project.removeById(meterCh.id) } - } - else - { - for (const keyCh of state.project.keyChanges.iterAtRange(range)) - state = { ...state, project: state.project.removeById(keyCh.id) } - - for (const meterCh of state.project.meterChanges.iterAtRange(range)) - state = { ...state, project: state.project.removeById(meterCh.id) } - } - - return state - } - - - static drag(state, trackIndex) - { - for (const id of state.tracks[trackIndex].selection) - { - const elem = state.tracks[trackIndex].dragData.elems.get(id) - if (!elem) - continue - - let changes = {} - - if (state.mouse.action & Editor.actionDragTime) - changes.time = elem.time.add(state.mouse.drag.timeDelta).snap(state.timeSnap) - - if (state.mouse.action & Editor.actionStretchTimeStart) - { - changes.time = elem.time.stretch(state.mouse.drag.timeDelta, state.mouse.dragOrig.range.end, state.mouse.dragOrig.range.start) - if (elem.time.compare(state.mouse.dragOrig.range.start) == 0) - changes.time = changes.time.snap(state.timeSnap) - } - - if (state.mouse.action & Editor.actionStretchTimeEnd) - { - changes.time = elem.time.stretch(state.mouse.drag.timeDelta, state.mouse.dragOrig.range.start, state.mouse.dragOrig.range.end) - if (elem.time.compare(state.mouse.dragOrig.range.start) == 0) - changes.time = changes.time.snap(state.timeSnap) - } - - state = { ...state, project: state.project.update(elem.withChanges(changes)) } - } - - return state - } - - - static drawMove(state, trackIndex, input) - { - let draw = state.tracks[trackIndex].draw - - if (draw) - draw = { ...draw, time: input.mouse.time } - - return Track.update(state, trackIndex, { draw }) - } - - - static drawEnd(state, trackIndex, input) - { - const draw = state.tracks[trackIndex].draw - if (draw) - { - const id = state.project.nextId - - if (draw.kind == "key") - state = { ...state, project: state.project.upsertKeyChange(new Project.KeyChange(draw.time, Editor.defaultKey())) } - - else if (draw.kind == "meter") - state = { ...state, project: state.project.upsertMeterChange(new Project.MeterChange(draw.time, Editor.defaultMeter())) } - - state = Track.selectionAdd(state, trackIndex, id) - } - - return state - } - - - static keyCommandTimeShift(state, trackIndex, commandData) - { - for (const id of state.tracks[trackIndex].selection) - { - const elem = state.project.findById(id) - if (!elem) - continue - - let changes = {} - changes.time = elem.time.add(commandData.timeDelta) - - state = { ...state, project: state.project.update(elem.withChanges(changes)) } - } - - return state - } - - - static keyCommandPitchShift(state, trackIndex, commandData) - { - for (const id of state.tracks[trackIndex].selection) - { - const elem = state.project.findById(id) - if (!elem) - continue - - if (!(elem instanceof Project.KeyChange)) - continue - - let changes = {} - if (commandData.diatonic) - { - const key = elem.key - const newRootMidi = key.midiForDegree(0) + commandData.degreeDelta - changes.key = new Theory.Key(key.nameForMidi(newRootMidi).simplified, key.scale) - /*const key = elem.key - const newRoot = new Theory.PitchName(key.tonic.letter + commandData.degreeDelta, key.tonic.accidental) - changes.key = new Theory.Key(newRoot, key.scale)*/ - } - else - { - const key = elem.key - const newRootMidi = key.midiForDegree(0) + commandData.pitchDelta - changes.key = new Theory.Key(key.nameForMidi(newRootMidi).simplified, key.scale) - } - - state = { ...state, project: state.project.update(elem.withChanges(changes)) } - } - - return state - } - - - static knobRectForMeterChange(state, time) - { - const x = (time.asFloat() - state.timeScroll) * state.timeScale - - return new Rect(x - markerWidth / 2, meterMarkerY, markerWidth, meterMarkerHeight) - } - - - static knobRectForKeyChange(state, time) - { - const x = (time.asFloat() - state.timeScroll) * state.timeScale - - return new Rect(x - markerWidth / 2, keyMarkerY, markerWidth, keyMarkerHeight) - } - - - static render(state, trackIndex, ctx) - { - const visibleTimeRange = Editor.visibleTimeRange(state) - - for (const keyCh of state.project.keyChanges.iterAtRange(visibleTimeRange)) - TrackMarkers.renderKeyChange(state, trackIndex, ctx, keyCh) - - for (const meterCh of state.project.meterChanges.iterAtRange(visibleTimeRange)) - TrackMarkers.renderMeterChange(state, trackIndex, ctx, meterCh) - - if (state.tracks[trackIndex].draw) - { - ctx.globalAlpha = 0.6 - - if (state.tracks[trackIndex].draw.kind == "key") - TrackMarkers.renderKeyChangeKnob(state, trackIndex, ctx, state.tracks[trackIndex].draw.time) - - else if (state.tracks[trackIndex].draw.kind == "meter") - TrackMarkers.renderMeterChangeKnob(state, trackIndex, ctx, state.tracks[trackIndex].draw.time) - - ctx.globalAlpha = 1 - } - } - - - static renderMeterChangeKnob(state, trackIndex, ctx, time) - { - const rect = TrackMarkers.knobRectForMeterChange(state, time) - - ctx.fillStyle = state.prefs.meterChangeColor - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2, 0, Math.PI * 2) - ctx.fill() - } - - - static renderMeterChange(state, trackIndex, ctx, meterCh) - { - const rect = TrackMarkers.knobRectForMeterChange(state, meterCh.time) - - ctx.fillStyle = state.prefs.meterChangeColor - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2, 0, Math.PI * 2) - ctx.fill() - - ctx.font = "14px Verdana" - ctx.textAlign = "left" - ctx.textBaseline = "middle" - ctx.fillText( - meterCh.meter.numerator.toString() + " / " + meterCh.meter.denominator.toString(), - rect.x + rect.w + 5, - rect.y + rect.h / 2) - - if (state.tracks[trackIndex].selection.has(meterCh.id)) - { - ctx.globalAlpha = 0.75 - ctx.fillStyle = "#fff" - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2 - 3, 0, Math.PI * 2) - ctx.fill() - - ctx.globalAlpha = 1 - } - - const hover = state.tracks[trackIndex].hover - if (hover && hover.id === meterCh.id) - { - ctx.globalAlpha = 0.5 - ctx.fillStyle = "#fff" - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2, 0, Math.PI * 2) - ctx.fill() - - ctx.globalAlpha = 1 - } - } - - - static renderKeyChangeKnob(state, trackIndex, ctx, time) - { - const rect = TrackMarkers.knobRectForKeyChange(state, time) - - ctx.fillStyle = state.prefs.keyChangeColor - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2, 0, Math.PI * 2) - ctx.fill() - } - - - static renderKeyChange(state, trackIndex, ctx, keyCh) - { - const rect = TrackMarkers.knobRectForKeyChange(state, keyCh.time) - - ctx.fillStyle = state.prefs.keyChangeColor - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2, 0, Math.PI * 2) - ctx.fill() - - ctx.font = "14px Verdana" - ctx.textAlign = "left" - ctx.textBaseline = "middle" - ctx.fillText(keyCh.key.str, rect.x + rect.w + 5, rect.y + rect.h / 2) - - if (state.tracks[trackIndex].selection.has(keyCh.id)) - { - ctx.globalAlpha = 0.75 - ctx.fillStyle = "#fff" - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2 - 3, 0, Math.PI * 2) - ctx.fill() - - ctx.globalAlpha = 1 - } - - const hover = state.tracks[trackIndex].hover - if (hover && hover.id === keyCh.id) - { - ctx.globalAlpha = 0.5 - ctx.fillStyle = "#fff" - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2, 0, Math.PI * 2) - ctx.fill() - - ctx.globalAlpha = 1 - } - } -} \ No newline at end of file diff --git a/src_old/editor/trackNotes.js b/src_old/editor/trackNotes.js deleted file mode 100644 index 8e4472b..0000000 --- a/src_old/editor/trackNotes.js +++ /dev/null @@ -1,591 +0,0 @@ -import Project from "../project/project.js" -import Editor from "./editor.js" -import Track from "./track.js" -import Rational from "../util/rational.js" -import Range from "../util/range.js" -import Rect from "../util/rect.js" -import Theory from "../theory.ts" -import CanvasUtils from "../util/canvas.js" - - -export default class TrackNotes -{ - static init(state, trackIndex, action) - { - const newTrack = - { - ...Track.init(state, action), - kind: "notes", - rowScale: 15, - } - - return { ...state, - tracks: [ ...state.tracks, newTrack ], - } - } - - - static updateHover(state, trackIndex, input) - { - let hover = null - let draw = null - - if (state.keys[Editor.keyDraw]) - { - const keyCh = state.project.keyChanges.findActiveAt(input.mouse.time) - const key = keyCh ? keyCh.key : Editor.defaultKey() - const row = TrackNotes.rowAtY(state, trackIndex, input.mouse.y, key) - const pitch = TrackNotes.pitchForRow(state, trackIndex, row, key) - draw = - { - kind: "note", - time1: input.mouse.time, - time2: input.mouse.time.add(state.timeSnap.multiply(new Rational(4))), - pitch, - } - } - else - { - const margin = 10 - const checkRange = Editor.timeRangeAtX(state, input.mouse.x - margin, input.mouse.x + margin) - - let hoverPrimary = null - let hoverSecondary = null - - for (const [note, keyCh, xMin, xMax] of TrackNotes.iterNotesAndKeyChangesAtRange(state, trackIndex, checkRange)) - { - const margin = 2 - - const row = TrackNotes.rowForPitch(state, trackIndex, note.pitch, keyCh.key) - const rectExact = TrackNotes.rectForNote(state, trackIndex, note.range, row, xMin, xMax) - const rect = new Rect(rectExact.x - margin, rectExact.y, rectExact.w + margin * 2, rectExact.h) - - const dragMarginOut = Math.abs(Math.min(0, rect.w - 16)) - const stretchMarginOut = 16 - const stretchMarginIn = Math.max(0, Math.min(10, rect.w / 4)) - const rectDrag = new Rect(rect.x - dragMarginOut, rect.y, rect.w + dragMarginOut * 2, rect.h) - const rectStretchStart = new Rect(rect.x - stretchMarginOut, rect.y, stretchMarginOut + stretchMarginIn, rect.h) - const rectStretchEnd = new Rect(rect.x2 - stretchMarginIn, rect.y, stretchMarginOut + stretchMarginIn, rect.h) - - const exactHover = rect.contains(input.mouse) - - let action = 0 - if (dragMarginOut > 0 && rectDrag.contains(input.mouse)) - action = Editor.actionDragTime | Editor.actionDragPitchRow - else if (rectStretchStart.contains(input.mouse) && !rectExact.cutStart) - action = Editor.actionStretchTimeStart - else if (rectStretchEnd.contains(input.mouse) && !rectExact.cutEnd) - action = Editor.actionStretchTimeEnd - else if (exactHover) - action = Editor.actionDragTime | Editor.actionDragPitchRow - - const hoverNote = - { - id: note.id, - range: note.range, - action, - } - - if (action != 0) - { - if (exactHover) - hoverPrimary = hoverNote - else - hoverSecondary = hoverNote - } - - if (hoverPrimary) - break - } - - hover = hoverPrimary || hoverSecondary - } - - return Track.update(state, trackIndex, { hover, draw }) - } - - - static hoveredPlaySoundPreview(state, trackIndex) - { - const hover = state.tracks[trackIndex].hover - if (!hover) - return state - - const id = hover.id - const note = state.project.notes.findById(id) - - if (!note) - return state - - return Editor.soundPreviewSet(state, TrackNotes.mergeSoundPreview(null, note.pitch)) - } - - - static selectionAddAtCursor(state, trackIndex) - { - const timeRange = new Range(state.cursor.time1, state.cursor.time2, false, false).sorted() - - for (const note of state.project.notes.iterAtRange(timeRange)) - state = Track.selectionAdd(state, trackIndex, note.id) - - return state - } - - - static selectionAddAtRect(state, trackIndex) - { - const timeRange = new Range(state.rectSelection.time1, state.rectSelection.time2, false, false).sorted() - const y1 = Math.min(state.rectSelection.y1, state.rectSelection.y2) - const y2 = Math.max(state.rectSelection.y1, state.rectSelection.y2) - - for (const [note, keyCh, xMin, xMax] of TrackNotes.iterNotesAndKeyChangesAtRange(state, trackIndex, timeRange)) - { - const row = TrackNotes.rowForPitch(state, trackIndex, note.pitch, keyCh.key) - const rect = TrackNotes.rectForNote(state, trackIndex, note.range, row, xMin, xMax) - if (y1 < rect.y2 && y2 > rect.y1) - state = Track.selectionAdd(state, trackIndex, note.id) - } - - return state - } - - - static selectionRemoveConflictingBehind(state, trackIndex) - { - const selection = state.tracks[trackIndex].selection - - for (const id of selection) - { - const elem = state.project.findById(id) - if (!elem) - continue - - for (const note of state.project.notes.iterAtRange(elem.range)) - { - if (selection.has(note.id)) - continue - - if (note.pitch !== elem.pitch) - continue - - state = { ...state, project: state.project.removeById(note.id) } - - for (const slice of note.range.iterSlices(elem.range)) - { - const newNote = new Project.Note(slice, note.pitch) - state = { ...state, project: state.project.upsertNote(newNote) } - } - } - } - - return state - } - - - static clipboardPaste(state, trackIndex, pasteData) - { - for (const [clipboardId, elem] of state.clipboard.elems) - { - if (elem instanceof Project.Note) - { - const range = elem.range.displace(pasteData.time.subtract(state.clipboard.range.start)) - const note = new Project.Note(range, elem.pitch) - const id = state.project.nextId - state = { ...state, project: state.project.upsertNote(note) } - state = Track.selectionAdd(state, trackIndex, id) - } - } - - return state - } - - - static previousAnchor(state, trackIndex, time) - { - return state.project.notes.findPreviousAnchor(time) - } - - - static deleteRange(state, trackIndex, range) - { - for (const note of state.project.notes.iterAtRange(range)) - { - state = { ...state, project: state.project.removeById(note.id) } - - for (const slice of note.range.iterSlices(range)) - { - const newNote = new Project.Note(slice, note.pitch) - state = { ...state, project: state.project.upsertNote(newNote) } - } - } - - return state - } - - - static pan(state, trackIndex) - { - const pitchRowDelta = -(state.mouse.drag.yDelta / state.tracks[trackIndex].rowScale) - - const yScroll = state.tracks[trackIndex].dragData.yScroll + pitchRowDelta - state = Track.update(state, trackIndex, { yScroll }) - - return state - } - - - static drag(state, trackIndex) - { - const pitchRowDelta = -(state.mouse.drag.yDelta / state.tracks[trackIndex].rowScale) - - let soundPreview = null - - for (const id of state.tracks[trackIndex].selection) - { - const elem = state.tracks[trackIndex].dragData.elems.get(id) - if (!elem) - continue - - let changes = {} - - if (state.mouse.action & Editor.actionDragTime) - changes.range = elem.range.displace(state.mouse.drag.timeDelta) - - if (state.mouse.action & Editor.actionStretchTimeStart) - { - changes.range = elem.range.stretch(state.mouse.drag.timeDelta, state.mouse.dragOrig.range.end, state.mouse.dragOrig.range.start) - if (elem.range.start.compare(state.mouse.dragOrig.range.start) == 0) - changes.range = new Range(changes.range.start.snap(state.timeSnap), changes.range.end) - - changes.range = changes.range.sorted() - } - - if (state.mouse.action & Editor.actionStretchTimeEnd) - { - changes.range = elem.range.stretch(state.mouse.drag.timeDelta, state.mouse.dragOrig.range.start, state.mouse.dragOrig.range.end) - if (elem.range.end.compare(state.mouse.dragOrig.range.end) == 0) - changes.range = new Range(changes.range.start, changes.range.end.snap(state.timeSnap)) - - changes.range = changes.range.sorted() - } - - if (state.mouse.action & Editor.actionDragPitchRow) - { - const keyCh = state.project.keyChanges.findActiveAt(elem.range.start) - const key = keyCh ? keyCh.key : Editor.defaultKey() - const degree = key.octavedDegreeForMidi(elem.pitch) - const newPitch = key.midiForDegree(Math.floor(degree + pitchRowDelta)) - changes.pitch = newPitch - soundPreview = TrackNotes.mergeSoundPreview(soundPreview, newPitch) - } - - state = { ...state, project: state.project.update(elem.withChanges(changes)) } - } - - state = Editor.soundPreviewSet(state, soundPreview) - return state - } - - - static drawMove(state, trackIndex, input) - { - let draw = state.tracks[trackIndex].draw - - if (draw) - { - let time2 = input.mouse.time - if (Math.abs(Editor.xAtTime(state, draw.time1) - Editor.xAtTime(state, time2)) < 5) - time2 = draw.time1.add(state.timeSnap.multiply(new Rational(4))) - - draw = { ...draw, time2 } - } - - return Track.update(state, trackIndex, { draw }) - } - - - static drawEnd(state, trackIndex, input) - { - const draw = state.tracks[trackIndex].draw - if (draw) - { - if (draw.kind == "note") - { - const note = new Project.Note(new Range(draw.time1, draw.time2).sorted(), draw.pitch) - const id = state.project.nextId - state = { ...state, project: state.project.upsertNote(note) } - state = Track.selectionAdd(state, trackIndex, id) - state = Editor.soundPreviewSet(state, TrackNotes.mergeSoundPreview(null, draw.pitch)) - } - } - - return state - } - - - static keyCommandTimeShift(state, trackIndex, commandData) - { - for (const id of state.tracks[trackIndex].selection) - { - const elem = state.project.findById(id) - if (!elem) - continue - - let changes = {} - changes.range = elem.range.displace(commandData.timeDelta) - - state = { ...state, project: state.project.update(elem.withChanges(changes)) } - } - - return state - } - - - static keyCommandPitchShift(state, trackIndex, commandData) - { - let soundPreview = null - - for (const id of state.tracks[trackIndex].selection) - { - const elem = state.project.findById(id) - if (!elem) - continue - - let changes = {} - if (commandData.diatonic) - { - const keyCh = state.project.keyChanges.findActiveAt(elem.range.start) - const key = keyCh ? keyCh.key : Editor.defaultKey() - const degree = key.octavedDegreeForMidi(elem.pitch) - const newDegree = degree + commandData.degreeDelta - const newPitch = key.midiForDegree(commandData.degreeDelta >= 0 ? Math.floor(newDegree) : Math.ceil(newDegree)) - changes.pitch = newPitch - } - else - changes.pitch = elem.pitch + commandData.pitchDelta - - soundPreview = TrackNotes.mergeSoundPreview(soundPreview, changes.pitch) - state = { ...state, project: state.project.update(elem.withChanges(changes)) } - } - - state = Editor.soundPreviewSet(state, soundPreview) - return state - } - - - static yForRow(state, trackIndex, row) - { - const track = state.tracks[trackIndex] - return track.rect.h / 2 - (row + 1 + track.yScroll) * track.rowScale - } - - - static rowAtY(state, trackIndex, y, key) - { - const track = state.tracks[trackIndex] - return Math.floor((track.rect.h / 2 - y) / track.rowScale - track.yScroll) - } - - - static rowForPitch(state, trackIndex, pitch, key) - { - const tonicRowOffset = Theory.Utils.chromaToDegreeInCMajor(key.tonic.chroma) - return key.octavedDegreeForMidi(pitch - 60) + tonicRowOffset - } - - - static pitchForRow(state, trackIndex, row, key) - { - const tonicRowOffset = Theory.Utils.chromaToDegreeInCMajor(key.tonic.chroma) - return key.midiForDegree(row - Math.floor(tonicRowOffset)) + 60 - } - - - static mergeSoundPreview(soundPreview, pitch) - { - if (soundPreview && soundPreview.pitch >= pitch) - return soundPreview - - return { kind: "note", pitch } - } - - - static rectForNote(state, trackIndex, range, row, xStart, xEnd) - { - const track = state.tracks[trackIndex] - - const noteOrigX1 = Editor.xAtTime(state, range.start) - const noteOrigX2 = Editor.xAtTime(state, range.end) - - const noteY = TrackNotes.yForRow(state, trackIndex, row) - - let noteX1 = Math.max(noteOrigX1, xStart) - let noteX2 = Math.min(noteOrigX2, xEnd) - - const cutStart = noteOrigX1 < noteX1 - const cutEnd = noteOrigX2 > noteX2 - - if (!cutStart) noteX1 += 1 - if (!cutEnd) noteX2 -= 1 - - const noteW = Math.max(2, noteX2 - noteX1) - - return Object.assign(new Rect(noteX1, noteY, noteW, track.rowScale), { cutStart, cutEnd }) - } - - - static *iterKeyChangesAtRange(state, trackIndex, range) - { - const defaultKey = Editor.defaultKey() - - for (const pair of state.project.keyChanges.iterActiveAtRangePairwise(range)) - { - const keyCh1 = pair[0] || new Project.KeyChange(range.start, defaultKey) - const keyCh2 = pair[1] || new Project.KeyChange(range.end, defaultKey) - - const keyCh1X = Editor.xAtTime(state, keyCh1.time) - const keyCh2X = Editor.xAtTime(state, keyCh2.time) - - yield [keyCh1, keyCh1X, keyCh2X] - } - } - - - static *iterNotesAndKeyChangesAtRange(state, trackIndex, range) - { - const defaultKey = Editor.defaultKey() - - for (const pair of state.project.keyChanges.iterActiveAtRangePairwise(range)) - { - const keyCh1 = pair[0] || new Project.KeyChange(range.start, defaultKey) - const keyCh2 = pair[1] || new Project.KeyChange(range.end, defaultKey) - - const keyCh1X = Editor.xAtTime(state, keyCh1.time) - const keyCh2X = Editor.xAtTime(state, keyCh2.time) - - const time1 = keyCh1.time.max(range.start) - const time2 = keyCh2.time.min(range.end) - - for (const note of state.project.notes.iterAtRange(new Range(time1, time2))) - yield [note, keyCh1, keyCh1X, keyCh2X] - } - } - - - static render(state, trackIndex, ctx) - { - const track = state.tracks[trackIndex] - const visibleTimeRange = Editor.visibleTimeRange(state) - - for (const [keyCh, xMin, xMax] of TrackNotes.iterKeyChangesAtRange(state, trackIndex, visibleTimeRange)) - { - const tonicRowOffset = Theory.Utils.chromaToDegreeInCMajor(keyCh.key.tonic.chroma) - - const rowAtTop = TrackNotes.rowAtY(state, trackIndex, 0) - const rowAtBottom = TrackNotes.rowAtY(state, trackIndex, track.rect.h) - - const octaveAtTop = Math.ceil(rowAtTop / 7) + 1 - const octaveAtBottom = Math.floor(rowAtBottom / 7) - 1 - - for (let i = octaveAtBottom; i <= octaveAtTop; i++) - { - const y = TrackNotes.yForRow(state, trackIndex, tonicRowOffset + i * 7) + track.rowScale - - ctx.strokeStyle = "#000" - ctx.beginPath() - ctx.moveTo(xMin, y) - ctx.lineTo(xMax, y) - ctx.stroke() - - for (let j = 2; j < 7; j += 2) - { - const ySuboctave = TrackNotes.yForRow(state, trackIndex, tonicRowOffset + i * 7 + j) + track.rowScale - - ctx.globalAlpha = 0.025 - ctx.fillStyle = "#000" - ctx.fillRect(xMin, ySuboctave, xMax - xMin, track.rowScale) - ctx.globalAlpha = 1 - } - - if (i == 0) - { - ctx.globalAlpha = 0.05 - ctx.fillStyle = "#fff" - ctx.fillRect(xMin, y - 7 * track.rowScale, xMax - xMin, 7 * track.rowScale) - ctx.globalAlpha = 1 - } - } - } - - for (const [note, keyCh, xMin, xMax] of TrackNotes.iterNotesAndKeyChangesAtRange(state, trackIndex, visibleTimeRange)) - { - if (!state.playback.playing && track.selection.has(note.id)) - continue - - const row = TrackNotes.rowForPitch(state, trackIndex, note.pitch, keyCh.key) - const mode = keyCh.key.scale.metadata.mode - const fillStyle = CanvasUtils.fillStyleForDegree(ctx, keyCh.key.degreeForMidi(note.pitch) + mode) - const hovering = track.hover && track.hover.id == note.id - const playing = state.playback.playing && note.range.overlapsPoint(state.playback.time) - TrackNotes.renderNote(state, trackIndex, ctx, note.range, row, xMin, xMax, fillStyle, hovering, false, playing) - } - - if (!state.playback.playing) - { - for (const [note, keyCh, xMin, xMax] of TrackNotes.iterNotesAndKeyChangesAtRange(state, trackIndex, visibleTimeRange)) - { - if (!track.selection.has(note.id)) - continue - - const row = TrackNotes.rowForPitch(state, trackIndex, note.pitch, keyCh.key) - const mode = keyCh.key.scale.metadata.mode - const fillStyle = CanvasUtils.fillStyleForDegree(ctx, keyCh.key.degreeForMidi(note.pitch) + mode) - const hovering = track.hover && track.hover.id == note.id - TrackNotes.renderNote(state, trackIndex, ctx, note.range, row, xMin, xMax, fillStyle, hovering, true, false) - } - } - - const draw = state.tracks[trackIndex].draw - if (draw) - { - ctx.globalAlpha = 0.6 - - const keyCh = state.project.keyChanges.findActiveAt(draw.time1) - const key = keyCh ? keyCh.key : Editor.defaultKey() - const row = TrackNotes.rowForPitch(state, trackIndex, draw.pitch, key) - const mode = key.scale.metadata.mode - const fillStyle = CanvasUtils.fillStyleForDegree(ctx, key.degreeForMidi(draw.pitch) + mode) - TrackNotes.renderNote(state, trackIndex, ctx, new Range(draw.time1, draw.time2).sorted(), row, -Infinity, Infinity, fillStyle) - - ctx.globalAlpha = 1 - } - } - - - static renderNote(state, trackIndex, ctx, range, row, xMin, xMax, fillStyle, hovering, selected, playing) - { - const rect = TrackNotes.rectForNote(state, trackIndex, range, row, xMin, xMax) - - ctx.fillStyle = fillStyle - - ctx.beginPath() - ctx.fillRect(rect.x, rect.y, rect.w, rect.h) - - if (hovering) - { - ctx.globalAlpha = 0.4 - ctx.fillStyle = "#fff" - ctx.fillRect(rect.x, rect.y, rect.w, rect.h) - ctx.globalAlpha = 1 - } - - if (selected || playing) - { - const margin = 3 - ctx.globalAlpha = 0.6 - ctx.fillStyle = "#fff" - ctx.fillRect(rect.x, rect.y + margin, rect.w, rect.h - margin * 2) - ctx.globalAlpha = 1 - } - } -} \ No newline at end of file diff --git a/src_old/editor2/EditorContent.tsx b/src_old/editor2/EditorContent.tsx deleted file mode 100644 index a8d0e9e..0000000 --- a/src_old/editor2/EditorContent.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import React from "react" -import Editor from "./editor" -import EditorState from "./editorState" -import { AppState, AppReducer, ContentManager } from "../AppState" -import { useAppManager } from "../AppContext" -import Rect from "../util/rect" - - -interface EditorContentProps -{ - contentId: number - rect: Rect -} - - -export function EditorContent(props: EditorContentProps) -{ - const appManager = useAppManager() - const contentCtx = appManager.makeContentManager(props.contentId) - - const refDiv = React.useRef(null) - const refCanvas = React.useRef(null) - const needsResetView = React.useRef(true) - - const transformMousePos = (canvas: HTMLCanvasElement, ev: MouseEvent) => - { - const rect = canvas.getBoundingClientRect() - return { - x: (ev.clientX - rect.left), - y: (ev.clientY - rect.top) - } - } - - const resize = (contentCtx: ContentManager) => - { - if (!refDiv.current) - return - - const rect = refDiv.current!.getBoundingClientRect() - const x = Math.floor(rect.x) - const y = Math.floor(rect.y) - const w = Math.floor(rect.width) - const h = Math.floor(rect.height) - - refCanvas.current!.style.width = w + "px" - refCanvas.current!.style.height = h + "px" - refCanvas.current!.width = w - refCanvas.current!.height = h - - Editor.resize(contentCtx, { x, y, w, h }) - Editor.tracksRefresh(contentCtx) - } - - React.useEffect(() => - { - resize(contentCtx) - contentCtx.dispatch() - - }, [props.rect]) - - React.useEffect(() => - { - if (!refCanvas.current) - return - - const refresh = () => - { - if (!contentCtx.contentState || contentCtx.contentState.tracks.length == 0) - resize(contentCtx) - } - - const preventDefault = (ev: Event) => ev.preventDefault() - - const onMouseMove = (ev: MouseEvent) => - { - refresh() - const pos = transformMousePos(refCanvas.current!, ev) - Editor.reduce_mouseMove(contentCtx, { pos }) - contentCtx.dispatch() - } - - const onMouseDown = (ev: MouseEvent) => - { - //if (window.document.activeElement) - // window.document.activeElement.blur() - - refresh() - ev.preventDefault() - const pos = transformMousePos(refCanvas.current!, ev) - - Editor.reduce_mouseMove(contentCtx, { pos }) - Editor.reduce_mouseDown(contentCtx, { rightButton: ev.button != 0, ctrlKey: ev.ctrlKey }) - contentCtx.dispatch() - } - - const onMouseUp = (ev: MouseEvent) => - { - refresh() - const pos = transformMousePos(refCanvas.current!, ev) - Editor.reduce_mouseMove(contentCtx, { pos }) - Editor.reduce_mouseUp(contentCtx, { }) - Editor.reduce_mouseMove(contentCtx, { pos }) - contentCtx.dispatch() - } - - const onMouseWheel = (ev: MouseWheelEvent) => - { - refresh() - ev.preventDefault() - Editor.reduce_mouseWheel(contentCtx, { deltaX: ev.deltaX, deltaY: ev.deltaY }) - contentCtx.dispatch() - } - - const onKeyDown = (ev: KeyboardEvent) => - { - refresh() - Editor.reduce_keyDown(contentCtx, { key: ev.key.toLowerCase() }) - Editor.reduce_keyCommand(contentCtx, { - ev, - key: ev.key.toLowerCase(), - ctrlKey: ev.ctrlKey, - shiftKey: ev.shiftKey, - }) - contentCtx.dispatch() - } - - const onKeyUp = (ev: KeyboardEvent) => - { - refresh() - Editor.reduce_keyUp(contentCtx, { key: ev.key.toLowerCase() }) - contentCtx.dispatch() - } - - window.addEventListener("mousemove", onMouseMove) - window.addEventListener("mouseup", onMouseUp) - window.addEventListener("keydown", onKeyDown) - window.addEventListener("keyup", onKeyUp) - - refCanvas.current!.addEventListener("mousedown", onMouseDown) - refCanvas.current!.addEventListener("wheel", onMouseWheel) - refCanvas.current!.addEventListener("mouseup", preventDefault) - refCanvas.current!.addEventListener("contextmenu", preventDefault) - - return () => - { - window.removeEventListener("mousemove", onMouseMove) - window.removeEventListener("mouseup", onMouseUp) - window.removeEventListener("keydown", onKeyDown) - window.removeEventListener("keyup", onKeyUp) - - if (!refCanvas.current) - return - - refCanvas.current!.removeEventListener("mousedown", onMouseDown) - refCanvas.current!.removeEventListener("wheel", onMouseWheel) - refCanvas.current!.removeEventListener("mouseup", preventDefault) - refCanvas.current!.removeEventListener("contextmenu", preventDefault) - } - - }, [refCanvas.current, props.contentId]) - - React.useEffect(() => - { - if (!refCanvas.current) - return - - Editor.render(contentCtx, refCanvas.current!.getContext("2d")!) - - const mouseAction = - contentCtx.contentState.mouse.action || - (contentCtx.contentState.mouse.hover && contentCtx.contentState.mouse.hover.action)! - - if (contentCtx.contentState.tracks.some((tr: any) => !!tr.draw) || mouseAction & (Editor.actionDraw)) - refCanvas.current!.style.cursor = "crosshair" - else if (mouseAction & (Editor.actionDragTime)) - refCanvas.current!.style.cursor = (contentCtx.contentState.mouse.down ? "grabbing" : "grab") - else if (mouseAction & (Editor.actionPan)) - refCanvas.current!.style.cursor = "move" - else if (mouseAction & (Editor.actionStretchTimeStart | Editor.actionStretchTimeEnd)) - refCanvas.current!.style.cursor = "col-resize" - else - refCanvas.current!.style.cursor = "text" - - }, [refCanvas.current, contentCtx.appState]) - - React.useEffect(() => - { - if (!contentCtx.contentState || contentCtx.contentState.tracks.length == 0) - { - window.requestAnimationFrame(() => - { - resize(contentCtx) - contentCtx.dispatch() - }) - return - } - - }, [props.contentId]) - - - return ( -
- - - { contentCtx.contentState.tracks.map((track, i) => -
- Track Name -
- )} -
- ) -} \ No newline at end of file diff --git a/src_old/editor2/InspectorContent.tsx b/src_old/editor2/InspectorContent.tsx deleted file mode 100644 index f62d6d4..0000000 --- a/src_old/editor2/InspectorContent.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import React from "react" -import ButtonList from "../toolbox/ButtonList" -import DropdownMenu from "../toolbox/DropdownMenu" -import Editor from "./editor" -import { AppState } from "../AppState" -import Rect from "../util/rect" -import Project from "../project/project2" -import * as Theory from "../theory/theory" -import { useAppManager } from "../AppContext" - - -interface InspectorContentProps -{ - contentId: number - rect: Rect -} - - -interface InspectorState -{ - elemIds: Project.ID[] -} - - -export default function InspectorContent(props: InspectorContentProps) -{ - const appManager = useAppManager() - const contentCtx = appManager.makeContentManager(props.contentId) - - - const elem = contentCtx.appState.project.elems.get(contentCtx.contentState.elemIds[0]) - if (!elem) - return null - - - const keyCh = elem as Project.KeyChange - const key = keyCh.key - - - const chromaticOptions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(chroma => - { - chroma = Theory.Utils.mod(chroma + key.tonic.chroma, 12) - return { - label: key.nameForChroma(chroma).str, - value: chroma, - bkgColor: Theory.Utils.degreeToColor(key.degreeForChroma(chroma)), - width: "2em", - } - }) - - - const chromaticSelected = [0, 1, 2, 3, 4, 5, 6].map(degree => key.chromaForDegree(degree)) - - - const tonicLetterOptions = [0, 4, 1, 5, 2, 6, 3].map(letter => - { - return { - label: Theory.Utils.letterToStr(letter), - value: letter, - } - }) - - - const tonicAccidentalOptions = [-1, 0, 1].map(acc => - { - return { - label: acc == 0 ? "♮" : Theory.Utils.accidentalToStr(acc), - value: acc, - } - }) - - - const scaleOptions = Theory.Scale.list.map(scale => - { - return { - label: scale.names[0], - value: scale.id, - } - }) - - - const onChangeTonicLetter = (item: any) => - { - const tonic = new Theory.PitchName(item.value, key.tonic.accidental) - const newKey = new Theory.Key(tonic, key.scale) - changeKey(newKey) - } - - - const onChangeTonicAccidental = (item: any) => - { - const tonic = new Theory.PitchName(key.tonic.letter, item.value) - const newKey = new Theory.Key(tonic, key.scale) - changeKey(newKey) - } - - - const onChangeScale = (item: any) => - { - const scale = Theory.Scale.fromId(item.value) - const newKey = new Theory.Key(key.tonic, scale) - changeKey(newKey) - } - - - const changeKey = (newKey: Theory.Key) => - { - const newKeyCh = Project.Element.withChanges(keyCh, { key: newKey }) - const project = Project.upsertTimedElement(appManager.appState.project, newKeyCh) - appManager.mergeAppState({ project }) - appManager.dispatch() - } - - - return
- -
- [Key Change]
- { (elem as Project.KeyChange).key.str } -
- -
-
- Tonic -
- - - -
- Accidental -
- - - -
- Scale -
- - - -
-
- -
- Pitches -
- - {} } - /> - -
- -
-} \ No newline at end of file diff --git a/src_old/editor2/TrackInstrumentContent.tsx b/src_old/editor2/TrackInstrumentContent.tsx deleted file mode 100644 index 55612bd..0000000 --- a/src_old/editor2/TrackInstrumentContent.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import React from "react" -import ButtonList from "../toolbox/ButtonList" -import DropdownMenu from "../toolbox/DropdownMenu" -import Editor from "./editor" -import { AppState } from "../AppState" -import Rect from "../util/rect" -import Project from "../project/project2" -import * as Theory from "../theory/theory" -import { useAppManager } from "../AppContext" -import ListBox from "../toolbox/ListBox" - - -interface TrackInstrumentContentProps -{ - contentId: number - rect: Rect -} - - -interface TrackInstrumentContentState -{ - elemIds: Project.ID[] -} - - -function getMidiIcon(midiBank: number, midiPreset: number): string -{ - if (midiBank == 128) // Percussion - return "🥁" - - if (midiPreset <= 7) // Piano - return "🎹" - else if (midiPreset <= 15) // Chromatic Percussion - return "🔔" - else if (midiPreset <= 23) // Organ - return "💨" - else if (midiPreset <= 31) // Guitar - return "🎸" - else if (midiPreset <= 39) // Bass - return "🎸" - else if (midiPreset <= 47) // Strings - return "🎻" - else if (midiPreset <= 55) // Ensemble - return "🎻" - else if (midiPreset <= 63) // Brass - return "🎺" - else if (midiPreset <= 71) // Reed - return "🎷" - else if (midiPreset <= 79) // Pipe - return "✏️" - else if (midiPreset <= 87) // Synth Lead - return "🕹️" - else if (midiPreset <= 95) // Synth Pad - return "🕹️" - else if (midiPreset <= 103) // Synth FX - return "🕹️" - else if (midiPreset <= 111) // Ethnic - return "🪕" - else if (midiPreset <= 119) // Percussive - return "🥁" - else if (midiPreset <= 127) // Sound FX - return "🔊" - else - return "🎹" -} - - -export default function TrackInstrumentContent(props: TrackInstrumentContentProps) -{ - const appManager = useAppManager() - const contentCtx = appManager.makeContentManager(props.contentId) - - - const elem = contentCtx.appState.project.elems.get(contentCtx.contentState.elemIds[0]) - if (!elem) - return null - - const track = elem as Project.Track - if (track.trackType != Project.TrackType.Notes) - return null - - const trackNotes = track as Project.TrackNotes - const trackInstr = trackNotes.instrument as Project.TrackInstrumentSflib - const sflibMeta = appManager.appState.sflib - const sflibCurrentColl = sflibMeta.collections.find(coll => coll.id == trackInstr.collectionId)! - const sflibCurrentInstr = sflibCurrentColl.instruments.find(instr => instr.id == trackInstr.instrumentId)! - - - const onChange = (collectionId: string, instrumentId: string) => - { - const newTrack = Project.TrackNotes.withChanges(trackNotes, { - instrument: { - ...trackNotes.instrument, - collectionId, - instrumentId, - } - }) - - const newProject = Project.upsertTrack(contentCtx.appState.project, newTrack) - contentCtx.appManager.mergeAppState({ project: newProject }) - contentCtx.appManager.dispatch() - } - - - return
- -
- -
- Collection -
- -
- Instrument -
- - onChange(item.value, sflibMeta.collections.find(coll => coll.id == item.value)!.instruments[0].id) } - items={ sflibMeta.collections.map(coll => - { - return { - value: coll.id, - label: coll.name, - } - })} - /> - - onChange(sflibCurrentColl.id, item.value) } - items={ sflibCurrentColl.instruments.map(instr => - { - return { - value: instr.id, - label: - getMidiIcon(instr.midiBank, instr.midiPreset) + " " + - //instr.midiBank.toString().padStart(3, "0") + "." + - //instr.midiPreset.toString().padStart(3, "0") + " : " + - instr.name, - } - })} - /> - -
- -
-} \ No newline at end of file diff --git a/src_old/editor2/TrackPopup.tsx b/src_old/editor2/TrackPopup.tsx deleted file mode 100644 index 68d4a43..0000000 --- a/src_old/editor2/TrackPopup.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react" -import PopupButton from "../popup/PopupButton" -import Rect from "../util/rect" - - -interface TrackPopupProps -{ -} - - -export default function TrackPopup(props: TrackPopupProps) -{ - return <> - - - -} \ No newline at end of file diff --git a/src_old/editor2/editor.ts b/src_old/editor2/editor.ts deleted file mode 100644 index 4c45da9..0000000 --- a/src_old/editor2/editor.ts +++ /dev/null @@ -1,1392 +0,0 @@ -import { ContentManager, AppReducer } from "../AppState" -import EditorState, { EditorMode } from "./editorState" -import Track from "./track" -import TrackState from "./trackState" -import TrackStateManager from "./trackStateManager" -import TrackKeyChanges from "./trackKeyChanges" -import TrackMeterChanges from "./trackMeterChanges" -import Project from "../project/project2" -import Rational from "../util/rational" -import Range from "../util/range" -import Rect from "../util/rect" -import * as Theory from "../theory/theory" -import DockableData from "../dockable/state" -import { stat } from "fs" -import TrackPopup from "./TrackPopup" - - -export default class Editor -{ - static actionPan = 0x01 - static actionSelectCursor = 0x02 - static actionSelectRect = 0x04 - static actionDraw = 0x08 - static actionDragTime = 0x10 - static actionDragPitchRow = 0x20 - static actionStretchTimeStart = 0x40 - static actionStretchTimeEnd = 0x80 - - - static makeNewFull(): EditorState - { - return { - mode: EditorMode.Full, - modeTracks: [], - - x: 0, - y: 0, - w: 0, - h: 0, - - trackHeaderW: 200, - - tracks: [], - trackScroll: 0, - - timeScroll: -0.5, - timeScale: 100, - timeSnap: new Rational(1, 16), - timeSnapBase: new Rational(1, 16), - - cursor: - { - visible: true, - time1: new Rational(0), - time2: new Rational(0), - track1: 0, - track2: 0, - }, - - rectCursor: - { - track: 0, - time1: new Rational(0), - time2: new Rational(0), - y1: 0, - y2: 0, - }, - - keys: {}, - - mouse: - { - down: false, - downDate: new Date(), - - action: 0, - - pos: { x: 0, y: 0 }, - posPrev: { x: 0, y: 0 }, - time: new Rational(0), - track: 0, - trackPos: { x: 0, y: 0 }, - trackYRaw: 0, - row: 0, - - hover: null, - - drag: { - xLocked: true, - yLocked: true, - - posOrigin: { x: 0, y: 0 }, - timeOrigin: new Rational(0), - timeScrollOrigin: 0, - rangeOrigin: new Range(new Rational(), new Rational()), - trackOrigin: 0, - trackScrollOrigin: 0, - trackPosOrigin: { x: 0, y: 0 }, - trackYRawOrigin: 0, - trackYScrollOrigin: 0, - rowOrigin: 0, - projectOrigin: Project.getDefault(), - - posDelta: { x: 0, y: 0 }, - timeDelta: new Rational(0), - trackPosDelta: { x: 0, y: 0 }, - rowDelta: 0, - }, - - wheelDate: new Date(), - }, - } - } - - - static makeNewNoteEditor(trackIds: number[]): EditorState - { - const state = Editor.makeNewFull() - state.mode = EditorMode.NoteEditor - state.modeTracks = trackIds - return state - } - - - static init(state: ContentManager) - { - state.contentState = Editor.makeNewFull() - } - - - static resize(state: ContentManager, action: any) - { - state.mergeContentState({ - x: action.x, - y: action.y, - w: action.w, - h: action.h, - }) - } - - - static tracksRefresh(state: ContentManager) - { - const tracks: TrackState[] = [] - - for (let t = 0; t < state.appState.project.tracks.length; t++) - { - const track = state.appState.project.tracks[t] - if (track.trackType == Project.TrackType.Notes) - { - if (state.contentState.mode == EditorMode.Full) - tracks.push({ - type: "notesPreview", - trackIndex: t, - trackId: track.id, - y: 0, - h: 40, - yScroll: 0, - pinned: false, - }) - else if (state.contentState.modeTracks.some(t => t == track.id)) - tracks.push({ - type: "notes", - trackIndex: t, - trackId: track.id, - y: 0, - h: 0, - yScroll: 0, - pinned: false, - }) - } - else if (track.trackType == Project.TrackType.KeyChanges) - tracks.push({ - type: "keyChanges", - trackIndex: t, - trackId: track.id, - y: 0, - h: TrackKeyChanges.knobHeight, - yScroll: 0, - pinned: false, - }) - else if (track.trackType == Project.TrackType.MeterChanges) - tracks.push({ - type: "meterChanges", - trackIndex: t, - trackId: track.id, - y: 0, - h: TrackMeterChanges.knobHeight, - yScroll: 0, - pinned: false, - }) - } - - let fixedH = 0 - for (let t = 0; t < tracks.length; t++) - { - if (tracks[t].h > 0) - fixedH += tracks[t].h - } - - for (let t = 0; t < tracks.length; t++) - { - if (tracks[t].h == 0) - tracks[t].h = state.contentState.h - fixedH - } - - let y = 0 - for (let t = 0; t < tracks.length; t++) - { - tracks[t].y = y - y += tracks[t].h - } - - state.mergeContentState({ tracks }) - - for (let t = 0; t < state.contentState.tracks.length; t++) - Track.execute("init", state, t) - } - - - static reduce_keyDown(state: ContentManager, action: any) - { - state.mergeContentState({ - keys: { ...state.contentState.keys, [action.key]: true }, - }) - } - - - static reduce_keyUp(state: ContentManager, action: any) - { - let keys = { ...state.contentState.keys } - delete keys[action.key] - - state.mergeContentState({ keys }) - } - - - static reduce_keyCommand(state: ContentManager, action: any) - { - - } - - - static reduce_mouseMove(state: ContentManager, action: any) - { - const track = Editor.trackAtY(state, state.contentState.mouse.pos.y) - const trackDrag = !state.contentState.mouse.down ? track : state.contentState.mouse.drag.trackOrigin - - const trackX = action.pos.x - state.contentState.trackHeaderW - const trackYRaw = state.contentState.mouse.pos.y - Editor.trackY(state, trackDrag) - const trackY = trackYRaw + state.contentState.tracks[trackDrag].yScroll - const trackPos = { x: trackX, y: trackY } - - const row = Track.execute("rowAtY", state, trackDrag, trackY) - - state.mergeContentState({ - mouse: { - ...state.contentState.mouse, - pos: action.pos, - posPrev: state.contentState.mouse.pos, - time: Editor.timeAtX(state, trackX), - track, - trackPos, - trackYRaw, - row, - } - }) - - if (!state.contentState.mouse.down) - { - state.mergeContentState({ - mouse: { - ...state.contentState.mouse, - hover: null, - } - }) - - if (state.contentState.keys[state.appState.prefs.editor.keyDraw]) - { - for (let t = 0; t < state.contentState.tracks.length; t++) - { - if (t == state.contentState.mouse.track) - Track.execute("drawHover", state, t) - else - Track.execute("drawClear", state, t) - } - } - else - { - for (let t = 0; t < state.contentState.tracks.length; t++) - { - Track.execute("drawClear", state, t) - - if (t == state.contentState.mouse.track) - Track.execute("hover", state, t) - } - } - } - else - { - const posDelta = { - x: state.contentState.mouse.pos.x - state.contentState.mouse.drag.posOrigin.x, - y: state.contentState.mouse.pos.y - state.contentState.mouse.drag.posOrigin.y, - } - - const timeDelta = - state.contentState.mouse.time.subtract(state.contentState.mouse.drag.timeOrigin) - - const trackPosDelta = { - x: state.contentState.mouse.trackPos.x - state.contentState.mouse.drag.trackPosOrigin.x, - y: state.contentState.mouse.trackPos.y - state.contentState.mouse.drag.trackPosOrigin.y, - } - - const rowDelta = state.contentState.mouse.row - state.contentState.mouse.drag.rowOrigin - - const xLocked = state.contentState.mouse.drag.xLocked && Math.abs(posDelta.x) < state.appState.prefs.editor.mouseDragXLockedDistance - const yLocked = state.contentState.mouse.drag.yLocked && Math.abs(posDelta.y) < state.appState.prefs.editor.mouseDragYLockedDistance - - state.mergeContentState({ - mouse: { - ...state.contentState.mouse, - drag: { - ...state.contentState.mouse.drag, - xLocked, - yLocked, - posDelta, - timeDelta, - trackPosDelta, - rowDelta, - }, - }, - }) - - if (state.contentState.mouse.action == Editor.actionPan) - { - state.mergeContentState({ - timeScroll: state.contentState.mouse.drag.timeScrollOrigin - state.contentState.mouse.drag.posDelta.x / state.contentState.timeScale, - }) - - if (state.contentState.mouse.drag.posOrigin.x > state.contentState.trackHeaderW && - Track.execute("yScrollEnabled", state, state.contentState.mouse.drag.trackOrigin)) - { - const trackState = new TrackStateManager(state, state.contentState.mouse.drag.trackOrigin) - const yScroll = - state.contentState.mouse.drag.trackYScrollOrigin - - state.contentState.mouse.drag.posDelta.y - - trackState.mergeTrackState({ yScroll }) - } - else - { - const lastTrack = state.contentState.tracks[state.contentState.tracks.length - 1] - const trackScroll = - Math.max(0, - Math.min( - lastTrack.y + lastTrack.h - state.contentState.h, - state.contentState.mouse.drag.trackScrollOrigin - state.contentState.mouse.drag.posDelta.y)) - - state.mergeContentState({ trackScroll }) - } - } - else if (state.contentState.mouse.action == Editor.actionSelectCursor) - { - Editor.cursorDrag(state, state.contentState.mouse.time, state.contentState.mouse.track) - Editor.selectionClear(state) - Editor.selectionAddAtCursor(state) - Editor.handleEdgeScroll(state, false) - } - else if (state.contentState.mouse.action == Editor.actionSelectRect) - { - state.mergeContentState({ - rectCursor: { ...state.contentState.rectCursor, - time2: state.contentState.mouse.time, - y2: state.contentState.mouse.trackPos.y, - }, - }) - - Editor.selectionClear(state) - Editor.selectionAddAtRectCursor(state) - Editor.handleEdgeScroll(state, true) - } - else if (state.contentState.mouse.action == Editor.actionDraw) - { - Track.execute("drawDrag", state, state.contentState.mouse.drag.trackOrigin) - Editor.handleEdgeScroll(state, false) - } - else - { - Editor.drag(state) - Editor.handleEdgeScroll(state, true) - } - } - } - - - static drag(state: ContentManager) - { - let mouseActionBlocked = 0 - if (state.contentState.mouse.drag.xLocked) - mouseActionBlocked |= Editor.actionDragTime | Editor.actionStretchTimeEnd | Editor.actionStretchTimeStart - - if (state.contentState.mouse.drag.yLocked) - mouseActionBlocked |= Editor.actionDragPitchRow - - const mouseAction = state.contentState.mouse.action & ~mouseActionBlocked - const mouseDrag = state.contentState.mouse.drag - - for (const id of state.appState.selection) - { - const elem = mouseDrag.projectOrigin.elems.get(id) - if (!elem) - continue - - let changes: any = {} - - if (elem.type == Project.ElementType.Note) - { - const rangedElem = elem as any as Project.RangedElement - - if (mouseAction & Editor.actionDragTime) - changes.range = rangedElem.range.displace(mouseDrag.timeDelta) - - if (mouseAction & Editor.actionStretchTimeStart) - { - changes.range = rangedElem.range.stretch(mouseDrag.timeDelta, mouseDrag.rangeOrigin.end, mouseDrag.rangeOrigin.start) - if (rangedElem.range.start.compare(mouseDrag.rangeOrigin.start) == 0) - changes.range = new Range(changes.range.start.snap(state.contentState.timeSnap), changes.range.end) - - changes.range = changes.range.sorted() - } - - if (mouseAction & Editor.actionStretchTimeEnd) - { - changes.range = rangedElem.range.stretch(mouseDrag.timeDelta, mouseDrag.rangeOrigin.start, mouseDrag.rangeOrigin.end) - if (rangedElem.range.end.compare(mouseDrag.rangeOrigin.end) == 0) - changes.range = new Range(changes.range.start, changes.range.end.snap(state.contentState.timeSnap)) - - changes.range = changes.range.sorted() - } - - if (mouseAction & Editor.actionDragPitchRow) - { - const note = rangedElem as Project.Note - const key = Editor.keyAt(state, state.contentState.tracks[state.contentState.mouse.drag.trackOrigin].trackId, rangedElem.range.start) - const degree = key.octavedDegreeForMidi(note.pitch) - const newPitch = key.midiForDegree(Math.floor(degree + mouseDrag.rowDelta)) - changes.pitch = newPitch - } - - const newRangedElem = Project.RangedElement.withChanges(rangedElem, changes) - - state.mergeAppState({ - project: Project.upsertRangedElement(state.appState.project, newRangedElem) - }) - } - - else if (elem.type == Project.ElementType.KeyChange || - elem.type == Project.ElementType.MeterChange) - { - const timedElem = elem as any as Project.TimedElement - - if (mouseAction & Editor.actionDragTime) - changes.time = timedElem.time.add(mouseDrag.timeDelta).snap(state.contentState.timeSnap) - - if (mouseAction & Editor.actionStretchTimeStart) - { - changes.time = timedElem.time.stretch(mouseDrag.timeDelta, mouseDrag.rangeOrigin.end, mouseDrag.rangeOrigin.start) - if (timedElem.time.compare(mouseDrag.rangeOrigin.start) == 0) - changes.time = changes.time.snap(state.contentState.timeSnap) - } - - if (mouseAction & Editor.actionStretchTimeEnd) - { - changes.time = timedElem.time.stretch(mouseDrag.timeDelta, mouseDrag.rangeOrigin.start, mouseDrag.rangeOrigin.end) - if (timedElem.time.compare(mouseDrag.rangeOrigin.start) == 0) - changes.time = changes.time.snap(state.contentState.timeSnap) - } - - const newTimedElem = Project.TimedElement.withChanges(timedElem, changes) - - state.mergeAppState({ - project: Project.upsertTimedElement(state.appState.project, newTimedElem) - }) - } - } - } - - - static reduce_mouseDown(state: ContentManager, action: any) - { - if (state.contentState.mouse.down) - return - - const now = new Date() - const timeSinceLastDown = now.getTime() - state.contentState.mouse.downDate.getTime() - const isDoubleClick = timeSinceLastDown < 250 - - state.mergeContentState({ - mouse: { ...state.contentState.mouse, - down: true, - downDate: now, - drag: { ...state.contentState.mouse.drag, - xLocked: true, - yLocked: true, - posOrigin: state.contentState.mouse.pos, - timeOrigin: state.contentState.mouse.time, - timeScrollOrigin: state.contentState.timeScroll, - trackScrollOrigin: state.contentState.trackScroll, - trackOrigin: state.contentState.mouse.track, - trackPosOrigin: state.contentState.mouse.trackPos, - trackYRawOrigin: state.contentState.mouse.trackYRaw, - trackYScrollOrigin: state.contentState.tracks[state.contentState.mouse.track].yScroll, - rowOrigin: state.contentState.mouse.row, - projectOrigin: state.appState.project, - }, - action: 0, - } - }) - - Editor.popupClear(state) - - if (action.rightButton || state.contentState.keys[state.appState.prefs.editor.keyPan]) - { - state.mergeContentState({ - mouse: { ...state.contentState.mouse, - action: Editor.actionPan - } - }) - } - else if (state.contentState.keys[state.appState.prefs.editor.keySelectRect]) - { - Editor.cursorSetVisible(state, false) - - state.mergeContentState({ - rectCursor: { - track: state.contentState.mouse.track, - time1: state.contentState.mouse.time, - time2: state.contentState.mouse.time, - y1: state.contentState.mouse.trackPos.y, - y2: state.contentState.mouse.trackPos.y, - }, - mouse: { ...state.contentState.mouse, - action: Editor.actionSelectRect, - } - }) - } - else if (state.contentState.keys[state.appState.prefs.editor.keyDraw]) - { - Editor.cursorSetVisible(state, false) - Editor.selectionClear(state) - Track.execute("drawStart", state, state.contentState.mouse.track) - - state.mergeContentState({ - mouse: { ...state.contentState.mouse, - action: Editor.actionDraw, - } - }) - } - else - { - const selectMultiple = state.contentState.keys[state.appState.prefs.editor.keySelectMultiple] - - if (isDoubleClick && state.contentState.mode == EditorMode.Full) - { - let noteEditorState = Editor.makeNewNoteEditor([state.contentState.tracks[state.contentState.mouse.track].trackId]) - noteEditorState.timeScale = state.contentState.timeScale - noteEditorState.timeScroll = state.contentState.timeScroll - - state.appState = AppReducer.createOrUpdateTab( - state.appState, state.contentId, - "editorNotes", noteEditorState) - - state.mergeContentState({ - mouse: { ...state.contentState.mouse, - down: false, - } - }) - } - else if (state.contentState.mouse.hover) - { - const alreadySelected = state.appState.selection.has(state.contentState.mouse.hover.id) - - if (!selectMultiple && !alreadySelected) - Editor.selectionClear(state) - - if (!alreadySelected || !selectMultiple) - Editor.selectionAdd(state, state.contentState.mouse.hover.id) - else - Editor.selectionRemove(state, state.contentState.mouse.hover.id) - - Editor.cursorSetVisible(state, false) - - state.mergeContentState({ - mouse: { ...state.contentState.mouse, - drag: { ...state.contentState.mouse.drag, - rangeOrigin: Editor.selectionRange(state)!, - }, - action: state.contentState.mouse.hover.action, - } - }) - } - else - { - if (!selectMultiple) - Editor.selectionClear(state) - - Editor.cursorPlace(state, state.contentState.mouse.time, state.contentState.mouse.track) - Editor.cursorSetVisible(state, true) - state.mergeContentState({ - mouse: { ...state.contentState.mouse, - action: Editor.actionSelectCursor, - } - }) - state.mergeAppState({ - playback: { ...state.appState.playback, - timeStart: state.contentState.mouse.time, - } - }) - } - } - } - - - static reduce_mouseUp(state: ContentManager, action: any) - { - if (!state.contentState.mouse.down) - return state - - if (state.contentState.mouse.action == Editor.actionDraw) - { - Track.execute("drawEnd", state, state.contentState.mouse.track) - } - else if (state.contentState.mouse.action == Editor.actionPan && - state.contentState.mouse.drag.xLocked && - state.contentState.mouse.drag.yLocked) - { - if (state.contentState.mouse.pos.x < state.contentState.trackHeaderW) - { - Editor.popup(state, state.contentState.mouse.track, - "trackInstrument", - new Rect( - state.contentState.mouse.pos.x, - state.contentState.mouse.pos.y, - 350, 500), - { elemIds: [state.contentState.tracks[state.contentState.mouse.track].trackId] }) - - /*state.appState = AppReducer.createPopup( - state.appState, - new Rect( - state.contentState.x + state.contentState.mouse.pos.x, - state.contentState.y + state.contentState.mouse.pos.y, - 0, 0), - TrackPopup, {})*/ - } - else if (state.contentState.mouse.hover) - { - Editor.selectionClear(state) - Editor.selectionAdd(state, state.contentState.mouse.hover.id) - Track.execute("popup", state, state.contentState.mouse.track) - } - } - - state.mergeContentState({ - mouse: { ...state.contentState.mouse, - down: false, - action: 0, - } - }) - } - - - static reduce_mouseWheel(state: ContentManager, action: any) - { - if (Math.abs(action.deltaX) > 0) - { - let timeScroll = state.contentState.timeScroll + 0.01 / (state.contentState.timeScale / 100) * action.deltaX - let wheelDate = new Date() - - state.mergeContentState({ - timeScroll, - mouse: { ...state.contentState.mouse, - wheelDate - } - }) - } - else if (new Date().getTime() - state.contentState.mouse.wheelDate.getTime() > 250) - { - const snap = new Rational(1, 1024) - const prevMouseTime = Editor.timeAtX(state, state.contentState.mouse.pos.x - state.contentState.trackHeaderW, snap) - - let timeScale = state.contentState.timeScale * (action.deltaY > 0 ? 0.8 : 1.25) - timeScale = Math.max(4, Math.min(2048, timeScale)) - state.mergeContentState({ timeScale }) - - const newMouseTime = Editor.timeAtX(state, state.contentState.mouse.pos.x - state.contentState.trackHeaderW, snap) - - const timeScroll = state.contentState.timeScroll - newMouseTime.subtract(prevMouseTime).asFloat() - - const timeSnapAdjustThresholdUpper = 24 - const timeSnapAdjustThresholdLower = 8 - let timeSnap = state.contentState.timeSnapBase - - if (timeSnap.asFloat() * timeScale > timeSnapAdjustThresholdUpper) - while (timeSnap.asFloat() * timeScale > timeSnapAdjustThresholdUpper) - timeSnap = timeSnap.divide(new Rational(2)) - - else if (timeSnap.asFloat() * timeScale < timeSnapAdjustThresholdLower) - while (timeSnap.asFloat() * timeScale < timeSnapAdjustThresholdLower) - timeSnap = timeSnap.divide(new Rational(1, 2)) - - state.mergeContentState({ timeScroll, timeSnap }) - } - } - - - static handleEdgeScroll(state: ContentManager, handleYScroll: boolean) - { - const threshold = state.appState.prefs.editor.mouseEdgeScrollThreshold - const speed = state.appState.prefs.editor.mouseEdgeScrollSpeed - - const mouseX = state.contentState.mouse.pos.x - state.contentState.trackHeaderW - - let timeScroll = state.contentState.timeScroll - - if (mouseX > state.contentState.w - state.contentState.trackHeaderW - threshold) - timeScroll += state.contentState.timeSnap.asFloat() * speed - - else if (mouseX < threshold) - timeScroll -= state.contentState.timeSnap.asFloat() * speed - - state.mergeContentState({ timeScroll }) - - if (handleYScroll && - Track.execute("yScrollEnabled", state, state.contentState.mouse.drag.trackOrigin)) - { - const threshold = state.appState.prefs.editor.mouseEdgeScrollThreshold - const speed = state.appState.prefs.editor.mouseEdgeScrollSpeed * 2 - - const mouseY = state.contentState.mouse.trackYRaw - - const trackState = new TrackStateManager(state, state.contentState.mouse.drag.trackOrigin) - - if (mouseY > trackState.trackState.h - threshold) - trackState.mergeTrackState({ yScroll: trackState.trackState.yScroll + speed }) - - else if (mouseY < threshold) - trackState.mergeTrackState({ yScroll: trackState.trackState.yScroll - speed }) - } - } - - - static popup(state: ContentManager, trackIndex: number, type: string, rect: Rect, popupState: any) - { - const x = state.contentState.x + state.contentState.trackHeaderW - const y = state.contentState.y + Editor.trackY(state, trackIndex) - - state.appState = AppReducer.createFloating(state.appState, type, popupState, rect.displace(x, y)) - } - - - static popupClear(state: ContentManager) - { - state.appState = AppReducer.removeFloating(state.appState, "inspector") - } - - - static cursorSetVisible(state: ContentManager, visible: boolean) - { - state.mergeContentState({ - cursor: { - ...state.contentState.cursor, - visible, - } - }) - } - - - static cursorPlace(state: ContentManager, time: Rational | null, trackIndex: number | null) - { - if (trackIndex !== null) - trackIndex = Math.max(0, Math.min(state.contentState.tracks.length - 1, trackIndex)) - - const cursor = state.contentState.cursor - - state.mergeContentState({ - cursor: { ...cursor, - time1: time === null ? cursor.time1 : time, - time2: time === null ? cursor.time2 : time, - track1: trackIndex === null ? cursor.track1 : trackIndex, - track2: trackIndex === null ? cursor.track2 : trackIndex, - }, - }) - } - - - static cursorDrag(state: ContentManager, time: Rational | null, trackIndex: number | null) - { - if (trackIndex !== null) - trackIndex = Math.max(0, Math.min(state.contentState.tracks.length - 1, trackIndex)) - - const cursor = state.contentState.cursor - - state.mergeContentState({ - cursor: { ...cursor, - time2: time === null ? cursor.time2 : time, - track2: trackIndex === null ? cursor.track2 : trackIndex, - }, - }) - } - - - static selectionAdd(state: ContentManager, id: number) - { - state.mergeAppState({ - selection: state.appState.selection.add(id), - }) - } - - - static selectionAddAtCursor(state: ContentManager) - { - const trackMin = Math.min(state.contentState.cursor.track1, state.contentState.cursor.track2) - const trackMax = Math.max(state.contentState.cursor.track1, state.contentState.cursor.track2) - - const time1 = state.contentState.cursor.time1 - const time2 = state.contentState.cursor.time2 - if (time1.compare(time2) != 0) - { - let selection = state.appState.selection - - const range = new Range(time1, time2, false, false).sorted() - - for (let t = trackMin; t <= trackMax; t++) - { - const elems = Track.execute("elemsAt", state, t, { range }) - - for (const elem of elems) - selection = selection.add(elem) - } - - state.mergeAppState({ selection }) - } - } - - - static selectionAddAtRectCursor(state: ContentManager) - { - const track = state.contentState.rectCursor.track - - const time1 = state.contentState.rectCursor.time1 - const time2 = state.contentState.rectCursor.time2 - if (time1.compare(time2) != 0) - { - let selection = state.appState.selection - - const range = new Range(time1, time2, false, false).sorted() - const y1 = Math.min(state.contentState.rectCursor.y1, state.contentState.rectCursor.y2) - const y2 = Math.max(state.contentState.rectCursor.y1, state.contentState.rectCursor.y2) - const elems = Track.execute("elemsAt", state, track, { range, y1, y2 }) - - for (const elem of elems) - selection = selection.add(elem) - - state.mergeAppState({ selection }) - } - } - - - static selectionRemove(state: ContentManager, id: number) - { - state.mergeAppState({ - selection: state.appState.selection.remove(id), - }) - } - - - static selectionClear(state: ContentManager) - { - state.mergeAppState({ - selection: state.appState.selection.clear(), - }) - } - - - static selectionRange(state: ContentManager): Range | null - { - let range: Range | null = null - - for (const id of state.appState.selection) - { - const elem = state.appState.project.elems.get(id) as any - if (!elem) - continue - - if (elem.range) - { - const rangedElem = elem as Project.RangedElement - range = Range.merge(range, rangedElem.range) - } - - else if (elem.time) - { - const timedElem = elem as Project.TimedElement - range = Range.merge(range, Range.fromPoint(timedElem.time)) - } - } - - return range - } - - - static xAtTime(state: ContentManager, time: Rational): number - { - return (time.asFloat() - state.contentState.timeScroll) * state.contentState.timeScale - } - - - static timeAtX(state: ContentManager, x: number, timeSnap?: Rational): Rational - { - timeSnap = timeSnap || state.contentState.timeSnap - const time = x / state.contentState.timeScale + state.contentState.timeScroll - return Rational.fromFloat(time, timeSnap.denominator) - } - - - static timeRangeAtX(state: ContentManager, x1: number, x2: number, timeSnap?: Rational) - { - timeSnap = timeSnap || state.contentState.timeSnap - return new Range( - Editor.timeAtX(state, x1, timeSnap).subtract(timeSnap), - Editor.timeAtX(state, x2, timeSnap).add(timeSnap)) - } - - - static trackY(state: ContentManager, trackIndex: number): number - { - return state.contentState.tracks[trackIndex].y - state.contentState.trackScroll - } - - - static trackAtY(state: ContentManager, y: number): number - { - y += state.contentState.trackScroll - - if (y < 0) - return 0 - - for (let t = 0; t < state.contentState.tracks.length; t++) - { - const track = state.contentState.tracks[t] - - if (y >= track.y && y <= track.y + track.h) - return t - } - - return state.contentState.tracks.length - 1 - } - - - static rectForTrack(state: ContentManager, trackIndex: number): Rect | null - { - let y = 0 - for (let t = 0; t < state.contentState.tracks.length; t++) - { - const h = state.contentState.tracks[t].h - - if (t == trackIndex) - { - return new Rect( - state.contentState.trackHeaderW, - y, - state.contentState.w - state.contentState.trackHeaderW, - h) - } - - y += h - } - - return null - } - - - static visibleTimeRange(state: ContentManager): Range - { - return new Range( - Editor.timeAtX(state, 0).subtract(state.contentState.timeSnap), - Editor.timeAtX(state, state.contentState.w).add(state.contentState.timeSnap)) - } - - - static keyAt(state: ContentManager, trackId: Project.ID, time: Rational): Theory.Key - { - const keyChangeTrackId = Project.keyChangeTrackForTrack(state.appState.project, trackId) - const keyChangeTrackTimedElems = state.appState.project.timedLists.get(keyChangeTrackId) - if (!keyChangeTrackTimedElems) - return Editor.defaultKey() - - const keyCh = keyChangeTrackTimedElems.findActiveAt(time) - if (keyCh) - return (keyCh as Project.KeyChange).key - - const firstKeyCh = keyChangeTrackTimedElems.findFirst() - if (firstKeyCh) - return (firstKeyCh as Project.KeyChange).key - - return Editor.defaultKey() - } - - - static meterAt(state: ContentManager, trackId: Project.ID, time: Rational): Theory.Meter - { - const meterChangeTrackId = Project.meterChangeTrackForTrack(state.appState.project, trackId) - const meterChangeTrackTimedElems = state.appState.project.timedLists.get(meterChangeTrackId) - if (!meterChangeTrackTimedElems) - return Editor.defaultMeter() - - const meterCh = meterChangeTrackTimedElems.findActiveAt(time) - if (meterCh) - return (meterCh as Project.MeterChange).meter - - const firstMeterCh = meterChangeTrackTimedElems.findFirst() - if (firstMeterCh) - return (firstMeterCh as Project.MeterChange).meter - - return Editor.defaultMeter() - } - - - static defaultKey(): Theory.Key - { - return Theory.Key.parse("C Major") - } - - - static defaultMeter(): Theory.Meter - { - return new Theory.Meter(4, 4) - } - - - static render(state: ContentManager, ctx: CanvasRenderingContext2D) - { - ctx.save() - - ctx.fillStyle = state.appState.prefs.editor.bkgColor - ctx.fillRect(0, 0, state.contentState.w, state.contentState.h) - - ctx.save() - ctx.translate(state.contentState.trackHeaderW, 0) - - ctx.beginPath() - ctx.rect( - 0, - 0, - state.contentState.w - state.contentState.trackHeaderW, - state.contentState.h) - ctx.clip() - - Editor.renderCursorHighlight(state, ctx) - Editor.renderBackgroundMeasures(state, ctx) - - ctx.restore() - - let y = -state.contentState.trackScroll - for (let t = 0; t < state.contentState.tracks.length; t++) - { - ctx.save() - ctx.translate(state.contentState.trackHeaderW, y) - - ctx.beginPath() - ctx.rect( - 0, - 1, - state.contentState.w - state.contentState.trackHeaderW, - state.contentState.tracks[t].h - 1) - ctx.clip() - - ctx.translate(0, -state.contentState.tracks[t].yScroll) - - Editor.renderRectCursorHighlight(state, ctx, t) - Track.execute("render", state, t, ctx) - Editor.renderRectCursorContour(state, ctx, t) - - ctx.restore() - - y += state.contentState.tracks[t].h - - ctx.strokeStyle = state.appState.prefs.editor.trackHBorderColor - ctx.beginPath() - ctx.moveTo(0, y + 0.5) - ctx.lineTo(state.contentState.w, y + 0.5) - ctx.stroke() - } - - ctx.save() - ctx.translate(state.contentState.trackHeaderW, 0) - - ctx.beginPath() - ctx.rect( - 0, - 0, - state.contentState.w - state.contentState.trackHeaderW, - state.contentState.h) - ctx.clip() - - if (state.contentState.cursor.visible) - { - const timeMin = state.contentState.cursor.time1.min(state.contentState.cursor.time2)! - const timeMax = state.contentState.cursor.time1.max(state.contentState.cursor.time2)! - Editor.renderCursorBeam(state, ctx, timeMin, false) - Editor.renderCursorBeam(state, ctx, timeMax, true) - } - - if (state.appState.playback.playing) - Editor.renderPlaybackBeam(state, ctx, state.appState.playback.time) - - ctx.restore() - - ctx.strokeStyle = state.appState.prefs.editor.trackVBorderColor - ctx.beginPath() - ctx.moveTo(state.contentState.trackHeaderW + 0.5, 0) - ctx.lineTo(state.contentState.trackHeaderW + 0.5, state.contentState.h) - ctx.stroke() - - ctx.restore() - } - - - static renderBackgroundMeasures(state: ContentManager, ctx: CanvasRenderingContext2D) - { - const visibleRange = Editor.visibleTimeRange(state) - - /*ctx.fillStyle = "#1b191c" - - const songXMin = Editor.xAtTime(state, state.project.range.start) - const songXMax = Editor.xAtTime(state, state.project.range.end) - - if (songXMin > 0) - ctx.fillRect(0, 0, songXMin, state.h) - - if (songXMax < state.w) - ctx.fillRect(songXMax, 0, state.w - songXMax, state.h)*/ - - const meterChangeTrackId = Project.meterChangeTrackForTrack(state.appState.project, 0) - const meterChangeList = state.appState.project.timedLists.get(meterChangeTrackId)! - - for (let [meterCh1Raw, meterCh2Raw] of meterChangeList.iterActiveAtRangePairwise(visibleRange)) - { - let timeMin = (meterCh1Raw ? meterCh1Raw.time : null) - let timeMax = (meterCh2Raw ? meterCh2Raw.time : visibleRange.end) - - let measureAlternate = true - - let meterCh1 = meterCh1Raw as (Project.MeterChange | null) - let meterCh2 = meterCh2Raw as (Project.MeterChange | null) - - if (!meterCh1) - { - if (!meterCh2) - continue - - meterCh1 = meterCh2 - timeMin = meterCh2.time - while (timeMin.compare(visibleRange.start) > 0) - { - timeMin = timeMin.subtract(meterCh2.meter.fullCycleDuration) - - if (meterCh2.meter.alternatingMeasureCount % 2 != 0) - measureAlternate = !measureAlternate - } - } - - const meterCh1X = 0.5 + Math.floor(Editor.xAtTime(state, timeMin!)) - const meterCh2X = 0.5 + Math.floor(Editor.xAtTime(state, timeMax)) - - ctx.strokeStyle = state.appState.prefs.editor.meterChangeColor - ctx.lineCap = "square" - ctx.lineWidth = 1 - - for (const [measureN, measureD, time1, time2] of meterCh1.meter.iterMeasuresPairwise(timeMin)) - { - measureAlternate = !measureAlternate - - if (time2.compare(visibleRange.start) < 0) - continue - - if (time1.compare(timeMax) > 0 || time1.compare(visibleRange.end) > 0) - break - - const measureX1 = 0.5 + Math.floor(Editor.xAtTime(state, time1)) - const measureX2 = 0.5 + Math.floor(Editor.xAtTime(state, time2)) - - if (measureAlternate) - { - const x1 = Math.min(meterCh2X, measureX1) - const x2 = Math.min(meterCh2X, measureX2) - - ctx.fillStyle = state.appState.prefs.editor.measureAlternateBkgColor - ctx.fillRect(x1, 0, x2 - x1, state.contentState.h) - } - - if (time1.compare(meterCh1.time) == 0) - ctx.strokeStyle = state.appState.prefs.editor.meterChangeColor - else - ctx.strokeStyle = state.appState.prefs.editor.measureColor - - ctx.beginPath() - ctx.moveTo(measureX1, 0) - ctx.lineTo(measureX1, state.contentState.h) - ctx.stroke() - - const halfSubmeasureSize = Editor.xAtTime(state, new Rational(1, measureD * 2)) - Editor.xAtTime(state, new Rational(0)) - if (halfSubmeasureSize > 16) - { - let halfSubmeasureTime = time1.add(new Rational(-1, measureD * 2)) - for (let sub = 1; sub <= measureN; sub++) - { - halfSubmeasureTime = halfSubmeasureTime.add(new Rational(2, measureD * 2)) - - const halfSubmeasureX = 0.5 + Math.floor(Editor.xAtTime(state, halfSubmeasureTime)) - if (halfSubmeasureX >= meterCh1X && halfSubmeasureX <= meterCh2X) - { - ctx.strokeStyle = state.appState.prefs.editor.halfSubmeasureColor - ctx.beginPath() - ctx.moveTo(halfSubmeasureX, 0) - ctx.lineTo(halfSubmeasureX, state.contentState.h) - ctx.stroke() - } - } - } - - const submeasureSize = Editor.xAtTime(state, new Rational(1, measureD)) - Editor.xAtTime(state, new Rational(0)) - if (submeasureSize > 8) - { - let submeasureTime = time1 - for (let sub = 1; sub <= measureN; sub++) - { - submeasureTime = submeasureTime.add(new Rational(1, measureD)) - - const submeasureX = 0.5 + Math.floor(Editor.xAtTime(state, submeasureTime)) - if (submeasureX >= meterCh1X && submeasureX <= meterCh2X) - { - ctx.strokeStyle = state.appState.prefs.editor.submeasureColor - ctx.beginPath() - ctx.moveTo(submeasureX, 0) - ctx.lineTo(submeasureX, state.contentState.h) - ctx.stroke() - } - } - } - } - } - - const keyChangeTrackId = Project.keyChangeTrackForTrack(state.appState.project, 0) - const keyChangeList = state.appState.project.timedLists.get(keyChangeTrackId)! - - for (const keyCh of keyChangeList.iterAtRange(visibleRange)) - { - const keyChX = 0.5 + Math.floor(Editor.xAtTime(state, keyCh.time)) - - ctx.strokeStyle = state.appState.prefs.editor.keyChangeColor - ctx.lineCap = "square" - ctx.lineWidth = 1 - - ctx.beginPath() - ctx.moveTo(keyChX, 0) - ctx.lineTo(keyChX, state.contentState.h) - ctx.stroke() - } - } - - - static renderPlaybackBeam(state: ContentManager, ctx: CanvasRenderingContext2D, time: Rational) - { - const trackMin = 0 - const trackMax = state.contentState.tracks.length - 1 - - const x = 0.5 + Math.floor(Editor.xAtTime(state, time)) - - ctx.strokeStyle = state.appState.prefs.editor.playbackCursorColor - ctx.lineCap = "square" - ctx.lineWidth = 1 - - const y1 = 0.5 + Math.floor(Editor.trackY(state, trackMin)) - const y2 = 0.5 + Math.floor(Editor.trackY(state, trackMax) + state.contentState.tracks[trackMax].h) - - ctx.beginPath() - ctx.lineTo(x, y1) - ctx.lineTo(x, y2) - ctx.stroke() - } - - - static renderCursorHighlight(state: ContentManager, ctx: CanvasRenderingContext2D) - { - if (!state.contentState.cursor.visible) - return - - const timeMin = state.contentState.cursor.time1.min(state.contentState.cursor.time2)! - const timeMax = state.contentState.cursor.time1.max(state.contentState.cursor.time2)! - const trackMin = Math.min(state.contentState.cursor.track1, state.contentState.cursor.track2) - const trackMax = Math.max(state.contentState.cursor.track1, state.contentState.cursor.track2) - - if (trackMin < 0 || trackMax < 0 || - trackMin >= state.contentState.tracks.length || - trackMax >= state.contentState.tracks.length) - return - - const y1 = 0.5 + Math.floor(Editor.trackY(state, trackMin)) - const y2 = 0.5 + Math.floor(Editor.trackY(state, trackMax) + state.contentState.tracks[trackMax].h) - - const x1 = Editor.xAtTime(state, timeMin) - const x2 = Editor.xAtTime(state, timeMax) - - ctx.fillStyle = state.appState.prefs.editor.selectionBkgColor - ctx.fillRect(x1, y1, x2 - x1, y2 - y1) - } - - - static renderCursorBeam(state: ContentManager, ctx: CanvasRenderingContext2D, time: Rational, tipOffsetSide: boolean) - { - const trackMin = Math.min(state.contentState.cursor.track1, state.contentState.cursor.track2) - const trackMax = Math.max(state.contentState.cursor.track1, state.contentState.cursor.track2) - - if (trackMin < 0 || trackMax < 0 || - trackMin >= state.contentState.tracks.length || - trackMax >= state.contentState.tracks.length) - return - - const x = 0.5 + Math.floor(Editor.xAtTime(state, time)) - - ctx.strokeStyle = state.appState.prefs.editor.selectionCursorColor - ctx.lineCap = "square" - ctx.lineWidth = 1 - - const headSize = 7 * (tipOffsetSide ? -1 : 1) - - const y1 = 0.5 + Math.floor(Editor.trackY(state, trackMin)) - const y2 = 0.5 + Math.floor(Editor.trackY(state, trackMax) + state.contentState.tracks[trackMax].h) - - ctx.beginPath() - ctx.moveTo(x + headSize, y1) - ctx.lineTo(x, y1) - ctx.lineTo(x, y2) - ctx.lineTo(x + headSize, y2) - ctx.stroke() - } - - - static renderRectCursorHighlight(state: ContentManager, ctx: CanvasRenderingContext2D, trackIndex: number) - { - if (state.contentState.mouse.action != Editor.actionSelectRect) - return - - if (trackIndex != state.contentState.rectCursor.track) - return - - const timeMin = state.contentState.rectCursor.time1.min(state.contentState.rectCursor.time2)! - const timeMax = state.contentState.rectCursor.time1.max(state.contentState.rectCursor.time2)! - const y1 = 0.5 + Math.floor(Math.min(state.contentState.rectCursor.y1, state.contentState.rectCursor.y2)) - const y2 = 0.5 + Math.floor(Math.max(state.contentState.rectCursor.y1, state.contentState.rectCursor.y2)) + 1 - - const x1 = 0.5 + Math.floor(Editor.xAtTime(state, timeMin)) - const x2 = 0.5 + Math.floor(Editor.xAtTime(state, timeMax)) - - ctx.fillStyle = state.appState.prefs.editor.selectionBkgColor - ctx.fillRect(x1, y1, x2 - x1, y2 - y1) - } - - - static renderRectCursorContour(state: ContentManager, ctx: CanvasRenderingContext2D, trackIndex: number) - { - if (state.contentState.mouse.action != Editor.actionSelectRect) - return - - if (trackIndex != state.contentState.rectCursor.track) - return - - const timeMin = state.contentState.rectCursor.time1.min(state.contentState.rectCursor.time2)! - const timeMax = state.contentState.rectCursor.time1.max(state.contentState.rectCursor.time2)! - const y1 = 0.5 + Math.floor(Math.min(state.contentState.rectCursor.y1, state.contentState.rectCursor.y2)) - const y2 = 0.5 + Math.floor(Math.max(state.contentState.rectCursor.y1, state.contentState.rectCursor.y2)) + 1 - - const x1 = 0.5 + Math.floor(Editor.xAtTime(state, timeMin)) - const x2 = 0.5 + Math.floor(Editor.xAtTime(state, timeMax)) - - ctx.strokeStyle = state.appState.prefs.editor.selectionCursorColor - ctx.lineCap = "square" - ctx.lineWidth = 1 - ctx.strokeRect(x1, y1, x2 - x1, y2 - y1) - } -} \ No newline at end of file diff --git a/src_old/editor2/editorState.ts b/src_old/editor2/editorState.ts deleted file mode 100644 index a59e5f0..0000000 --- a/src_old/editor2/editorState.ts +++ /dev/null @@ -1,101 +0,0 @@ -import TrackState from "./trackState" -import Project from "../project/project2" -import Rational from "../util/rational" -import Range from "../util/range" - - -export enum EditorMode -{ - Full, - NoteEditor, -} - - -export default interface EditorState -{ - mode: EditorMode - modeTracks: number[] - - x: number - y: number - w: number - h: number - - trackHeaderW: number - - tracks: TrackState[] - trackScroll: number - - timeScroll: number - timeScale: number - timeSnap: Rational - timeSnapBase: Rational - - cursor: - { - visible: boolean - time1: Rational - time2: Rational - track1: number - track2: number - } - - rectCursor: - { - track: number - time1: Rational - time2: Rational - y1: number - y2: number - } - - keys: { [key: string]: boolean } - - mouse: - { - down: boolean - downDate: Date - - action: number - - pos: { x: number, y: number } - posPrev: { x: number, y: number } - time: Rational - track: number, - trackPos: { x: number, y: number } - trackYRaw: number, - row: number, - - hover: null | - { - id: number, - range: Range, - action: number, - } - - drag: - { - xLocked: boolean - yLocked: boolean - - posOrigin: { x: number, y: number } - timeOrigin: Rational - timeScrollOrigin: number - trackScrollOrigin: number - rangeOrigin: Range - trackOrigin: number - trackPosOrigin: { x: number, y: number } - trackYRawOrigin: number - trackYScrollOrigin: number - rowOrigin: number - projectOrigin: Project - - posDelta: { x: number, y: number } - timeDelta: Rational - trackPosDelta: { x: number, y: number } - rowDelta: number - } - - wheelDate: Date - } -} \ No newline at end of file diff --git a/src_old/editor2/track.ts b/src_old/editor2/track.ts deleted file mode 100644 index 1d5d8b9..0000000 --- a/src_old/editor2/track.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { ContentManager } from "../AppState" -import EditorState from "./editorState" -import TrackStateManager from "./trackStateManager" -import TrackNotes from "./trackNotes" -import TrackNotesPreview from "./trackNotesPreview" -import TrackKeyChanges from "./trackKeyChanges" -import TrackMeterChanges from "./trackMeterChanges" -import Project from "../project/project2" - - -export default class Track -{ - static init(state: TrackStateManager) - { - state.mergeTrackState({ - yScroll: 0, - }) - } - - - static handlerForTrackType(type: string): any - { - switch (type) - { - case "notes": return TrackNotes - case "notesPreview": return TrackNotesPreview - case "keyChanges": return TrackKeyChanges - case "meterChanges": return TrackMeterChanges - default: throw "invalid track kind" - } - } - - - static yScrollEnabled(state: TrackStateManager): boolean - { - return false - } - - - static rowAtY(state: TrackStateManager, y: number): number - { - return 0 - } - - - static elemsAt(state: TrackStateManager, region: any): Project.ID[] - { - return [] - } - - - static execute(fnName: string, state: ContentManager, trackIndex: number, ...args: any[]): any - { - const trackStateManager = new TrackStateManager(state, trackIndex) - - const handlerDerived = Track.handlerForTrackType(trackStateManager.trackState.type) - const fnDerived = handlerDerived[fnName] - if (fnDerived) - return fnDerived(trackStateManager, ...args) - - const fn = (Track as any)[fnName] - if (fn) - return fn(trackStateManager, ...args) - - return null - } -} \ No newline at end of file diff --git a/src_old/editor2/trackKeyChanges.ts b/src_old/editor2/trackKeyChanges.ts deleted file mode 100644 index 5f7d679..0000000 --- a/src_old/editor2/trackKeyChanges.ts +++ /dev/null @@ -1,249 +0,0 @@ -import Editor from "./editor" -import Track from "./track" -import TrackStateManager from "./trackStateManager" -import TrackKeyChangesState from "./trackKeyChangesState" -import Rect from "../util/rect" -import Range from "../util/range" -import Project from "../project/project2" -import Rational from "../util/rational" -import * as Theory from "../theory/theory" - - -export default class TrackKeyChanges -{ - static knobWidth = 16 - static knobHeight = 22 - - - static init(state: TrackStateManager) - { - Track.init(state) - state.mergeTrackState({ - type: "keyChanges", - }) - } - - - static hover(state: TrackStateManager) - { - const pos = state.contentState.mouse.trackPos - - const checkRange = Editor.timeRangeAtX( - state.contentStateManager, - pos.x - TrackKeyChanges.knobWidth, - pos.x + TrackKeyChanges.knobWidth) - - let hover = null - - for (const keyCh of TrackKeyChanges.iterAtRange(state, checkRange)) - { - const rect = TrackKeyChanges.knobRectForKeyChange(state, keyCh.time) - if (rect.contains(pos)) - { - hover = - { - id: keyCh.id, - range: Range.fromPoint(keyCh.time), - action: Editor.actionDragTime, - } - } - } - - state.mergeContentState({ - mouse: { ...state.contentState.mouse, - hover, - } - }) - } - - - static drawClear(state: TrackStateManager) - { - state.mergeTrackState({ draw: null }) - } - - - static drawHover(state: TrackStateManager) - { - const time = state.contentState.mouse.time - - state.mergeTrackState({ draw: { time } }) - } - - - static drawDrag(state: TrackStateManager) - { - const draw = state.trackState.draw - if (draw) - { - const time = state.contentState.mouse.time - state.mergeTrackState({ draw: { time } }) - } - } - - - static drawEnd(state: TrackStateManager) - { - const draw = state.trackState.draw - if (draw) - { - const keyCh = new Project.KeyChange( - state.trackState.trackId, - draw.time, - Editor.defaultKey()) - - const id = state.appState.project.nextId - state.mergeAppState({ - project: Project.upsertTimedElement(state.appState.project, keyCh), - selection: state.appState.selection.add(id), - }) - } - } - - - static popup(state: TrackStateManager) - { - const elemId = state.contentState.mouse.hover!.id - const elem = state.appState.project.elems.get(elemId) as Project.KeyChange - - const knob = TrackKeyChanges.knobRectForKeyChange(state, elem.time) - - Editor.popup(state.contentStateManager, state.trackIndex, - "inspector", - new Rect(knob.x2 + 10, knob.y2 + 10, 450, 250), - { elemIds: [elemId] }) - } - - - static elemsAt(state: TrackStateManager, region: any): Project.ID[] - { - const elems = [] - - for (const keyCh of TrackKeyChanges.iterAtRange(state, region.range)) - elems.push(keyCh.id) - - return elems - } - - - static *iterAtRange(state: TrackStateManager, range: Range): Generator - { - const trackElems = state.appState.project.timedLists.get(state.trackState.trackId) - if (!trackElems) - return - - for (const keyCh of trackElems.iterAtRange(range)) - yield keyCh as Project.KeyChange - } - - - static knobRectForKeyChange(state: TrackStateManager, time: Rational) - { - return new Rect( - Editor.xAtTime(state.contentStateManager, time) - TrackKeyChanges.knobWidth / 2, - 0, - TrackKeyChanges.knobWidth, - TrackKeyChanges.knobHeight) - } - - - static render(state: TrackStateManager, ctx: CanvasRenderingContext2D) - { - const visibleRange = Editor.visibleTimeRange(state.contentStateManager) - - const firstKey = Editor.keyAt(state.contentStateManager, state.trackState.trackId, visibleRange.start) - let shouldDrawFirstKey = true - - for (const keyCh of TrackKeyChanges.iterAtRange(state, visibleRange)) - { - if (shouldDrawFirstKey) - { - const x = Editor.xAtTime(state.contentStateManager, keyCh.time) - if (x > 80) - TrackKeyChanges.renderKeyChangeLabel(state, ctx, firstKey) - } - - TrackKeyChanges.renderKeyChange(state, ctx, keyCh) - shouldDrawFirstKey = false - } - - if (shouldDrawFirstKey) - TrackKeyChanges.renderKeyChangeLabel(state, ctx, firstKey) - - const draw = state.trackState.draw - if (draw) - { - ctx.globalAlpha = 0.6 - TrackKeyChanges.renderKeyChangeKnob(state, ctx, draw.time) - ctx.globalAlpha = 1 - } - } - - - static renderKeyChangeKnob(state: TrackStateManager, ctx: CanvasRenderingContext2D, time: Rational) - { - const rect = TrackKeyChanges.knobRectForKeyChange(state, time) - - ctx.fillStyle = state.appState.prefs.editor.keyChangeColor - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2, 0, Math.PI * 2) - ctx.fill() - } - - - static renderKeyChangeLabel(state: TrackStateManager, ctx: CanvasRenderingContext2D, key: Theory.Key) - { - const rect = TrackKeyChanges.knobRectForKeyChange(state, new Rational(0)) - const x = 5 - - ctx.fillStyle = state.appState.prefs.editor.keyChangeColor - - ctx.font = "14px Verdana" - ctx.textAlign = "left" - ctx.textBaseline = "middle" - ctx.fillText(key.str, x, rect.y + rect.h / 2) - } - - - static renderKeyChange(state: TrackStateManager, ctx: CanvasRenderingContext2D, keyCh: Project.KeyChange) - { - const rect = TrackKeyChanges.knobRectForKeyChange(state, keyCh.time) - - ctx.fillStyle = state.appState.prefs.editor.keyChangeColor - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2, 0, Math.PI * 2) - ctx.fill() - - ctx.font = "14px Verdana" - ctx.textAlign = "left" - ctx.textBaseline = "middle" - ctx.fillText(keyCh.key.str, rect.x + rect.w + 5, rect.y + rect.h / 2) - - if (state.appState.selection.has(keyCh.id)) - { - ctx.globalAlpha = 0.75 - ctx.fillStyle = "#fff" - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2 - 3, 0, Math.PI * 2) - ctx.fill() - - ctx.globalAlpha = 1 - } - - const hover = state.contentState.mouse.hover - if (hover && hover.id === keyCh.id) - { - ctx.globalAlpha = 0.5 - ctx.fillStyle = "#fff" - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2, 0, Math.PI * 2) - ctx.fill() - - ctx.globalAlpha = 1 - } - } -} \ No newline at end of file diff --git a/src_old/editor2/trackKeyChangesState.ts b/src_old/editor2/trackKeyChangesState.ts deleted file mode 100644 index 09cd949..0000000 --- a/src_old/editor2/trackKeyChangesState.ts +++ /dev/null @@ -1,11 +0,0 @@ -import TrackState from "./trackState" -import Rational from "../util/rational" - - -export default interface TrackKeyChangesState extends TrackState -{ - draw: null | - { - time: Rational - } -} \ No newline at end of file diff --git a/src_old/editor2/trackMeterChanges.ts b/src_old/editor2/trackMeterChanges.ts deleted file mode 100644 index 4db3ec3..0000000 --- a/src_old/editor2/trackMeterChanges.ts +++ /dev/null @@ -1,236 +0,0 @@ -import Editor from "./editor" -import Track from "./track" -import TrackStateManager from "./trackStateManager" -import TrackMeterChangesState from "./trackMeterChangesState" -import Rect from "../util/rect" -import Range from "../util/range" -import Project from "../project/project2" -import Rational from "../util/rational" -import * as Theory from "../theory/theory" - - -export default class TrackMeterChanges -{ - static knobWidth = 16 - static knobHeight = 22 - - - static init(state: TrackStateManager) - { - Track.init(state) - state.mergeTrackState({ - type: "meterChanges", - }) - } - - - static hover(state: TrackStateManager) - { - const pos = state.contentState.mouse.trackPos - - const checkRange = Editor.timeRangeAtX( - state.contentStateManager, - pos.x - TrackMeterChanges.knobWidth, - pos.x + TrackMeterChanges.knobWidth) - - let hover = null - - for (const keyCh of TrackMeterChanges.iterAtRange(state, checkRange)) - { - const rect = TrackMeterChanges.knobRectForMeterChange(state, keyCh.time) - if (rect.contains(pos)) - { - hover = - { - id: keyCh.id, - range: Range.fromPoint(keyCh.time), - action: Editor.actionDragTime, - } - } - } - - state.mergeContentState({ - mouse: { ...state.contentState.mouse, - hover, - } - }) - } - - - static drawClear(state: TrackStateManager) - { - state.mergeTrackState({ draw: null }) - } - - - static drawHover(state: TrackStateManager) - { - const time = state.contentState.mouse.time - - state.mergeTrackState({ draw: { time } }) - } - - - static drawDrag(state: TrackStateManager) - { - const draw = state.trackState.draw - if (draw) - { - const time = state.contentState.mouse.time - state.mergeTrackState({ draw: { time } }) - } - } - - - static drawEnd(state: TrackStateManager) - { - const draw = state.trackState.draw - if (draw) - { - const meterCh = new Project.MeterChange( - state.trackState.trackId, - draw.time, - Editor.defaultMeter()) - - const id = state.appState.project.nextId - state.mergeAppState({ - project: Project.upsertTimedElement(state.appState.project, meterCh), - selection: state.appState.selection.add(id), - }) - } - } - - - static elemsAt(state: TrackStateManager, region: any): Project.ID[] - { - const elems = [] - - for (const meterCh of TrackMeterChanges.iterAtRange(state, region.range)) - elems.push(meterCh.id) - - return elems - } - - - static *iterAtRange(state: TrackStateManager, range: Range): Generator - { - const trackElems = state.appState.project.timedLists.get(state.trackState.trackId) - if (!trackElems) - return - - for (const keyCh of trackElems.iterAtRange(range)) - yield keyCh as Project.MeterChange - } - - - static knobRectForMeterChange(state: TrackStateManager, time: Rational) - { - return new Rect( - Editor.xAtTime(state.contentStateManager, time) - TrackMeterChanges.knobWidth / 2, - 0, - TrackMeterChanges.knobWidth, - TrackMeterChanges.knobHeight) - } - - - static render(state: TrackStateManager, ctx: CanvasRenderingContext2D) - { - const visibleRange = Editor.visibleTimeRange(state.contentStateManager) - - const firstMeter = Editor.meterAt(state.contentStateManager, state.trackState.trackId, visibleRange.start) - let shouldDrawFirstKey = true - - for (const meterCh of TrackMeterChanges.iterAtRange(state, visibleRange)) - { - if (shouldDrawFirstKey) - { - const x = Editor.xAtTime(state.contentStateManager, meterCh.time) - if (x > 80) - TrackMeterChanges.renderMeterChangeLabel(state, ctx, firstMeter) - } - - TrackMeterChanges.renderMeterChange(state, ctx, meterCh) - shouldDrawFirstKey = false - } - - if (shouldDrawFirstKey) - TrackMeterChanges.renderMeterChangeLabel(state, ctx, firstMeter) - - - const draw = state.trackState.draw - if (draw) - { - ctx.globalAlpha = 0.6 - TrackMeterChanges.renderMeterChangeKnob(state, ctx, draw.time) - ctx.globalAlpha = 1 - } - } - - - static renderMeterChangeKnob(state: TrackStateManager, ctx: CanvasRenderingContext2D, time: Rational) - { - const rect = TrackMeterChanges.knobRectForMeterChange(state, time) - - ctx.fillStyle = state.appState.prefs.editor.meterChangeColor - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2, 0, Math.PI * 2) - ctx.fill() - } - - - static renderMeterChangeLabel(state: TrackStateManager, ctx: CanvasRenderingContext2D, meter: Theory.Meter) - { - const rect = TrackMeterChanges.knobRectForMeterChange(state, new Rational(0)) - const x = 5 - - ctx.fillStyle = state.appState.prefs.editor.meterChangeColor - - ctx.font = "14px Verdana" - ctx.textAlign = "left" - ctx.textBaseline = "middle" - ctx.fillText(meter.numerator + " / " + meter.denominator, x, rect.y + rect.h / 2) - } - - - static renderMeterChange(state: TrackStateManager, ctx: CanvasRenderingContext2D, meterCh: Project.MeterChange) - { - const rect = TrackMeterChanges.knobRectForMeterChange(state, meterCh.time) - - ctx.fillStyle = state.appState.prefs.editor.meterChangeColor - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2, 0, Math.PI * 2) - ctx.fill() - - ctx.font = "14px Verdana" - ctx.textAlign = "left" - ctx.textBaseline = "middle" - ctx.fillText(meterCh.meter.numerator + " / " + meterCh.meter.denominator, rect.x + rect.w + 5, rect.y + rect.h / 2) - - if (state.appState.selection.has(meterCh.id)) - { - ctx.globalAlpha = 0.75 - ctx.fillStyle = "#fff" - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2 - 3, 0, Math.PI * 2) - ctx.fill() - - ctx.globalAlpha = 1 - } - - const hover = state.contentState.mouse.hover - if (hover && hover.id === meterCh.id) - { - ctx.globalAlpha = 0.5 - ctx.fillStyle = "#fff" - - ctx.beginPath() - ctx.arc(rect.x + rect.w / 2, rect.y + rect.h / 2, rect.w / 2, 0, Math.PI * 2) - ctx.fill() - - ctx.globalAlpha = 1 - } - } -} \ No newline at end of file diff --git a/src_old/editor2/trackMeterChangesState.ts b/src_old/editor2/trackMeterChangesState.ts deleted file mode 100644 index 3b2d4e0..0000000 --- a/src_old/editor2/trackMeterChangesState.ts +++ /dev/null @@ -1,11 +0,0 @@ -import TrackState from "./trackState" -import Rational from "../util/rational" - - -export default interface TrackMeterChangesState extends TrackState -{ - draw: null | - { - time: Rational - } -} \ No newline at end of file diff --git a/src_old/editor2/trackNotes.ts b/src_old/editor2/trackNotes.ts deleted file mode 100644 index e2a9698..0000000 --- a/src_old/editor2/trackNotes.ts +++ /dev/null @@ -1,391 +0,0 @@ -import Editor from "./editor" -import Track from "./track" -import TrackStateManager from "./trackStateManager" -import TrackNotesState from "./trackNotesState" -import Rect from "../util/rect" -import Range from "../util/range" -import Project from "../project/project2" -import Rational from "../util/rational" -import CanvasUtils from "../util/canvasUtils" -import * as Theory from "../theory/theory" - - -type StateManager = TrackStateManager - - -export default class TrackNotes -{ - static init(state: StateManager) - { - Track.init(state) - state.mergeTrackState({ - type: "notes", - rowScale: 10, - }) - } - - - static yScrollEnabled(state: StateManager): boolean - { - return true - } - - - static hover(state: StateManager) - { - const pos = state.contentState.mouse.trackPos - - const margin = 10 - const checkRange = Editor.timeRangeAtX(state.contentStateManager, pos.x - margin, pos.x + margin) - - let hoverPrimary = null - let hoverSecondary = null - - for (const [note, keyCh, xMin, xMax] of TrackNotes.iterNotesAndKeyChangesAtRange(state, checkRange)) - { - const margin = 2 - - const row = TrackNotes.rowForPitch(state, note.pitch, keyCh.key) - const rectExact = TrackNotes.rectForNote(state, note.range, row, xMin, xMax) - const rect = new Rect( - rectExact.x - margin, - rectExact.y, - rectExact.w + margin * 2, - rectExact.h) - - const dragMarginOut = Math.abs(Math.min(0, rect.w - 16)) - const stretchMarginOut = 16 - const stretchMarginIn = Math.max(0, Math.min(10, rect.w / 4)) - const rectDrag = new Rect(rect.x - dragMarginOut, rect.y, rect.w + dragMarginOut * 2, rect.h) - const rectStretchStart = new Rect(rect.x - stretchMarginOut, rect.y, stretchMarginOut + stretchMarginIn, rect.h) - const rectStretchEnd = new Rect(rect.x2 - stretchMarginIn, rect.y, stretchMarginOut + stretchMarginIn, rect.h) - - const exactHover = rect.contains(pos) - - let action = 0 - if (dragMarginOut > 0 && rectDrag.contains(pos)) - action = Editor.actionDragTime | Editor.actionDragPitchRow - else if (rectStretchStart.contains(pos) && !rectExact.cutStart) - action = Editor.actionStretchTimeStart - else if (rectStretchEnd.contains(pos) && !rectExact.cutEnd) - action = Editor.actionStretchTimeEnd - else if (exactHover) - action = Editor.actionDragTime | Editor.actionDragPitchRow - - const hoverNote = - { - id: note.id, - range: note.range, - action, - } - - if (action != 0) - { - if (exactHover) - hoverPrimary = hoverNote - else - hoverSecondary = hoverNote - } - - if (hoverPrimary) - break - } - - const hover = hoverPrimary || hoverSecondary - state.mergeContentState({ - mouse: { ...state.contentState.mouse, - hover, - } - }) - } - - - static drawClear(state: StateManager) - { - state.mergeTrackState({ draw: null }) - } - - - static drawHover(state: StateManager) - { - const time = state.contentState.mouse.time - const key = Editor.keyAt(state.contentStateManager, state.trackState.trackId, time) - const row = TrackNotes.rowAtY(state, state.contentState.mouse.trackPos.y) - const pitch = TrackNotes.pitchForRow(state, row, key) - - state.mergeTrackState({ - draw: - { - time1: time, - time2: time.add(state.contentState.timeSnap.multiply(new Rational(4))), - pitch, - } - }) - } - - - static drawDrag(state: StateManager) - { - const draw = state.trackState.draw - if (draw) - { - let time2 = state.contentState.mouse.time - - const time1X = Editor.xAtTime(state.contentStateManager, draw.time1) - const time2X = Editor.xAtTime(state.contentStateManager, time2) - if (Math.abs(time1X - time2X) < 5) - time2 = draw.time1.add(state.contentState.timeSnap.multiply(new Rational(4))) - - state.mergeTrackState({ - draw: { ...draw, - time2, - } - }) - } - } - - - static drawEnd(state: StateManager) - { - const draw = state.trackState.draw - if (draw) - { - const note = new Project.Note( - state.trackState.trackId, - new Range(draw.time1, draw.time2).sorted(), - draw.pitch) - - const id = state.appState.project.nextId - state.mergeAppState({ - project: Project.upsertRangedElement(state.appState.project, note), - selection: state.appState.selection.add(id), - }) - } - } - - - static elemsAt(state: StateManager, region: any): Project.ID[] - { - const elems = [] - - if (region.y1 === undefined) - { - for (const note of TrackNotes.iterNotesAtRange(state, region.range)) - elems.push(note.id) - } - else - { - for (const [note, keyCh, xMin, xMax] of TrackNotes.iterNotesAndKeyChangesAtRange(state, region.range)) - { - const row = TrackNotes.rowForPitch(state, note.pitch, keyCh.key) - const rect = TrackNotes.rectForNote(state, note.range, row, xMin, xMax) - - if (rect.y1 < region.y2 && rect.y2 > region.y1) - elems.push(note.id) - } - } - - return elems - } - - - static *iterNotesAtRange(state: StateManager, range: Range): Generator - { - const trackElems = state.appState.project.rangedLists.get(state.trackState.trackId) - if (!trackElems) - return - - for (const note of trackElems.iterAtRange(range)) - yield note as Project.Note - } - - - static *iterKeyChangePairsAtRange(state: StateManager, range: Range): Generator<[Project.KeyChange, Project.KeyChange, number, number], void, void> - { - const keyChangeTrackId = Project.keyChangeTrackForTrack(state.appState.project, state.trackState.trackId) - const keyChangeTrackTimedElems = state.appState.project.timedLists.get(keyChangeTrackId) - if (!keyChangeTrackTimedElems) - return - - const firstKeyCh = keyChangeTrackTimedElems.findFirst() as (Project.KeyChange | null) - const defaultKey = firstKeyCh ? firstKeyCh.key : Editor.defaultKey() - - for (const pair of keyChangeTrackTimedElems.iterActiveAtRangePairwise(range)) - { - const keyCh1 = pair[0] || new Project.KeyChange(-1, range.start, defaultKey) - const keyCh2 = pair[1] || new Project.KeyChange(-1, range.end, defaultKey) - - const keyCh1X = Editor.xAtTime(state.contentStateManager, keyCh1.time) - const keyCh2X = Editor.xAtTime(state.contentStateManager, keyCh2.time) - - yield [keyCh1 as Project.KeyChange, keyCh2 as Project.KeyChange, keyCh1X, keyCh2X] - } - } - - - static *iterNotesAndKeyChangesAtRange(state: StateManager, range: Range): Generator<[Project.Note, Project.KeyChange, number, number], void, void> - { - for (const [keyCh1, keyCh2, keyCh1X, keyCh2X] of TrackNotes.iterKeyChangePairsAtRange(state, range)) - { - const time1 = keyCh1.time.max(range.start)! - const time2 = keyCh2.time.min(range.end)! - - for (const note of TrackNotes.iterNotesAtRange(state, new Range(time1, time2))) - yield [note, keyCh1, keyCh1X, keyCh2X] - } - } - - - static yForRow(state: StateManager, row: number): number - { - return state.trackState.h / 2 - (row + 1) * state.trackState.rowScale - } - - - static rowAtY(state: StateManager, y: number): number - { - return -Math.floor((y - state.trackState.h / 2) / state.trackState.rowScale) - 1 - } - - - static rowForPitch(state: StateManager, pitch: number, key: Theory.Key): number - { - const tonicRowOffset = Theory.Utils.chromaToDegreeInCMajor(key.tonic.chroma) - return key.octavedDegreeForMidi(pitch - 60) + tonicRowOffset - } - - - static pitchForRow(state: StateManager, row: number, key: Theory.Key): number - { - const tonicRowOffset = Theory.Utils.chromaToDegreeInCMajor(key.tonic.chroma) - return key.midiForDegree(row - Math.floor(tonicRowOffset)) + 60 - } - - - static rectForNote(state: StateManager, range: Range, row: number, xStart: number, xEnd: number) - { - const noteOrigX1 = Editor.xAtTime(state.contentStateManager, range.start) - const noteOrigX2 = Editor.xAtTime(state.contentStateManager, range.end) - - const noteY = Math.floor(TrackNotes.yForRow(state, row)) - - let noteX1 = Math.max(noteOrigX1, xStart) - let noteX2 = Math.min(noteOrigX2, xEnd) - - const cutStart = noteOrigX1 < noteX1 - const cutEnd = noteOrigX2 > noteX2 - - //if (!cutStart) noteX1 += 1 - if (!cutEnd) noteX2 -= 1 - - noteX1 = Math.floor(noteX1) - noteX2 = Math.floor(noteX2) - - const noteW = Math.max(2, noteX2 - noteX1) - - return Object.assign( - new Rect(noteX1, noteY, noteW, state.trackState.rowScale), - { cutStart, cutEnd }) - } - - - static render(state: StateManager, ctx: CanvasRenderingContext2D) - { - const visibleRange = Editor.visibleTimeRange(state.contentStateManager) - - const rowAtTop = TrackNotes.rowAtY(state, state.trackState.yScroll) - const rowAtBottom = TrackNotes.rowAtY(state, state.trackState.yScroll + state.trackState.h) - - const octaveAtTop = Math.ceil(rowAtTop / 7) + 1 - const octaveAtBottom = Math.floor(rowAtBottom / 7) - 1 - - for (const [keyCh1, keyCh2, xMin, xMax] of TrackNotes.iterKeyChangePairsAtRange(state, visibleRange)) - { - const tonicRowOffset = Theory.Utils.chromaToDegreeInCMajor(keyCh1.key.tonic.chroma) - - for (let i = octaveAtBottom; i <= octaveAtTop; i++) - { - const y = 0.5 + Math.floor(TrackNotes.yForRow(state, tonicRowOffset + i * 7) + state.trackState.rowScale) - - ctx.strokeStyle = state.appState.prefs.editor.octaveDividerColor - ctx.beginPath() - ctx.moveTo(xMin, y) - ctx.lineTo(xMax, y) - ctx.stroke() - - for (let j = 1; j < 7; j += 1) - { - const ySuboctave = 0.5 + Math.floor(TrackNotes.yForRow(state, tonicRowOffset + i * 7 + j) + state.trackState.rowScale) - - ctx.strokeStyle = state.appState.prefs.editor.noteRowAlternateBkgColor - ctx.beginPath() - ctx.moveTo(xMin, ySuboctave) - ctx.lineTo(xMax, ySuboctave) - ctx.stroke() - } - - /*if (i == 0) - { - ctx.globalAlpha = 0.05 - ctx.fillStyle = "#fff" - ctx.fillRect(xMin, y - 7 * state.trackState.rowScale, xMax - xMin, 7 * state.trackState.rowScale) - ctx.globalAlpha = 1 - }*/ - } - } - - for (const [note, keyCh, xMin, xMax] of TrackNotes.iterNotesAndKeyChangesAtRange(state, visibleRange)) - { - const key = keyCh.key - const row = TrackNotes.rowForPitch(state, note.pitch, key) - const mode = key.scale.metadata!.mode - const fillStyle = CanvasUtils.fillStyleForDegree(ctx, key.degreeForMidi(note.pitch) + mode) - const hovering = !!state.contentState.mouse.hover && state.contentState.mouse.hover.id == note.id - const selected = state.appState.selection.contains(note.id) - const playing = state.appState.playback.playing && note.range.overlapsPoint(state.appState.playback.time) - TrackNotes.renderNote(state, ctx, note.range, row, xMin, xMax, fillStyle, hovering, selected, playing) - } - - const draw = state.trackState.draw - if (draw) - { - ctx.globalAlpha = 0.6 - - const key = Editor.keyAt(state.contentStateManager, state.trackState.trackId, draw.time1) - const row = TrackNotes.rowForPitch(state, draw.pitch, key) - const mode = key.scale.metadata!.mode - const fillStyle = CanvasUtils.fillStyleForDegree(ctx, key.degreeForMidi(draw.pitch) + mode) - TrackNotes.renderNote(state, ctx, new Range(draw.time1, draw.time2).sorted(), row, -Infinity, Infinity, fillStyle) - - ctx.globalAlpha = 1 - } - } - - - static renderNote(state: StateManager, ctx: CanvasRenderingContext2D, range: Range, row: number, xMin: number, xMax: number, fillStyle: any, hovering?: boolean, selected?: boolean, playing?: boolean) - { - const rect = TrackNotes.rectForNote(state, range, row, xMin, xMax) - - ctx.fillStyle = fillStyle - - ctx.beginPath() - ctx.fillRect(rect.x, rect.y, rect.w, rect.h) - - if (hovering) - { - ctx.globalAlpha = 0.4 - ctx.fillStyle = "#fff" - ctx.fillRect(rect.x, rect.y, rect.w, rect.h) - ctx.globalAlpha = 1 - } - - if (selected || playing) - { - const margin = 3 - ctx.globalAlpha = 0.6 - ctx.fillStyle = "#fff" - ctx.fillRect(rect.x, rect.y + margin, rect.w, rect.h - margin * 2) - ctx.globalAlpha = 1 - } - } -} \ No newline at end of file diff --git a/src_old/editor2/trackNotesPreview.ts b/src_old/editor2/trackNotesPreview.ts deleted file mode 100644 index a656402..0000000 --- a/src_old/editor2/trackNotesPreview.ts +++ /dev/null @@ -1,207 +0,0 @@ -import Editor from "./editor" -import Track from "./track" -import TrackStateManager from "./trackStateManager" -import TrackNotesPreviewState from "./TrackNotesPreviewState" -import Rect from "../util/rect" -import Range from "../util/range" -import Project from "../project/project2" -import Rational from "../util/rational" -import CanvasUtils from "../util/canvasUtils" -import * as Theory from "../theory/theory" - - -type StateManager = TrackStateManager - - -export default class TrackNotesPreview -{ - static init(state: StateManager) - { - Track.init(state) - state.mergeTrackState({ - type: "notesPreview", - rowScale: 2, - }) - } - - - static hover(state: StateManager) - { - state.mergeContentState({ - mouse: { ...state.contentState.mouse, - hover: null, - } - }) - } - - - static elemsAt(state: StateManager, region: any): Project.ID[] - { - const elems = [] - - if (region.y1 === undefined) - { - for (const note of TrackNotesPreview.iterNotesAtRange(state, region.range)) - elems.push(note.id) - } - else - { - for (const [note, keyCh, xMin, xMax] of TrackNotesPreview.iterNotesAndKeyChangesAtRange(state, region.range)) - { - const row = TrackNotesPreview.rowForPitch(state, note.pitch, keyCh.key) - const rect = TrackNotesPreview.rectForNote(state, note.range, row, xMin, xMax) - - if (rect.y1 < region.y2 && rect.y2 > region.y1) - elems.push(note.id) - } - } - - return elems - } - - - static *iterNotesAtRange(state: StateManager, range: Range): Generator - { - const trackElems = state.appState.project.rangedLists.get(state.trackState.trackId) - if (!trackElems) - return - - for (const note of trackElems.iterAtRange(range)) - yield note as Project.Note - } - - - static *iterKeyChangePairsAtRange(state: StateManager, range: Range): Generator<[Project.KeyChange, Project.KeyChange, number, number], void, void> - { - const keyChangeTrackId = Project.keyChangeTrackForTrack(state.appState.project, state.trackState.trackId) - const keyChangeTrackTimedElems = state.appState.project.timedLists.get(keyChangeTrackId) - if (!keyChangeTrackTimedElems) - return - - const firstKeyCh = keyChangeTrackTimedElems.findFirst() as (Project.KeyChange | null) - const defaultKey = firstKeyCh ? firstKeyCh.key : Editor.defaultKey() - - for (const pair of keyChangeTrackTimedElems.iterActiveAtRangePairwise(range)) - { - const keyCh1 = pair[0] || new Project.KeyChange(-1, range.start, defaultKey) - const keyCh2 = pair[1] || new Project.KeyChange(-1, range.end, defaultKey) - - const keyCh1X = Editor.xAtTime(state.contentStateManager, keyCh1.time) - const keyCh2X = Editor.xAtTime(state.contentStateManager, keyCh2.time) - - yield [keyCh1 as Project.KeyChange, keyCh2 as Project.KeyChange, keyCh1X, keyCh2X] - } - } - - - static *iterNotesAndKeyChangesAtRange(state: StateManager, range: Range): Generator<[Project.Note, Project.KeyChange, number, number], void, void> - { - for (const [keyCh1, keyCh2, keyCh1X, keyCh2X] of TrackNotesPreview.iterKeyChangePairsAtRange(state, range)) - { - const time1 = keyCh1.time.max(range.start)! - const time2 = keyCh2.time.min(range.end)! - - for (const note of TrackNotesPreview.iterNotesAtRange(state, new Range(time1, time2))) - yield [note, keyCh1, keyCh1X, keyCh2X] - } - } - - - static yForRow(state: StateManager, row: number): number - { - return state.trackState.h / 2 - (row + 1) * state.trackState.rowScale - } - - - static rowAtY(state: StateManager, y: number): number - { - return -Math.floor((y - state.trackState.h / 2) / state.trackState.rowScale) - 1 - } - - - static rowForPitch(state: StateManager, pitch: number, key: Theory.Key): number - { - const tonicRowOffset = Theory.Utils.chromaToDegreeInCMajor(key.tonic.chroma) - return key.octavedDegreeForMidi(pitch - 60) + tonicRowOffset - } - - - static pitchForRow(state: StateManager, row: number, key: Theory.Key): number - { - const tonicRowOffset = Theory.Utils.chromaToDegreeInCMajor(key.tonic.chroma) - return key.midiForDegree(row - Math.floor(tonicRowOffset)) + 60 - } - - - static rectForNote(state: StateManager, range: Range, row: number, xStart: number, xEnd: number) - { - const noteOrigX1 = Editor.xAtTime(state.contentStateManager, range.start) - const noteOrigX2 = Editor.xAtTime(state.contentStateManager, range.end) - - const noteY = Math.floor(TrackNotesPreview.yForRow(state, row)) - - let noteX1 = Math.max(noteOrigX1, xStart) - let noteX2 = Math.min(noteOrigX2, xEnd) - - const cutStart = noteOrigX1 < noteX1 - const cutEnd = noteOrigX2 > noteX2 - - //if (!cutStart) noteX1 += 1 - if (!cutEnd) noteX2 -= 1 - - noteX1 = Math.floor(noteX1) - noteX2 = Math.floor(noteX2) - - const noteW = Math.max(2, noteX2 - noteX1) - - return Object.assign( - new Rect(noteX1, noteY, noteW, state.trackState.rowScale), - { cutStart, cutEnd }) - } - - - static render(state: StateManager, ctx: CanvasRenderingContext2D) - { - const visibleRange = Editor.visibleTimeRange(state.contentStateManager) - - for (const [note, keyCh, xMin, xMax] of TrackNotesPreview.iterNotesAndKeyChangesAtRange(state, visibleRange)) - { - const key = keyCh.key - const row = TrackNotesPreview.rowForPitch(state, note.pitch, key) - const mode = key.scale.metadata!.mode - const fillStyle = CanvasUtils.fillStyleForDegree(ctx, key.degreeForMidi(note.pitch) + mode) - const hovering = !!state.contentState.mouse.hover && state.contentState.mouse.hover.id == note.id - const selected = state.appState.selection.contains(note.id) - const playing = state.appState.playback.playing && note.range.overlapsPoint(state.appState.playback.time) - TrackNotesPreview.renderNote(state, ctx, note.range, row, xMin, xMax, fillStyle, hovering, selected, playing) - } - } - - - static renderNote(state: StateManager, ctx: CanvasRenderingContext2D, range: Range, row: number, xMin: number, xMax: number, fillStyle: any, hovering?: boolean, selected?: boolean, playing?: boolean) - { - const rect = TrackNotesPreview.rectForNote(state, range, row, xMin, xMax) - - ctx.fillStyle = fillStyle - - ctx.beginPath() - ctx.fillRect(rect.x, rect.y, rect.w, rect.h) - - if (hovering) - { - ctx.globalAlpha = 0.4 - ctx.fillStyle = "#fff" - ctx.fillRect(rect.x, rect.y, rect.w, rect.h) - ctx.globalAlpha = 1 - } - - if (selected || playing) - { - const margin = 3 - ctx.globalAlpha = 0.6 - ctx.fillStyle = "#fff" - ctx.fillRect(rect.x, rect.y + margin, rect.w, rect.h - margin * 2) - ctx.globalAlpha = 1 - } - } -} \ No newline at end of file diff --git a/src_old/editor2/trackNotesPreviewState.ts b/src_old/editor2/trackNotesPreviewState.ts deleted file mode 100644 index 92d1b8b..0000000 --- a/src_old/editor2/trackNotesPreviewState.ts +++ /dev/null @@ -1,8 +0,0 @@ -import TrackState from "./trackState" -import Rational from "../util/rational" - - -export default interface TrackNotesPreviewState extends TrackState -{ - rowScale: number -} \ No newline at end of file diff --git a/src_old/editor2/trackNotesState.ts b/src_old/editor2/trackNotesState.ts deleted file mode 100644 index 936cc5e..0000000 --- a/src_old/editor2/trackNotesState.ts +++ /dev/null @@ -1,15 +0,0 @@ -import TrackState from "./trackState" -import Rational from "../util/rational" - - -export default interface TrackNotesState extends TrackState -{ - rowScale: number - - draw: null | - { - time1: Rational - time2: Rational - pitch: number - } -} \ No newline at end of file diff --git a/src_old/editor2/trackState.ts b/src_old/editor2/trackState.ts deleted file mode 100644 index f17792d..0000000 --- a/src_old/editor2/trackState.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default interface TrackState -{ - type: string, - trackIndex: number, - trackId: number, - - y: number, - h: number, - yScroll: number, - - pinned: boolean, -} \ No newline at end of file diff --git a/src_old/editor2/trackStateManager.ts b/src_old/editor2/trackStateManager.ts deleted file mode 100644 index 2d80a50..0000000 --- a/src_old/editor2/trackStateManager.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { AppState, ContentManager, AppReducer } from "../AppState" -import EditorState from "./editorState" -import TrackState from "./trackState" - - -export default class TrackStateManager -{ - contentStateManager: ContentManager - trackIndex: number - - - constructor(contentStateManager: ContentManager, trackIndex: number) - { - this.contentStateManager = contentStateManager - this.trackIndex = trackIndex - } - - - get appState(): AppState - { - return this.contentStateManager.appState - } - - - mergeAppState(newState: Partial) - { - this.contentStateManager.appState = AppReducer.merge(this.contentStateManager.appState, newState) - } - - - get contentState(): EditorState - { - return this.contentStateManager.contentState - } - - - set contentState(newState: EditorState) - { - this.contentStateManager.contentState = newState - } - - - mergeContentState(newState: Partial) - { - this.contentStateManager.mergeContentState(newState) - } - - - get trackState(): T - { - return this.contentStateManager.contentState.tracks[this.trackIndex] as any as T - } - - - set trackState(newState: T) - { - this.contentStateManager.mergeContentState({ - tracks: [ - ...this.contentStateManager.contentState.tracks.slice(0, this.trackIndex), - newState as any as TrackState, - ...this.contentStateManager.contentState.tracks.slice(this.trackIndex + 1), - ] - }) - } - - - mergeTrackState(newState: Partial) - { - this.trackState = { - ...this.trackState, - ...newState, - } - } -} \ No newline at end of file diff --git a/src/main.js b/src_old/main.js similarity index 100% rename from src/main.js rename to src_old/main.js diff --git a/src/menubar/Item.tsx b/src_old/menubar/Item.tsx similarity index 100% rename from src/menubar/Item.tsx rename to src_old/menubar/Item.tsx diff --git a/src/menubar/Root.tsx b/src_old/menubar/Root.tsx similarity index 100% rename from src/menubar/Root.tsx rename to src_old/menubar/Root.tsx diff --git a/src/menubar/index.ts b/src_old/menubar/index.ts similarity index 100% rename from src/menubar/index.ts rename to src_old/menubar/index.ts diff --git a/src_old/playback/PlaybackController.ts b/src_old/playback/PlaybackController.ts deleted file mode 100644 index cb0bd64..0000000 --- a/src_old/playback/PlaybackController.ts +++ /dev/null @@ -1,90 +0,0 @@ -import React from "react" -import { AppManager } from "../AppState" -import { Synth } from "./synth" -import SynthFeed from "./synthFeed" -import Rational from "../util/rational" - - -let synth: Synth | null = null - - -export function usePlaybackController(appManager: AppManager) -{ - const playData = React.useRef({ - playing: false, - timestamp: -1, - }) - - function processFrame(timestamp: number) - { - if (!appManager.appState.playback.playing && playData.current.playing) - { - requestAnimationFrame(() => - { - playData.current.playing = false - - if (synth) - synth.stopAll() - - appManager.mergeAppState({ - playback: { ...appManager.appState.playback, - playing: false, - } - }) - appManager.dispatch() - }) - return - } - - const prevTimestamp = playData.current.timestamp - const deltaTime = (prevTimestamp < 0 ? 0 : timestamp - prevTimestamp) - playData.current.timestamp = timestamp - - if (deltaTime > 0 && deltaTime < 250) - { - const measuresPerSecond = (appManager.appState.project.baseBpm / 4 / 60) - - synth!.process(deltaTime / 1000) - const timeAsFloat = appManager.appState.playback.timeAsFloat + deltaTime / 1000 * measuresPerSecond - appManager.mergeAppState({ - playback: { ...appManager.appState.playback, - timeAsFloat, - time: Rational.fromFloat(timeAsFloat, 64), - } - }) - appManager.dispatch() - } - - requestAnimationFrame(processFrame) - } - - if (appManager.appState.playback.playing && !playData.current.playing) - { - playData.current.playing = true - playData.current.timestamp = -1 - - requestAnimationFrame(() => - { - if (!synth) - synth = new Synth() - - synth.reset() - SynthFeed.feed(appManager.appState.project, synth, appManager.appState.playback.timeStart) - synth.play() - - appManager.mergeAppState({ - playback: { ...appManager.appState.playback, - time: appManager.appState.playback.timeStart, - timeAsFloat: appManager.appState.playback.timeStart.asFloat(), - } - }) - appManager.dispatch() - - requestAnimationFrame(async () => - { - await synth!.prepare(appManager.appState.sflib, appManager.appState.project) - requestAnimationFrame(processFrame) - }) - }) - } -} \ No newline at end of file diff --git a/src/playback/_global.ts b/src_old/playback/_global.ts similarity index 100% rename from src/playback/_global.ts rename to src_old/playback/_global.ts diff --git a/src_old/playback/index.ts b/src_old/playback/index.ts new file mode 100644 index 0000000..81bccd9 --- /dev/null +++ b/src_old/playback/index.ts @@ -0,0 +1,7 @@ +export * from "./_global" +export * from "./synthManager" +export * from "./instrument" +export * from "./instrumentBasic" +export * from "./instrumentSflib" +export * from "./projectFeed" +export * from "./sflib" \ No newline at end of file diff --git a/src_old/playback/instrument.ts b/src_old/playback/instrument.ts new file mode 100644 index 0000000..bbbe196 --- /dev/null +++ b/src_old/playback/instrument.ts @@ -0,0 +1,73 @@ +import * as Playback from "./index" +import * as Project from "../project" + + +export interface NoteRequest +{ + trackId: Project.ID + noteId: Project.ID + + startMs: number + durationMs: number + + midiPitchSeq: TimeVariableProperty[] + volumeSeq: TimeVariableProperty[] + velocitySeq: TimeVariableProperty[] +} + + +export interface TimeVariableProperty +{ + timeMs: number + value: number +} + + +export class Instrument +{ + synth: Playback.SynthManager + outputNode: AudioNode + + + constructor(synth: Playback.SynthManager, outputNode: AudioNode) + { + this.synth = synth + this.outputNode = outputNode + } + + + async prepare() + { + + } + + + async destroy() + { + + } + + + isFinished() + { + return true + } + + + playNote(request: Playback.NoteRequest) + { + + } + + + stopAll() + { + + } + + + process(deltaTimeMs: number) + { + + } +} \ No newline at end of file diff --git a/src_old/playback/instrumentBasic.ts b/src_old/playback/instrumentBasic.ts new file mode 100644 index 0000000..6ee9149 --- /dev/null +++ b/src_old/playback/instrumentBasic.ts @@ -0,0 +1,260 @@ +import * as Playback from "./index" +import * as Project from "../project" +import * as MathUtils from "../util/mathUtils" + + +interface Sample +{ + frequency: number + buffer: AudioBuffer | null +} + + +interface Note +{ + released: boolean + releasedTimerMs: number + voices: Voice[] +} + + +interface Voice +{ + nodeSrc: AudioBufferSourceNode + nodeEnvelope: GainNode + nodeVolume: GainNode +} + + +const releaseDurationMs = 100 + + +export class InstrumentBasic extends Playback.Instrument +{ + samples: Sample[] + notes: Map + + + constructor(synth: Playback.SynthManager, outputNode: AudioNode) + { + super(synth, outputNode) + this.samples = [] + this.notes = new Map() + } + + + async prepare() + { + if (this.samples.length > 0) + return + + const sampleDefs: [number, string][] = + [ + [ 55.0, "audio/piano/a1.mp3"], + [ 110.0, "audio/piano/a2.mp3"], + [ 220.0, "audio/piano/a3.mp3"], + [ 440.0, "audio/piano/a4.mp3"], + [ 880.0, "audio/piano/a5.mp3"], + [1760.0, "audio/piano/a6.mp3"], + //[3520.0, "audio/piano/a7.mp3"] + ] + + this.samples = [] + this.samples = await Promise.all(sampleDefs.map(s => this.load(s[0], s[1]))) + } + + + async load(frequency: number, url: string): Promise + { + const sample: Sample = + { + buffer: null, + frequency + } + + return new Promise((resolve, reject) => + { + fetch(url) + .then(res => res.arrayBuffer()) + .then(arrayBuf => + { + this.synth.audioCtx.decodeAudioData( + arrayBuf, + (audioBuf: AudioBuffer) => + { + sample.buffer = audioBuf + resolve(sample) + }, + () => + { + console.log("Error loading audio <" + url + ">") + reject() + }) + }) + }) + } + + + isFinished() + { + return this.notes.size == 0 + } + + + playNote(request: Playback.NoteRequest) + { + /*if (this.notes.has(request.noteId)) + return + + // Find the primary sample whose frequency is nearest to the desired frequency. + let sample1 = 0 + let sample1Dist = Math.abs(this.samples[0].frequency - desiredFreq) + for (let i = 1; i < this.samples.length; i++) + { + const dist = Math.abs(this.samples[i].frequency - desiredFreq) + if (dist < sample1Dist) + { + sample1 = i + sample1Dist = dist + } + } + + // Find the sample whose frequency is second nearest to the desired frequency, + // unless the primary sample is already exactly a match. + // Then, calculate a cross-fade between the two. + // TODO: Probably should use an equal-power (logarithmic) cross-fade, + // but this seems good enough for now. + let sample2 = null + + let volume1 = 1 + let volume2 = 0 + + if (this.samples[sample1].frequency < desiredFreq) + { + if (sample1 + 1 < this.samples.length) + { + sample2 = sample1 + 1 + + const t = + (desiredFreq - this.samples[sample1].frequency) / + (this.samples[sample2].frequency - this.samples[sample1].frequency) + + volume1 = 1 - t + volume2 = t + } + } + else if (this.samples[sample1].frequency > desiredFreq) + { + if (sample1 - 1 >= 0) + { + sample2 = sample1 - 1 + + const t = + (desiredFreq - this.samples[sample2].frequency) / + (this.samples[sample1].frequency - this.samples[sample2].frequency) + + volume1 = t + volume2 = 1 - t + } + } + + // Play the samples. + const voices = [ this.playVoice(this.samples[sample1], desiredFreq, desiredVolume * volume1) ] + if (sample2 != null) + voices.push(this.playVoice(this.samples[sample2], desiredFreq, desiredVolume * volume2)) + + const note: Note = + { + released: false, + releasedTimerMs: 0, + voices, + } + + this.notes.set(noteId, note)*/ + } + + + playVoice(sample: Sample, midiPitch: number, volume: number): Voice + { + let sourceNode = this.synth.audioCtx.createBufferSource() + sourceNode.playbackRate.value = MathUtils.midiToHertz(midiPitch) / sample.frequency + sourceNode.buffer = sample.buffer + + let envelopeNode = this.synth.audioCtx.createGain() + envelopeNode.gain.value = 1 + + let volumeNode = this.synth.audioCtx.createGain() + volumeNode.gain.value = volume + + sourceNode.connect(envelopeNode) + envelopeNode.connect(volumeNode) + volumeNode.connect(this.outputNode) + + sourceNode.start(0) + + return { + nodeSrc: sourceNode, + nodeEnvelope: envelopeNode, + nodeVolume: volumeNode, + } + } + + + releaseNote(noteId: Project.ID) + { + const note = this.notes.get(noteId) + if (!note) + return + + if (!note.released) + { + note.released = true + for (let i = 0; i < note.voices.length; i++) + { + note.voices[i].nodeEnvelope.gain.setValueAtTime( + 1, this.synth.audioCtx.currentTime) + + note.voices[i].nodeEnvelope.gain.linearRampToValueAtTime( + 0, this.synth.audioCtx.currentTime + releaseDurationMs / 1000) + } + } + } + + + stopNote(noteId: Project.ID) + { + const note = this.notes.get(noteId) + if (!note) + return + + note.released = true + note.releasedTimerMs = releaseDurationMs * 100 + + for (let i = 0; i < note.voices.length; i++) + note.voices[i].nodeSrc.stop() + + this.notes.delete(noteId) + } + + + stopAll() + { + for (const noteId of [...this.notes.keys()]) + this.stopNote(noteId) + } + + + process(deltaTimeMs: number) + { + for (const [noteId, note] of [...this.notes.entries()]) + { + if (note.released) + { + note.releasedTimerMs += deltaTimeMs + + if (note.releasedTimerMs > releaseDurationMs * 2) + this.stopNote(noteId) + } + } + } +} \ No newline at end of file diff --git a/src_old/playback/instrumentSflib.ts b/src_old/playback/instrumentSflib.ts index 57d59f3..758f64a 100644 --- a/src_old/playback/instrumentSflib.ts +++ b/src_old/playback/instrumentSflib.ts @@ -1,60 +1,256 @@ -import { Synth } from "./synth" -import Project from "../project/project2" -import { SflibInstrument, SflibMeta } from "./library" -import { sflibGetInstrument } from "./libraryCache" +import * as Playback from "./index" +import * as Project from "../project" +import * as MathUtils from "../util/mathUtils" -export class InstrumentSflib +const notesToDelete = new Set() + + +interface Note { - synth: Synth - trackId: number - audioWorklet?: AudioWorkletNode + request: Playback.NoteRequest + zone: Playback.SflibInstrumentZone + timePassedMs: number + endTimeMs: number + nodeSrc: AudioBufferSourceNode + nodeEnvelope: GainNode + nodeVolume: GainNode +} - constructor(synth: Synth, trackId: number) +export class InstrumentSflib extends Playback.Instrument +{ + collectionId: string + instrumentId: string + sflibInstrument: Playback.SflibInstrument | null + notes: Note[] + + + constructor( + synth: Playback.SynthManager, + outputNode: AudioNode, + collectionId: string, + instrumentId: string) { - this.synth = synth - this.trackId = trackId + super(synth, outputNode) + this.collectionId = collectionId + this.instrumentId = instrumentId + this.sflibInstrument = null + this.notes = [] } - async prepare(sflibMeta: SflibMeta, trackInstrument: Project.TrackInstrumentSflib) + async prepare() { - const instrument = await sflibGetInstrument(sflibMeta, trackInstrument.collectionId, trackInstrument.instrumentId) - if (!instrument) - throw "missing sflib instrument" - - this.audioWorklet = new AudioWorkletNode(this.synth.audioCtx, "SflibAudioProcessor") - this.audioWorklet.connect(this.synth.audioCtxOutput) - this.audioWorklet.port.postMessage({ - type: "setInstrument", - outputSampleRate: this.synth.audioCtx.sampleRate, - instrument - }) - } + this.sflibInstrument = await Playback.sflibGetInstrument( + this.collectionId, this.instrumentId, this.synth.audioCtx) - - destroy() - { - this.audioWorklet!.disconnect() - this.audioWorklet = undefined + if (!this.sflibInstrument) + { + console.error("could not load sflib " + this.collectionId + "/" + this.instrumentId) + return + } } - noteOn(midiPitch: number) - { - this.audioWorklet!.port.postMessage({ type: "noteOn", midiPitch }) - } - - - noteOff(midiPitch: number) - { - this.audioWorklet!.port.postMessage({ type: "noteOff", midiPitch }) + async destroy() + { + + } + + + isFinished() + { + return !this.sflibInstrument || this.notes.length == 0 + } + + + playNote(request: Playback.NoteRequest) + { + if (!this.sflibInstrument) + return + + //if (this.notes.has(request.noteId)) + // return + + const midiPitch = request.midiPitchSeq[0].value + + let zone = this.sflibInstrument.zones.find(z => + midiPitch >= z.midiPitchMin && + midiPitch <= z.midiPitchMax) + + if (!zone) + { + let nearestMidiPitchDistance = Infinity + + for (const z of this.sflibInstrument.zones) + { + const distance = Math.min( + Math.abs(z.midiPitchMin - midiPitch), + Math.abs(z.midiPitchMax - midiPitch)) + + if (distance < nearestMidiPitchDistance) + { + nearestMidiPitchDistance = distance + zone = z + } + } + } + + if (!zone) + return + + const freq = MathUtils.midiToHertz(midiPitch) + + const audioBuffer = this.sflibInstrument.audioBuffers[zone.sampleIndex] + + + const sourceNode = this.synth.audioCtx.createBufferSource() + sourceNode.playbackRate.value = freq / MathUtils.midiToHertz(zone.midiPitchBase) + sourceNode.buffer = audioBuffer + sourceNode.loop = (zone.loopMode == "loop") + sourceNode.loopStart = zone.loopStartIndex / audioBuffer.sampleRate + sourceNode.loopEnd = zone.loopEndIndex / audioBuffer.sampleRate + + + const time = 0.05 + Math.max(0, request.startMs / 1000) + const delayTime = time + zone.volEnvDelaySec + const attackTime = delayTime + (zone.volEnvAttackSec || 0.005) + const holdTime = attackTime + zone.volEnvHoldSec + const decayTime = holdTime + zone.volEnvDecaySec + const sustainLevel = zone.volEnvSustain || 0.0001 + const releaseTime = time + request.durationMs / 1000 + const endTime = releaseTime + Math.max(zone.volEnvReleaseSec, 0.05) + + /* + + Envelope parameters: + + | 100% level + | ______ + | /| |\ + | / | | \ sustainLevel + | / | | \____________ + | / | | | |\ + | / | | | | \ + | / | | | | \ + --+------------------------------------------- + ^ ^ ^ ^ ^ ^ ^ endTime + | | | | | + releaseTime + | | | | + decayTime + | | | + holdTime + | | + attackTime + | + delayTime + + time + + */ + + const releaseTimeCap = releaseTime - 0.001 + const cappedDelayTime = Math.min(delayTime, releaseTimeCap) + const cappedAttackTime = Math.min(attackTime, releaseTimeCap) + const cappedHoldTime = Math.min(holdTime, releaseTimeCap) + + const releaseF = (releaseTime - cappedHoldTime) / (decayTime - cappedHoldTime) + const releaseLevel = releaseF >= 1 ? sustainLevel : Math.pow(sustainLevel, releaseF) + + const envelopeNode = this.synth.audioCtx.createGain() + envelopeNode.gain.value = 0 + envelopeNode.gain.setValueAtTime(0.0001, cappedDelayTime) + envelopeNode.gain.exponentialRampToValueAtTime(1, cappedAttackTime) + envelopeNode.gain.setValueAtTime(1, cappedHoldTime) + + if (decayTime < releaseTime) + { + envelopeNode.gain.exponentialRampToValueAtTime(sustainLevel, decayTime) + envelopeNode.gain.setValueAtTime(sustainLevel, releaseTime) + envelopeNode.gain.exponentialRampToValueAtTime(0.0001, endTime) + } + else + { + envelopeNode.gain.exponentialRampToValueAtTime(releaseLevel, releaseTime) + envelopeNode.gain.setValueAtTime(releaseLevel, releaseTime) + envelopeNode.gain.exponentialRampToValueAtTime(0.0001, endTime) + } + + /*console.log("playNote", this.synth.audioCtx.currentTime, request) + console.log("envelope", + "del", (delayTime - time).toFixed(3), + "atk", (attackTime - time).toFixed(3), + "hld", (holdTime - time).toFixed(3), + "dec", (decayTime - time).toFixed(3), + "::", + sustainLevel.toFixed(3), + releaseLevel.toFixed(3), + "::", + "rel", (releaseTimeCap - time).toFixed(3), + "end", (endTime - time).toFixed(3))*/ + + const volumeNode = this.synth.audioCtx.createGain() + volumeNode.gain.value = MathUtils.dbToLinearGain(request.volumeSeq[0].value) + + + sourceNode.connect(envelopeNode) + envelopeNode.connect(volumeNode) + volumeNode.connect(this.outputNode) + + sourceNode.start(time) + + const note: Note = + { + request, + zone, + timePassedMs: -(time - this.synth.audioCtx.currentTime) * 1000, + endTimeMs: (endTime - time) * 1000, + nodeSrc: sourceNode, + nodeEnvelope: envelopeNode, + nodeVolume: volumeNode, + } + + this.notes.push(note) } - stop() + stopNote(note: Note) + { + note.nodeSrc.stop() + note.nodeSrc.disconnect() + } + + + stopAll() { - this.audioWorklet!.port.postMessage({ type: "stopAll" }) + for (const note of this.notes) + this.stopNote(note) + + this.notes = [] + } + + + process(deltaTimeMs: number) + { + notesToDelete.clear() + + for (const note of this.notes) + { + note.timePassedMs += deltaTimeMs + + if (note.timePassedMs > note.endTimeMs) + { + this.stopNote(note) + notesToDelete.add(note) + } + else if (note.zone.loopMode != "loop") + { + const sample = this.sflibInstrument!.audioBuffers[note.zone.sampleIndex] + if (note.timePassedMs > sample.length / sample.sampleRate * 1000) + { + this.stopNote(note) + notesToDelete.add(note) + } + } + } + + this.notes = this.notes.filter(n => !notesToDelete.has(n)) + notesToDelete.clear() } } \ No newline at end of file diff --git a/src_old/playback/library.ts b/src_old/playback/library.ts deleted file mode 100644 index b66f65e..0000000 --- a/src_old/playback/library.ts +++ /dev/null @@ -1,91 +0,0 @@ -import React from "react" -import { AppManager } from "../AppState" - - -export const sflibUrl = "/library/" - - -export interface SflibMeta -{ - ready: boolean - collections: SflibCollectionMeta[] -} - - -export interface SflibCollectionMeta -{ - id: string - name: string - instruments: SflibInstrumentMeta[] -} - - -export interface SflibInstrumentMeta -{ - id: string - filename: string - filesize: number - name: string - midiBank: number - midiPreset: number -} - - -export interface SflibInstrument -{ - id: string - name: string - midiPreset: number - midiBank: number - zones: SflibInstrumentZone[] - samples: string[] -} - - -export interface SflibInstrumentZone -{ - sampleIndex: number - sampleRate: number - startLoop: number - endLoop: number - loopMode: string - - minPitch: number - maxPitch: number - basePitch: number - minVel: number - maxVel: number - - delayVolEnv?: number - attackVolEnv?: number - holdVolEnv?: number - decayVolEnv?: number - sustainVolEnv?: number - releaseVolEnv?: number - exclusiveClass?: number -} - - -async function loadSflibMeta(): Promise -{ - const data = await fetch(sflibUrl + "library.json") - const meta: SflibMeta = await data.json() - meta.ready = true - console.log("loaded sflib meta", meta) - return meta -} - - -export function useSoundfontLibrary(appManager: AppManager) -{ - React.useEffect(() => - { - window.requestAnimationFrame(async () => - { - const sflib = await loadSflibMeta() - appManager.mergeAppState({ sflib }) - appManager.dispatch() - }) - - }, []) -} \ No newline at end of file diff --git a/src_old/playback/libraryCache.ts b/src_old/playback/libraryCache.ts deleted file mode 100644 index 661bc65..0000000 --- a/src_old/playback/libraryCache.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { sflibUrl, SflibMeta, SflibInstrument } from "./library" -import zlib from "zlib" - - -const sflibCache = new Map() - - -export async function sflibGetInstrument(meta: SflibMeta, collectionId: string, instrumentId: string): Promise -{ - const id = collectionId + "/" + instrumentId - const cached = sflibCache.get(id) - if (cached) - return cached - - const collMeta = meta.collections.find(c => c.id == collectionId) - if (!collMeta) - return null - - const instrMeta = collMeta.instruments.find(i => i.id == instrumentId) - if (!instrMeta) - return null - - const instrFilename = instrMeta.filename - - const data = await fetch(sflibUrl + collectionId + "/" + instrFilename) - const dataCompressed = await data.arrayBuffer() - console.log(dataCompressed) - const instr: SflibInstrument = JSON.parse(zlib.inflateSync(Buffer.from(dataCompressed)).toString("utf8")) - - console.log("loaded sflib instrument", instr) - - sflibCache.set(id, instr) - return instr -} \ No newline at end of file diff --git a/src/playback/projectFeed.ts b/src_old/playback/projectFeed.ts similarity index 100% rename from src/playback/projectFeed.ts rename to src_old/playback/projectFeed.ts diff --git a/src/playback/sflib.ts b/src_old/playback/sflib.ts similarity index 100% rename from src/playback/sflib.ts rename to src_old/playback/sflib.ts diff --git a/src_old/playback/sflibWorklet.ts b/src_old/playback/sflibWorklet.ts deleted file mode 100644 index 8a11180..0000000 --- a/src_old/playback/sflibWorklet.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { SflibInstrument, SflibInstrumentZone } from "./library" - - -interface AudioWorkletProcessor -{ - readonly port: MessagePort - process(inputs: Float32Array[][], outputs: Float32Array[][], parameters: Record): boolean -} - -declare var AudioWorkletProcessor: -{ - prototype: AudioWorkletProcessor - new (options?: AudioWorkletNodeOptions): AudioWorkletProcessor -} - -declare function registerProcessor( - name: string, - processorCtor: (new (options?: AudioWorkletNodeOptions) => AudioWorkletProcessor) & { parameterDescriptors?: AudioParamDescriptor[] } -): void - - -function lerp(a: number, b: number, t: number): number -{ - return a + (b - a) * t -} - - -function sampleEnvelopeStart(envelope: SflibInstrumentZone, time: number): number -{ - const delay = envelope.delayVolEnv || 0 - if (time < delay) - return 0 - - time -= delay - const attack = envelope.attackVolEnv || 0 - if (time < attack) - return time / attack - - time -= attack - const hold = envelope.holdVolEnv || 0 - if (time < hold) - return 1 - - time -= hold - const decay = envelope.decayVolEnv || 0 - const sustain = envelope.sustainVolEnv || 1 - if (time < decay) - return lerp(1, sustain, time / decay) - - return sustain -} - - -function sampleEnvelope(envelope: SflibInstrumentZone, time: number, isOff: boolean, offTime: number): number -{ - const envelopeStart = sampleEnvelopeStart(envelope, time) - - if (!isOff) - { - return envelopeStart - } - else - { - time -= offTime - const release = envelope.releaseVolEnv || 0 - if (time < release) - return lerp(envelopeStart, 0, time / release) - - return 0 - } -} - - -interface Instrument extends SflibInstrument -{ - sampleBuffers: number[][] -} - - -interface PlayingNote -{ - on: boolean - offTime: number - time: number - sampleHead: number - midiPitch: number - zone: SflibInstrumentZone -} - - -class SflibAudioProcessor extends AudioWorkletProcessor -{ - outputSampleRate: number - instrument: Instrument | null - notes: PlayingNote[] - - - constructor(options?: AudioWorkletNodeOptions) - { - super(options) - - this.outputSampleRate = 1 - this.instrument = null - this.notes = [] - - this.port.onmessage = (e) => - { - //console.log(e.data) - - if (e.data.type == "setInstrument") - { - this.outputSampleRate = e.data.outputSampleRate - - this.instrument = e.data.instrument - this.instrument!.sampleBuffers = [] - - for (const sampleRaw of this.instrument!.samples) - { - //console.log(sampleRaw) - const buffer = Buffer.from(sampleRaw, "base64") - //console.log(buffer) - - const array = new Array(buffer.length / 2) - for (let i = 0; i < buffer.length / 2; i++) - { - const b0 = buffer[i * 2] - const b1 = buffer[i * 2 + 1] - let uint16 = (b0 << 8) | b1 - if ((uint16 & 0x8000) != 0) - uint16 = -(0x10000 - uint16) - - array[i] = uint16 / 0x8000 - } - - //console.log(array) - this.instrument!.sampleBuffers.push(array) - } - } - - else if (e.data.type == "noteOn") - { - for (const oldNote of this.notes) - { - if (oldNote.on && oldNote.midiPitch == e.data.midiPitch) - { - oldNote.on = false - oldNote.offTime = oldNote.time - } - } - - const zone = this.instrument!.zones.find( - z => e.data.midiPitch >= z.minPitch && e.data.midiPitch <= z.maxPitch) - - if (zone) - { - const note: PlayingNote = - { - on: true, - offTime: 0, - time: 0, - sampleHead: 0, - midiPitch: e.data.midiPitch, - zone, - } - - this.notes.push(note) - } - } - - else if (e.data.type == "noteOff") - { - for (const note of this.notes) - { - if (note.on && note.midiPitch == e.data.midiPitch) - { - note.on = false - note.offTime = note.time - } - } - } - - else if (e.data.type == "stopAll") - { - for (const note of this.notes) - { - if (note.on) - { - note.on = false - note.offTime = note.time - } - } - } - - //delete this.instrument.samples - } - } - - - process(inputs: Float32Array[][], outputs: Float32Array[][], parameters: Record): boolean - { - for (let n = this.notes.length - 1; n >= 0; n--) - { - const note = this.notes[n] - const output = outputs[0] - const zone = note.zone - const sampleData = this.instrument!.sampleBuffers[zone.sampleIndex] - const rateConversion = zone.sampleRate / this.outputSampleRate - const pitchConversion = midiToHertz(note.midiPitch) / midiToHertz(zone.basePitch) - - const getSampleAt = (index: number): number => - { - const indexBefore = Math.floor(index) - const indexAfter = Math.ceil(index) - - const indexFraction = (index - indexBefore) - - const sampleBefore = sampleData[indexBefore % sampleData.length] - const sampleAfter = sampleData[indexAfter % sampleData.length] - - const sampleInterp = sampleBefore + (sampleAfter - sampleBefore) * indexFraction - //console.log(index, indexFraction, sampleBefore, sampleAfter, sampleInterp) - - return sampleInterp - } - - for (let i = 0; i < output[0].length; i++) - { - const envelope = sampleEnvelope(zone, note.time, !note.on, note.offTime) - const sample = Math.pow(envelope, 4) * getSampleAt(note.sampleHead * rateConversion * pitchConversion) - - for (let c = 0; c < output.length; c++) - { - output[c][i] += sample * 0.25 - } - - note.time += 1 / this.outputSampleRate - - if (zone.loopMode == "noLoop") - { - if (note.sampleHead * rateConversion * pitchConversion < sampleData.length - 1) - note.sampleHead += 1 - } - else - { - note.sampleHead += 1 - if (note.sampleHead * rateConversion * pitchConversion >= zone.endLoop - 1) - note.sampleHead -= (zone.endLoop - zone.startLoop - 1) / rateConversion / pitchConversion - } - } - - if (!note.on && note.time - note.offTime > (zone.releaseVolEnv || 0)) - this.notes = [...this.notes.slice(0, n), ...this.notes.slice(n + 1)] - } - - return true - } -} - - -function midiToHertz(midi: number): number -{ - return Math.pow(2, (midi - 69) / 12) * 440 -} - - -registerProcessor("SflibAudioProcessor", SflibAudioProcessor) -console.log("registered SflibAudioProcessor") \ No newline at end of file diff --git a/src_old/playback/synth.ts b/src_old/playback/synth.ts deleted file mode 100644 index 63b010c..0000000 --- a/src_old/playback/synth.ts +++ /dev/null @@ -1,168 +0,0 @@ -import Project from "../project/project2" -import { sflibGetInstrument } from "./libraryCache" -import { AppState } from "../AppState" -import { SflibMeta } from "./library" -import { InstrumentSflib } from "./instrumentSflib" - - -interface NoteEvent -{ - trackId: number - time: number - midiPitch: number - volume: number - duration: number -} - - -interface PlayingNote -{ - event: NoteEvent - remainingDuration: number -} - - -export class Synth -{ - audioCtx: AudioContext - audioCtxOutput: GainNode - audioTracks: InstrumentSflib[] - - cachedTracks: Project.Track[] = [] - - time: number - noteEvents: NoteEvent[] - playingNotes: PlayingNote[] - - - constructor() - { - this.audioCtx = new AudioContext() - this.audioTracks = [] - - this.audioCtxOutput = this.audioCtx.createGain() - this.audioCtxOutput.connect(this.audioCtx.destination) - this.audioCtxOutput.gain.value = 0.25 - - this.time = 0 - this.noteEvents = [] - this.playingNotes = [] - } - - - destroy() - { - this.audioCtx.close() - } - - - reset() - { - } - - - async prepare(sflibMeta: SflibMeta, project: Project) - { - await this.audioCtx.audioWorklet.addModule("/build/sflibWorklet.js") - - if (this.cachedTracks !== project.tracks) - { - for (const audioTrack of this.audioTracks) - audioTrack.destroy() - - this.audioTracks = [] - - for (const track of project.tracks) - { - if (track.trackType == Project.TrackType.Notes) - { - const trackNotes = track - if (trackNotes.instrument.instrumentType == Project.TrackInstrumentType.Sflib) - { - const audioTrack = new InstrumentSflib(this, track.id) - await audioTrack.prepare(sflibMeta, trackNotes.instrument) - this.audioTracks.push(audioTrack) - } - } - } - } - - this.cachedTracks = project.tracks - } - - - isFinished() - { - return this.noteEvents.length == 0 && this.playingNotes.length == 0 - } - - - play() - { - this.noteEvents.sort(function (a, b) { return a.time - b.time }) - } - - - stopAll() - { - this.time = 0 - this.noteEvents = [] - this.playingNotes = [] - - for (const audioTrack of this.audioTracks) - audioTrack.stop() - } - - - process(deltaTime: number) - { - this.time += deltaTime - - // Update audio output. - for (let i = this.playingNotes.length - 1; i >= 0; i--) - { - const note = this.playingNotes[i] - note.remainingDuration -= deltaTime - if (note.remainingDuration <= 0) - { - this.audioTracks.find(t => t.trackId == note.event.trackId)!.noteOff(note.event.midiPitch) - this.playingNotes.splice(i, 1) - } - } - - // Process pending note events up to the current time. - let noteEventsProcessed = 0 - while (noteEventsProcessed < this.noteEvents.length && - this.noteEvents[noteEventsProcessed].time <= this.time) - { - const ev = this.noteEvents[noteEventsProcessed] - noteEventsProcessed++ - - this.audioTracks.find(t => t.trackId == ev.trackId)!.noteOn(ev.midiPitch) - - const playingNote = - { - event: ev, - remainingDuration: ev.duration, - } - - this.playingNotes.push(playingNote) - } - - // Remove processed events. - this.noteEvents.splice(0, noteEventsProcessed) - } - - - addNoteEvent(trackId: number, time: number, midiPitch: number, volume: number, duration: number) - { - this.noteEvents.push( - { - trackId, - time: time + this.time, - midiPitch, - volume, - duration - }) - } -} \ No newline at end of file diff --git a/src_old/playback/synthFeed.ts b/src_old/playback/synthFeed.ts deleted file mode 100644 index 5029441..0000000 --- a/src_old/playback/synthFeed.ts +++ /dev/null @@ -1,56 +0,0 @@ -import Rational from "../util/rational" -import Project from "../project/project2" -import { Synth } from "./synth" -import MathUtils from "../util/mathUtils" - - -export default class SynthFeed -{ - static feed(project: Project, synth: Synth, startTick: Rational, useChordPatterns = true) - { - const chordVolumeMul = 0.5 - - const addNoteEvent = (trackId: number, tickStart: Rational, duration: Rational, midiPitch: number, volume: number) => - { - const offsetStart = tickStart.subtract(startTick) - - const measuresPerSecond = project.baseBpm / 4 / 60 - - let timeStart = offsetStart.asFloat() / measuresPerSecond - let timeDuration = duration.asFloat() / measuresPerSecond - - if (timeStart <= 0) - { - timeDuration += timeStart - timeStart = 0 - } - - if (timeDuration <= 0) - return - - //console.log("event note [" + pitch + "] at tick " + tickStart.toString() + " = " + timeStart + " s") - synth.addNoteEvent(trackId, timeStart, midiPitch, volume, timeDuration) - } - - // Register notes. - for (const track of project.tracks) - { - const rangedList = project.rangedLists.get(track.id) - if (!rangedList) - continue - - for (const elem of rangedList.iterAll()) - { - if (elem.type != Project.ElementType.Note) - continue - - const note = elem - - if (note.range.end.compare(startTick) <= 0) - continue - - addNoteEvent(track.id, note.range.start, note.range.duration, note.pitch, 1) - } - } - } -} diff --git a/src/playback/synthManager.ts b/src_old/playback/synthManager.ts similarity index 100% rename from src/playback/synthManager.ts rename to src_old/playback/synthManager.ts diff --git a/src/popup/Button.tsx b/src_old/popup/Button.tsx similarity index 100% rename from src/popup/Button.tsx rename to src_old/popup/Button.tsx diff --git a/src/popup/Divider.tsx b/src_old/popup/Divider.tsx similarity index 100% rename from src/popup/Divider.tsx rename to src_old/popup/Divider.tsx diff --git a/src_old/popup/MenuBar.tsx b/src_old/popup/MenuBar.tsx deleted file mode 100644 index a13b36a..0000000 --- a/src_old/popup/MenuBar.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React from "react" -import Rect from "../util/rect" -import { useAppManager } from "../AppContext" -import Project from "../project/project2" -import { AppReducer } from "../AppState" -import MenuFilePopup from "./MenuFilePopup" -import PlaybackToolbox from "./PlaybackToolbox" - - -interface MenuBarProps -{ -} - - -interface MenuBarItemProps -{ - label: string - popupElem: any -} - - -function MenuBarItem(props: MenuBarItemProps) -{ - const appManager = useAppManager() - - const refButton = React.useRef(null) - const onClick = () => - { - const domRect = refButton.current!.getBoundingClientRect() - const rect = new Rect(domRect.x, domRect.y, domRect.width, domRect.height) - appManager.appState = AppReducer.createPopup( - appManager.appState, - rect, - props.popupElem, - {}) - appManager.dispatch() - } - - const isOpen = - appManager.appState.popup != null && - appManager.appState.popup.elem == props.popupElem - - return -} - - -export default function MenuBar(props: MenuBarProps) -{ - return
- - - - - -
-} \ No newline at end of file diff --git a/src_old/popup/MenuFilePopup.tsx b/src_old/popup/MenuFilePopup.tsx deleted file mode 100644 index ed4d191..0000000 --- a/src_old/popup/MenuFilePopup.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from "react" -import PopupButton from "../popup/PopupButton" -import Rect from "../util/rect" -import { useAppManager } from "../AppContext" -import Project from "../project/project2" -import IoMidi from "../project/ioMidi" - - -interface MenuFilePopupProps -{ -} - - -export default function MenuFilePopup(props: MenuFilePopupProps) -{ - const appManager = useAppManager() - - const doNew = () => - { - appManager.mergeAppState({ project: Project.getDefault() }) - appManager.dispatch() - } - - const doOpen = () => - { - document.getElementById("gInputFileOpen")!.click() - } - - React.useEffect(() => - { - const handleOpenFile = (ev: Event) => - { - const elem = ev.target as HTMLInputElement - - if (elem.files!.length != 1) - return - - let reader = new FileReader() - reader.readAsArrayBuffer(elem.files![0]) - reader.onload = () => - { - const bytes = new Uint8Array(reader.result as any) - - const project = IoMidi.read(bytes) - appManager.mergeAppState({ project }) - appManager.dispatch() - } - } - - const input = document.getElementById("gInputFileOpen") as HTMLInputElement - input!.addEventListener("change", handleOpenFile) - - return () => - { - //input!.removeEventListener("change", handleOpenFile) - } - - }, []) - - return <> - - - - - - - - - -} \ No newline at end of file diff --git a/src_old/popup/PlaybackToolbox.tsx b/src_old/popup/PlaybackToolbox.tsx deleted file mode 100644 index 65b3cb9..0000000 --- a/src_old/popup/PlaybackToolbox.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React from "react" -import { useAppManager } from "../AppContext" - - -export default function PlaybackToolbox(props: {}) -{ - const appManager = useAppManager() - const togglePlayback = () => - { - appManager.mergeAppState({ playback: { ...appManager.appState.playback, - playing: !appManager.appState.playback.playing, - }}) - appManager.dispatch() - } - - - React.useEffect(() => - { - const handleSpacebarKey = (ev: KeyboardEvent) => - { - if (ev.key == " ") - { - ev.preventDefault() - ev.stopPropagation() - togglePlayback() - } - } - - window.addEventListener("keydown", handleSpacebarKey) - - return () => - { - window.removeEventListener("keydown", handleSpacebarKey) - } - - }, []) - - - return
- - - -
-} \ No newline at end of file diff --git a/src/popup/Root.tsx b/src_old/popup/Root.tsx similarity index 100% rename from src/popup/Root.tsx rename to src_old/popup/Root.tsx diff --git a/src/popup/_global.ts b/src_old/popup/_global.ts similarity index 100% rename from src/popup/_global.ts rename to src_old/popup/_global.ts diff --git a/src/popup/index.ts b/src_old/popup/index.ts similarity index 100% rename from src/popup/index.ts rename to src_old/popup/index.ts diff --git a/src/prefs/_global.ts b/src_old/prefs/_global.ts similarity index 100% rename from src/prefs/_global.ts rename to src_old/prefs/_global.ts diff --git a/src/prefs/index.ts b/src_old/prefs/index.ts similarity index 100% rename from src/prefs/index.ts rename to src_old/prefs/index.ts diff --git a/src/prefs/prefs.ts b/src_old/prefs/prefs.ts similarity index 100% rename from src/prefs/prefs.ts rename to src_old/prefs/prefs.ts diff --git a/src_old/project/ioCompressedStr.js b/src_old/project/ioCompressedStr.js deleted file mode 100644 index 67083b0..0000000 --- a/src_old/project/ioCompressedStr.js +++ /dev/null @@ -1,333 +0,0 @@ -import Project from "./project.js" -import Theory from "../theory.ts" -import { URLBinaryEncoder, URLBinaryDecoder } from "../util/urlBinaryEncoder.js" -import Rational from "../util/rational.js" -import Range from "../util/range.js" - - -export default class IOCompressedStr -{ - static read(str) - { - let song = new Project() - - if (!str.startsWith("3_") && - !str.startsWith("4_")) - throw "unsupported version" - - const r = new URLBinaryDecoder(str.substr(2)) - - // Read header. - const version = r.readInteger() - song.baseBpm = r.readInteger() - - if (version != 3 && version != 4) - throw "unsupported version" - - // Read note tracks. - const trackNum = r.readInteger() - for (let j = 0; j < trackNum; j++) - { - // Read note data as structure-of-arrays. - const noteNum = r.readInteger() - - let noteData = [] - - let prevNoteRangeStartInteger = 0 - for (let i = 0; i < noteNum; i++) - { - prevNoteRangeStartInteger += r.readInteger() // range.start.integer - noteData[i] = [ prevNoteRangeStartInteger ] - } - - for (let i = 0; i < noteNum; i++) - noteData[i][1] = r.readInteger() // range.start.numeratorWithoutInteger - - for (let i = 0; i < noteNum; i++) - noteData[i][2] = r.readInteger() // range.start.denominator - - for (let i = 0; i < noteNum; i++) - noteData[i][3] = r.readInteger() // range.duration.numerator - - for (let i = 0; i < noteNum; i++) - noteData[i][4] = r.readInteger() // range.duration.denominator - - for (let i = 0; i < noteNum; i++) - { - const start = Rational.fromIntegerPlusRational(noteData[i][0], noteData[i][1], noteData[i][2]) - const duration = new Rational(noteData[i][3], noteData[i][4]) - - song = song.upsertNote(new Project.Note( - Range.fromStartDuration(start, duration), - r.readInteger() + 60)) - } - } - - // Read chord data as structure-of-arrays. - const chordNum = r.readInteger() - - let chordData = [] - - let prevChordRangeStartInteger = 0 - for (let i = 0; i < chordNum; i++) - { - prevChordRangeStartInteger += r.readInteger() // range.start.integer - chordData[i] = [ prevChordRangeStartInteger ] - } - - for (let i = 0; i < chordNum; i++) - chordData[i][1] = r.readInteger() // range.start.numeratorWithoutInteger - - for (let i = 0; i < chordNum; i++) - chordData[i][2] = r.readInteger() // range.start.denominator - - for (let i = 0; i < chordNum; i++) - chordData[i][3] = r.readInteger() // range.duration.numerator - - for (let i = 0; i < chordNum; i++) - chordData[i][4] = r.readInteger() // range.duration.denominator - - for (let i = 0; i < chordNum; i++) - chordData[i][5] = r.readInteger() // chord.rootMidi - - for (let i = 0; i < chordNum; i++) - chordData[i][6] = r.readInteger() // chord.rootAccidental - - for (let i = 0; i < chordNum; i++) - chordData[i][7] = r.readString() // chord.kindId - - if (version >= 4) - { - for (let i = 0; i < chordNum; i++) - chordData[i][8] = r.readInteger() // chord.inversion - } - - for (let i = 0; i < chordNum; i++) - chordData[i][9] = r.readInteger() // chord.modifiers.length - - if (version >= 4) - { - for (let i = 0; i < chordNum; i++) - { - chordData[i][10] = [] - for (let mod = 0; mod < chordData[i][9]; mod++) - chordData[i][10].push([r.readString(), r.readInteger()]) // chord.modifiers[mod] - } - } - else - { - for (let i = 0; i < chordNum; i++) - for (let mod = 0; mod < chordData[i][8]; mod++) - r.readString() - } - - for (let i = 0; i < chordNum; i++) - { - const start = Rational.fromIntegerPlusRational(chordData[i][0], chordData[i][1], chordData[i][2]) - const duration = new Rational(chordData[i][3], chordData[i][4]) - - song = song.upsertChord(new Project.Chord( - Range.fromStartDuration(start, duration), - new Theory.Chord( - chordData[i][5], - chordData[i][6], - Theory.Chord.kindFromId(chordData[i][7]), - chordData[i][8], - chordData[i][10]))) - } - - // Read key change data as structure-of-arrays. - const keyChangeNum = r.readInteger() - - let keyChangeData = [] - - for (let i = 0; i < keyChangeNum; i++) - keyChangeData[i] = [ r.readInteger() ] // time.numerator - - for (let i = 0; i < keyChangeNum; i++) - keyChangeData[i][1] = r.readInteger() // time.denominator - - for (let i = 0; i < keyChangeNum; i++) - keyChangeData[i][2] = r.readInteger() // key.tonic.letter - - for (let i = 0; i < keyChangeNum; i++) - keyChangeData[i][3] = r.readInteger() // key.tonic.accidental - - for (let i = 0; i < keyChangeNum; i++) - keyChangeData[i][4] = r.readInteger() // key.scale.chromas.length - - for (let i = 0; i < keyChangeNum; i++) - { - keyChangeData[i][5] = [] - for (let p = 0; p < keyChangeData[i][4]; p++) - keyChangeData[i][5][p] = r.readInteger() // key.scale.chromas[p] - } - - for (let i = 0; i < keyChangeNum; i++) - { - const time = new Rational(keyChangeData[i][0], keyChangeData[i][1]) - - song = song.upsertKeyChange(new Project.KeyChange( - time, - new Theory.Key( - new Theory.PitchName(keyChangeData[i][2], keyChangeData[i][3]), - Theory.Scale.fromChromas(keyChangeData[i][5])))) - } - - // Read meter change data as structure-of-arrays. - const meterChangeNum = r.readInteger() - - let meterChangeData = [] - - for (let i = 0; i < meterChangeNum; i++) - meterChangeData[i] = [ r.readInteger() ] // time.numerator - - for (let i = 0; i < meterChangeNum; i++) - meterChangeData[i][1] = r.readInteger() // time.denominator - - for (let i = 0; i < meterChangeNum; i++) - meterChangeData[i][2] = r.readInteger() // numerator - - for (let i = 0; i < meterChangeNum; i++) - meterChangeData[i][3] = r.readInteger() // denominator - - for (let i = 0; i < meterChangeNum; i++) - { - const time = new Rational(meterChangeData[i][0], meterChangeData[i][1]) - - song = song.upsertMeterChange(new Project.MeterChange( - time, - new Theory.Meter(meterChangeData[i][2], meterChangeData[i][3]))) - } - - return song.withRefreshedRange() - } - - - static write(project) - { - const song = project.withRefreshedRange() - - const w = new URLBinaryEncoder() - - // Write header. - w.writeInteger(4) // Version - w.writeInteger(project.baseBpm) - - // Write note tracks. - const trackNum = 1 - w.writeInteger(trackNum) - for (var j = 0; j < trackNum; j++) - { - // Write note data as a structure-of-arrays. - w.writeInteger(project.notes.size) - - let prevNoteRangeStartInteger = 0 - for (const note of project.notes.iterAll()) - { - w.writeInteger(note.range.start.integer - prevNoteRangeStartInteger) - prevNoteRangeStartInteger = note.range.start.integer - } - - for (const note of project.notes.iterAll()) - w.writeInteger(note.range.start.numeratorWithoutInteger) - - for (const note of project.notes.iterAll()) - w.writeInteger(note.range.start.denominator) - - for (const note of project.notes.iterAll()) - w.writeInteger(note.range.duration.numerator) - - for (const note of project.notes.iterAll()) - w.writeInteger(note.range.duration.denominator) - - for (const note of project.notes.iterAll()) - w.writeInteger(note.pitch - 60) - } - - // Write chord data as a structure-of-arrays. - w.writeInteger(project.chords.size) - - let prevChordRangeStartInteger = 0 - for (const chord of project.chords.iterAll()) - { - w.writeInteger(chord.range.start.integer - prevChordRangeStartInteger) - prevChordRangeStartInteger = chord.range.start.integer - } - - for (const chord of project.chords.iterAll()) - w.writeInteger(chord.range.start.numeratorWithoutInteger) - - for (const chord of project.chords.iterAll()) - w.writeInteger(chord.range.start.denominator) - - for (const chord of project.chords.iterAll()) - w.writeInteger(chord.range.duration.numerator) - - for (const chord of project.chords.iterAll()) - w.writeInteger(chord.range.duration.denominator) - - for (const chord of project.chords.iterAll()) - w.writeInteger(chord.chord.rootMidi) - - for (const chord of project.chords.iterAll()) - w.writeInteger(chord.chord.rootAccidental) - - for (const chord of project.chords.iterAll()) - w.writeString(chord.chord.kindId) - - for (const chord of project.chords.iterAll()) - w.writeInteger(chord.chord.inversion) - - for (const chord of project.chords.iterAll()) - w.writeInteger(chord.chord.modifiers.length) - - for (const chord of project.chords.iterAll()) - for (const mod of chord.chord.modifiers) - { - w.writeString(mod[0]) - w.writeInteger(mod[1]) - } - - // Write key change data as a structure-of-arrays. - w.writeInteger(project.keyChanges.size) - - for (const keyChange of project.keyChanges.iterAll()) - w.writeInteger(keyChange.time.numerator) - - for (const keyChange of project.keyChanges.iterAll()) - w.writeInteger(keyChange.time.denominator) - - for (const keyChange of project.keyChanges.iterAll()) - w.writeInteger(keyChange.key.tonic.letter) - - for (const keyChange of project.keyChanges.iterAll()) - w.writeInteger(keyChange.key.tonic.accidental) - - for (const keyChange of project.keyChanges.iterAll()) - w.writeInteger(keyChange.key.scale.chromas.length) - - for (const keyChange of project.keyChanges.iterAll()) - for (const p of keyChange.key.scale.chromas) - w.writeInteger(p) - - // Write meter change data as a structure-of-arrays. - w.writeInteger(project.meterChanges.size) - - for (const meterChange of project.meterChanges.iterAll()) - w.writeInteger(meterChange.time.numerator) - - for (const meterChange of project.meterChanges.iterAll()) - w.writeInteger(meterChange.time.denominator) - - for (const meterChange of project.meterChanges.iterAll()) - w.writeInteger(meterChange.meter.numerator) - - for (const meterChange of project.meterChanges.iterAll()) - w.writeInteger(meterChange.meter.denominator) - - - console.log(w.getCompressedURLSafe().length) - return "4_" + w.getCompressedURLSafe() - } -} \ No newline at end of file diff --git a/src_old/project/ioJson.js b/src_old/project/ioJson.js deleted file mode 100644 index 9aa5284..0000000 --- a/src_old/project/ioJson.js +++ /dev/null @@ -1,191 +0,0 @@ -import Project from "./project.js" -import Theory from "../theory.ts" -import Rational from "../util/rational.js" -import Range from "../util/range.js" - - -export default class IOJson -{ - static write(project) - { - let str = "{\n" - - str += "\"version\": " + 4 + ",\n" - str += "\"bpm\": " + project.baseBpm + ",\n" - str += "\n" - - str += "\"tracks\":\n" - str += "[\n" - for (let track = 0; track < 1; track++) - { - str += "{\n" - str += "\"notes\":\n" - str += "[\n" - let firstNote = true - for (const note of project.notes.iterAll()) - { - if (!firstNote) - str += ",\n" - - firstNote = false - - str += "[" - str += note.range.start .toJSONString() + "," - str += note.range.duration.toJSONString() + "," - str += note.pitch - str += "]" - } - str += "\n" - str += "]\n" - str += "}\n" - } - str += "],\n" - str += "\n" - - str += "\"chords\":\n" - str += "[\n" - let firstChord = true - for (const chord of project.chords.iterAll()) - { - if (!firstChord) - str += ",\n" - - firstChord = false - - str += "[" - str += chord.range.start .toJSONString() + "," - str += chord.range.duration.toJSONString() + "," - str += chord.chord.rootMidi + "," - str += chord.chord.rootAccidental + "," - str += "\"" + chord.chord.kindId + "\"," - str += chord.chord.inversion + "," - str += JSON.stringify(chord.chord.modifiers) - str += "]" - } - str += "\n" - str += "],\n" - str += "\n" - - str += "\"keyChanges\":\n" - str += "[\n" - let firstKeyChange = true - for (const keyChange of project.keyChanges.iterAll()) - { - if (!firstKeyChange) - str += ",\n" - - firstKeyChange = false - - str += "[" - str += keyChange.time.toJSONString() + "," - str += keyChange.key.tonic.letter + "," - str += keyChange.key.tonic.accidental + "," - str += "[" + keyChange.key.scale.chromas.join(",") + "]" - str += "]" - } - str += "\n" - str += "],\n" - str += "\n" - - str += "\"meterChanges\":\n" - str += "[\n" - let firstMeterChange = true - for (const meterChange of project.meterChanges.iterAll()) - { - if (!firstMeterChange) - str += ",\n" - - firstMeterChange = false - - str += "[" - str += meterChange.time.toJSONString() + "," - str += meterChange.meter.numerator + "," - str += meterChange.meter.denominator - str += "]" - } - str += "\n" - str += "]\n" - - str += "}" - return str - } - - - static read(json) - { - let song = new Project() - song.baseBpm = json.bpm - - for (const note of json.tracks[0].notes) - song = song.upsertNote(new Project.Note( - Range.fromStartDuration(Rational.fromArray(note[0]), Rational.fromArray(note[1])), - note[2])) - - if (json.version >= 4) - { - for (const chord of json.chords) - { - let modifiers = [] - for (const mod of chord[6]) - modifiers.push(mod) - - song = song.upsertChord(new Project.Chord( - Range.fromStartDuration(Rational.fromArray(chord[0]), Rational.fromArray(chord[1])), - new Theory.Chord(chord[2], chord[3], Theory.Chord.kindFromId(chord[4]), chord[5], modifiers))) - } - - for (const keyChange of json.keyChanges) - song = song.upsertKeyChange(new Project.KeyChange( - Rational.fromArray(keyChange[0]), - new Theory.Key( - new Theory.PitchName(keyChange[1], keyChange[2]), - Theory.Scale.fromChromas(keyChange[3])))) - } - else if (json.version == 3) - { - for (const chord of json.chords) - { - song = song.upsertChord(new Project.Chord( - Range.fromStartDuration(Rational.fromArray(chord[0]), Rational.fromArray(chord[1])), - new Theory.Chord(chord[2], chord[3], Theory.Chord.kindFromId(chord[4]), 0, []))) - } - - for (const keyChange of json.keyChanges) - song = song.upsertKeyChange(new Project.KeyChange( - Rational.fromArray(keyChange[0]), - new Theory.Key( - new Theory.PitchName( - Theory.Utils.chromaToLetter(keyChange[1] + keyChange[2]) || 0, - Theory.Utils.chromaToAccidental(keyChange[1] + keyChange[2]) || 0), - Theory.Scale.fromChromas(keyChange[3])))) - } - else - { - const oldChordKinds = - ["M","m","+","o","5","6","m6","7","maj7","m7","mmaj7","+7","+maj7","o7","%7","9","maj9","m9","mmaj9","9?","+9","+maj9","o9","ob9","%9","%b9"] - - for (const chord of json.chords) - song = song.upsertChord(new Project.Chord( - Range.fromStartDuration(Rational.fromArray(chord[0]), Rational.fromArray(chord[1])), - new Theory.Chord(chord[3], chord[4], Theory.Chord.kindFromId(oldChordKinds[chord[2]]), 0, []))) - - const oldScales = [ "Major", "Dorian", "Phrygian", "Lydian", "Mixolydian", "Minor", "Locrian" ] - - for (const keyChange of json.keyChanges) - song = song.upsertKeyChange(new Project.KeyChange( - Rational.fromArray(keyChange[0]), - new Theory.Key( - new Theory.PitchName( - Theory.Utils.chromaToLetter(keyChange[2] + keyChange[3]) || 0, - Theory.Utils.chromaToAccidental(keyChange[2] + keyChange[3]) || 0), - Theory.Scale.parse(oldScales[keyChange[1]])))) - } - - for (const meterChange of json.meterChanges) - song = song.upsertMeterChange(new Project.MeterChange( - Rational.fromArray(meterChange[0]), - new Theory.Meter(meterChange[1], meterChange[2]))) - - return song.withRefreshedRange() - } -} \ No newline at end of file diff --git a/src_old/project/ioMidi.js b/src_old/project/ioMidi.js deleted file mode 100644 index 39bbe4d..0000000 --- a/src_old/project/ioMidi.js +++ /dev/null @@ -1,110 +0,0 @@ -import Project from "./project.js" -import Theory from "../theory.ts" -import { MidiFile } from "../util/midi.js" -import Rational from "../util/rational.js" -import Range from "../util/range.js" - - -export default class IOMidi -{ - static read(bytes) - { - const midi = MidiFile.fromBytes(bytes) - console.log(midi) - - const findFirstEvent = (kind) => - { - for (const track of midi.tracks) - for (const ev of track.events) - if (ev.kind == kind) - return ev - - return null - } - - let song = new Project() - - const tempoEv = findFirstEvent("setTempo") - const msPerQuarterNote = (tempoEv ? tempoEv.msPerQuarterNote : 500000) - song.baseBpm = Math.round(60 * 1000 * 1000 / msPerQuarterNote) - - let notesToAdd = [] - for (const track of midi.tracks) - { - for (const noteOn of track.events) - { - if (noteOn.kind != "noteOn" || noteOn.channel == 9 || noteOn.velocity == 0) - continue - - let noteOff = null - for (const ev of track.events) - { - if (ev.kind != "noteOn" && ev.kind != "noteOff") - continue - - if (ev.time <= noteOn.time || ev.channel != noteOn.channel || ev.key != noteOn.key) - continue - - if (noteOff == null || ev.time < noteOff.time) - { - noteOff = ev - break - } - } - - if (!noteOff) - continue - - const onTick = Rational.fromFloat(noteOn.time / midi.ticksPerQuarterNote / 4, 27720) - const offTick = Rational.fromFloat(noteOff.time / midi.ticksPerQuarterNote / 4, 27720) - - notesToAdd.push(new Project.Note(new Range(onTick, offTick), noteOn.key)) - } - } - - song = song.upsertNotes(notesToAdd) - - for (const track of midi.tracks) - { - for (const ev of track.events) - { - if (ev.kind != "setKeySignature") - continue - - const tonicPitches = [ 0, 7, 2, 9, 4, 11, 5, 0, 7, 2, 9, 4, 11, 5, 0, 7, 2, 9, 4, 11] - const tonicLetters = [ 0, 4, 1, 5, 2, 6, 3, 0, 4, 1, 5, 2, 6, 3, 0, 4, 1, 5, 2, 6] - const tonicAccidentals = [-1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] - const index = ev.accidentals + 7 + (ev.scale == 0 ? 0 : 2) - - if (index < 0 || index >= tonicPitches.length) - continue - - const time = Rational.fromFloat(ev.time / midi.ticksPerQuarterNote / 4, 27720) - song = song.upsertKeyChange(new Project.KeyChange(time, - new Theory.Key( - new Theory.PitchName(tonicLetters[index], tonicAccidentals[index]), - Theory.Scale.parse(ev.scale == 0 ? "Major" : "Minor")))) - } - } - - for (const track of midi.tracks) - { - for (const ev of track.events) - { - if (ev.kind != "setTimeSignature") - continue - - const time = Rational.fromFloat(ev.time / midi.ticksPerQuarterNote / 4, 27720) - song = song.upsertMeterChange(new Project.MeterChange(time, new Theory.Meter(ev.numerator, ev.denominator))) - } - } - - if (!song.keyChanges.findActiveAt(new Rational(0))) - song = song.upsertKeyChange(new Project.KeyChange(new Rational(0), Theory.Key.parse("C Major"))) - - if (!song.meterChanges.findActiveAt(new Rational(0))) - song = song.upsertMeterChange(new Project.MeterChange(new Rational(0), new Theory.Meter(4, 4))) - - return song.withRefreshedRange() - } -} \ No newline at end of file diff --git a/src_old/project/ioMidi.ts b/src_old/project/ioMidi.ts deleted file mode 100644 index 387c61e..0000000 --- a/src_old/project/ioMidi.ts +++ /dev/null @@ -1,120 +0,0 @@ -import Project from "../project/project2" -import * as Theory from "../theory/theory" -import { MidiFileReader } from "../util/midi" -import Rational from "../util/rational.js" -import Range from "../util/range.js" - - -export default class IoMidi -{ - static read(bytes: number[] | Buffer | Uint8Array): Project - { - const midi = MidiFileReader.fromBytes(bytes) - console.log(midi) - - const findFirstEvent = (kind: string) => - { - for (const track of midi.tracks) - for (const ev of track.events) - if (ev.kind == kind) - return ev - - return null - } - - let project = new Project() - - const tempoEv = findFirstEvent("setTempo") - const msPerQuarterNote = (tempoEv ? tempoEv.msPerQuarterNote : 500000) - project.baseBpm = Math.round(60 * 1000 * 1000 / msPerQuarterNote) - - const track1Id = project.nextId - project = Project.upsertTrack(project, new Project.TrackKeyChanges()) - - const track2Id = project.nextId - project = Project.upsertTrack(project, new Project.TrackMeterChanges()) - - project = Project.upsertTimedElement(project, new Project.KeyChange(track1Id, new Rational(0), Theory.Key.parse("D Major"))) - project = Project.upsertTimedElement(project, new Project.MeterChange(track2Id, new Rational(0), new Theory.Meter(4, 4))) - - /*for (const track of midi.tracks) - { - for (const ev of track.events) - { - if (ev.kind != "setKeySignature") - continue - - const tonicPitches = [ 0, 7, 2, 9, 4, 11, 5, 0, 7, 2, 9, 4, 11, 5, 0, 7, 2, 9, 4, 11] - const tonicLetters = [ 0, 4, 1, 5, 2, 6, 3, 0, 4, 1, 5, 2, 6, 3, 0, 4, 1, 5, 2, 6] - const tonicAccidentals = [-1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] - const index = ev.accidentals + 7 + (ev.scale == 0 ? 0 : 2) - - if (index < 0 || index >= tonicPitches.length) - continue - - const time = Rational.fromFloat(ev.time / midi.ticksPerQuarterNote / 4, 27720) - song = song.upsertKeyChange(new Project.KeyChange(time, - new Theory.Key( - new Theory.PitchName(tonicLetters[index], tonicAccidentals[index]), - Theory.Scale.parse(ev.scale == 0 ? "Major" : "Minor")))) - } - } - - for (const track of midi.tracks) - { - for (const ev of track.events) - { - if (ev.kind != "setTimeSignature") - continue - - const time = Rational.fromFloat(ev.time / midi.ticksPerQuarterNote / 4, 27720) - song = song.upsertMeterChange(new Project.MeterChange(time, new Theory.Meter(ev.numerator, ev.denominator))) - } - }*/ - - for (const track of midi.tracks) - { - const trackId = project.nextId - project = Project.upsertTrack(project, new Project.TrackNotes()) - - for (const noteOn of track.events) - { - if (noteOn.kind != "noteOn" || noteOn.channel == 9 || noteOn.velocity == 0) - continue - - let noteOff = null - for (const ev of track.events) - { - if (ev.kind != "noteOn" && ev.kind != "noteOff") - continue - - if (ev.time <= noteOn.time || ev.channel != noteOn.channel || ev.key != noteOn.key) - continue - - if (noteOff == null || ev.time < noteOff.time) - { - noteOff = ev - break - } - } - - if (!noteOff) - continue - - const onTick = Rational.fromFloat(noteOn.time / midi.ticksPerQuarterNote / 4, 27720) - const offTick = Rational.fromFloat(noteOff.time / midi.ticksPerQuarterNote / 4, 27720) - - project = Project.upsertRangedElement(project, - new Project.Note(trackId, new Range(onTick, offTick), noteOn.key)) - } - } - - /*if (!song.keyChanges.findActiveAt(new Rational(0))) - song = song.upsertKeyChange(new Project.KeyChange(new Rational(0), Theory.Key.parse("C Major"))) - - if (!song.meterChanges.findActiveAt(new Rational(0))) - song = song.upsertMeterChange(new Project.MeterChange(new Rational(0), new Theory.Meter(4, 4))) - */ - return project//.withRefreshedRange() - } -} \ No newline at end of file diff --git a/src_old/project/playbackSynth.js b/src_old/project/playbackSynth.js deleted file mode 100644 index bfcf0dc..0000000 --- a/src_old/project/playbackSynth.js +++ /dev/null @@ -1,174 +0,0 @@ -import Rational from "../util/rational.js" -import MathUtils from "../util/math.js" -import Editor from "../editor/editor.js" - - -export default class PlaybackSynth -{ - static feedToSynth(project, synth, startTick, useChordPatterns = true) - { - const chordVolumeMul = 0.5 - - const addNoteEvent = (tickStart, duration, instrument, pitch, volume) => - { - const offsetStart = tickStart.subtract(startTick) - - const measuresPerSecond = (project.baseBpm / 4 / 60) - - let timeStart = offsetStart.asFloat() / measuresPerSecond - let timeDuration = duration.asFloat() / measuresPerSecond - - if (timeStart <= 0) - { - timeDuration += timeStart - timeStart = 0 - } - - if (timeDuration <= 0) - return - - //console.log("event note [" + pitch + "] at tick " + tickStart.toString() + " = " + timeStart + " s") - synth.addNoteEvent(timeStart, instrument, MathUtils.midiToHertz(pitch), volume, timeDuration) - } - - // Register notes. - for (const note of project.notes.iterAll()) - { - if (note.range.end.compare(startTick) <= 0) - continue - - addNoteEvent(note.range.start, note.range.duration, 0, note.pitch, 1) - } - - // Register chords. - for (const chord of project.chords.iterAll()) - { - if (chord.range.end.compare(startTick) <= 0) - continue - - const pitches = chord.chord.strummingPitches - - if (!useChordPatterns) - { - for (let j = 0; j < pitches.length; j++) - addNoteEvent(chord.range.start, chord.range.duration, 1, pitches[j], chordVolumeMul) - - continue - } - - const meterCh = project.meterChanges.findActiveAt(chord.range.start) - const meter = meterCh ? meterCh.meter : Editor.defaultMeter() - const meterBeatLength = new Rational(1, meter.denominator) - const measureBreak = null//that.forcedMeasures.findActiveAt(chord.range.start) - - const pattern = getChordStrummingPattern(meter) - - let tick = meterCh ? meterCh.time : chord.range.start - //if (measureBreak != null) - // tick.max(measureBreak.tick) - - let skipTick = new Rational(0) - let patternIndex = 0 - let mustPlayFirstBeat = false - - while (tick.compare(chord.range.end) < 0) - { - const patternBeat = pattern[patternIndex] - const patternBeatKind = patternBeat[0] - let patternBeatLength = patternBeat[1].multiply(meterBeatLength) - patternIndex = (patternIndex + 1) % pattern.length - - let nextTick = tick.add(patternBeatLength) - if (nextTick.compare(chord.range.end) > 0) - { - nextTick = chord.range.end - patternBeatLength = nextTick.subtract(tick) - } - - // Handle beats after the first one. - if (tick.compare(chord.range.start) > 0 && skipTick.compare(new Rational(0)) <= 0) - { - if (mustPlayFirstBeat) - { - mustPlayFirstBeat = false - for (let j = 0; j < pitches.length; j++) - addNoteEvent(chord.range.start, tick.subtract(chord.range.start), 1, pitches[j], chordVolumeMul) - } - - switch (patternBeatKind) - { - case 0: - { - for (let j = 0; j < pitches.length; j++) - addNoteEvent(tick, patternBeatLength, 1, pitches[j], 0.9 * chordVolumeMul) - break - } - case 1: - { - for (let j = 1; j < pitches.length; j++) - addNoteEvent(tick, patternBeatLength, 1, pitches[j], 0.5 * chordVolumeMul) - break - } - case 2: - { - addNoteEvent(tick, patternBeatLength, 1, pitches[0], 0.5 * chordVolumeMul) - break - } - } - } - - skipTick = skipTick.subtract(patternBeatLength) - - if (tick.compare(chord.range.start) <= 0 && nextTick.compare(chord.range.start) > 0) - { - mustPlayFirstBeat = true - - if (!(tick.compare(chord.range.start) == 0 && patternBeatKind == 0)) - skipTick = pattern[0][1].multiply(meterBeatLength) - } - - tick = nextTick - } - - if (mustPlayFirstBeat) - { - mustPlayFirstBeat = false - for (let j = 0; j < pitches.length; j++) - addNoteEvent(chord.range.start, tick.subtract(chord.range.start), 1, pitches[j], 0.7) - } - } - } -} - - -function getChordStrummingPattern(meter) -{ - // [[beat kind, duration], ...] - // Beat kinds: - // 0: Full chord - // 1: Full chord minus bass - // 2: Only bass - const one = [[0, new Rational(1)]] - const two = [[0, new Rational(1)], [1, new Rational(1, 2)], [2, new Rational(1, 2)]] - const three = [[0, new Rational(1)], [1, new Rational(1) ], [1, new Rational(1) ]] - - switch (meter.numerator) - { - case 2: return two - case 3: return three - case 4: return two.concat(two) - case 5: return three.concat(two) - case 6: return three.concat(three) - case 7: return three.concat(two).concat(two) - case 8: return two.concat(two).concat(two).concat(two) - case 9: return three.concat(three).concat(three) - - default: - { - let pattern = [] - for (let i = 0; i < meter.numerator; i++) - pattern = pattern.concat(one) - return pattern - } - } -} diff --git a/src_old/project/project.js b/src_old/project/project.js deleted file mode 100644 index 5cd9015..0000000 --- a/src_old/project/project.js +++ /dev/null @@ -1,280 +0,0 @@ -import ListOfPoints from "../util/listOfPoints.js" -import ListOfRanges from "../util/listOfRanges.js" -import Rational from "../util/rational.js" -import Range from "../util/range.js" -import Theory from "../theory.ts" -import IOJson from "./ioJson.js" -import IOMidi from "./ioMidi.js" -import IOCompressedStr from "./ioCompressedStr.js" - - -export default class Project -{ - constructor() - { - this.nextId = 1 - this.baseBpm = 120 - this.range = new Range(new Rational(0), new Rational(4)) - this.notes = new ListOfRanges() - this.chords = new ListOfRanges() - this.meterChanges = new ListOfPoints() - this.keyChanges = new ListOfPoints() - } - - - static getDefault() - { - return new Project() - .upsertKeyChange(new Project.KeyChange(new Rational(0, 4), Theory.Key.parse("C Major"))) - .upsertMeterChange(new Project.MeterChange(new Rational(0, 4), new Theory.Meter(4, 4))) - .withRefreshedRange() - } - - - withChanges(obj) - { - let song = new Project() - song.nextId = this.nextId - song.baseBpm = this.baseBpm - song.range = this.range - song.notes = this.notes - song.chords = this.chords - song.meterChanges = this.meterChanges - song.keyChanges = this.keyChanges - - return Object.assign(song, obj) - } - - - withRefreshedRange() - { - let range = null - range = Range.merge(range, this.notes.getTotalRange()) - range = Range.merge(range, this.chords.getTotalRange()) - range = Range.merge(range, this.meterChanges.getTotalRange()) - range = Range.merge(range, this.keyChanges.getTotalRange()) - - if (range === null) - range = new Range(new Rational(0), new Rational(1)) - - if (range.duration.asFloat() < 1) - range = Range.fromStartDuration(range.start, new Rational(1)) - - if (this.range.start.compare(range.start) == 0 && - this.range.end.compare(range.end) == 0) - return this - - return this.withChanges({ range }) - } - - - _upsertElement(listField, elem, remove = false) - { - let nextId = this.nextId - let list = this[listField] - - if (elem.id < 0) - { - elem = elem.withChanges({ id: nextId }) - nextId++ - } - else - { - list = list.removeById(elem.id) - } - - if (!remove) - list = list.add(elem) - - return this.withChanges({ nextId, [listField]: list }) - } - - - _upsertElements(listField, elems, remove = false) - { - let nextId = this.nextId - let list = this[listField] - - let newElems = [] - for (let i = 0; i < elems.length; i++) - { - const elem = elems[i] - if (elem.id < 0) - { - newElems.push(elem.withChanges({ id: nextId })) - nextId++ - } - else - { - newElems.push(elem) - list = list.removeById(elem.id) - } - } - - if (!remove) - list = list.addMany(newElems) - - return this.withChanges({ nextId, [listField]: list }) - } - - - upsertNote(note, remove = false) - { - return this._upsertElement("notes", note, remove) - } - - - upsertNotes(notes, remove = false) - { - return this._upsertElements("notes", notes, remove) - } - - - upsertChord(chord, remove = false) - { - return this._upsertElement("chords", chord, remove) - } - - - upsertMeterChange(meterChange, remove = false) - { - return this._upsertElement("meterChanges", meterChange, remove) - } - - - upsertKeyChange(keyChange, remove = false) - { - return this._upsertElement("keyChanges", keyChange, remove) - } - - - findById(id) - { - return ( - this.notes.findById(id) || - this.chords.findById(id) || - this.keyChanges.findById(id) || - this.meterChanges.findById(id) - ) - } - - - update(elem) - { - let song = this.withChanges({}) - song.notes = song.notes.update(elem) - song.chords = song.chords.update(elem) - song.keyChanges = song.keyChanges.update(elem) - song.meterChanges = song.meterChanges.update(elem) - return song - } - - - removeById(id) - { - let song = this.withChanges({}) - song.notes = song.notes.removeById(id) - song.chords = song.chords.removeById(id) - song.keyChanges = song.keyChanges.removeById(id) - song.meterChanges = song.meterChanges.removeById(id) - return song - } - - - toJson() - { - return IOJson.write(this) - } - - - static fromJson(json) - { - return IOJson.read(json) - } - - - static fromMidi(bytes) - { - return IOMidi.read(bytes) - } - - - toCompressedStr() - { - return IOCompressedStr.write(this) - } - - - static fromCompressedStr(str) - { - return IOCompressedStr.read(str) - } - - - static Note = class ProjectNote - { - constructor(range, pitch) - { - this.id = -1 - this.range = range - this.pitch = pitch - } - - - withChanges(obj) - { - return Object.assign(new ProjectNote(this.range, this.pitch), { id: this.id }, obj) - } - } - - - static Chord = class ProjectChord - { - constructor(range, chord) - { - this.id = -1 - this.range = range - this.chord = chord - } - - - withChanges(obj) - { - return Object.assign(new ProjectChord(this.range, this.chord), { id: this.id }, obj) - } - } - - - static MeterChange = class ProjectMeterChange - { - constructor(time, meter) - { - this.id = -1 - this.time = time - this.meter = meter - } - - - withChanges(obj) - { - return Object.assign(new ProjectMeterChange(this.time, this.meter), { id: this.id }, obj) - } - } - - - static KeyChange = class ProjectKeyChange - { - constructor(time, key) - { - this.id = -1 - this.time = time - this.key = key - } - - - withChanges(obj) - { - return Object.assign(new ProjectKeyChange(this.time, this.key), { id: this.id }, obj) - } - } -} \ No newline at end of file diff --git a/src_old/project/project2.ts b/src_old/project/project2.ts deleted file mode 100644 index b36f78b..0000000 --- a/src_old/project/project2.ts +++ /dev/null @@ -1,329 +0,0 @@ -import Immutable from "immutable" -import Range from "../util/range" -import Rational from "../util/rational" -import ListOfPoints from "../util/listOfPoints" -import ListOfRanges from "../util/listOfRanges" -import * as Theory from "../theory/theory" - - -export class Project -{ - nextId: Project.ID - baseBpm: number - tracks: Project.Track[] - timedLists: Immutable.Map> - rangedLists: Immutable.Map> - elems: Immutable.Map - - - constructor() - { - this.nextId = 1 - this.baseBpm = 120 - this.tracks = [] - this.timedLists = Immutable.Map>() - this.rangedLists = Immutable.Map>() - this.elems = Immutable.Map() - } - - - static getDefault(): Project - { - let project = new Project() - - const track1Id = project.nextId - project = Project.upsertTrack(project, new Project.TrackKeyChanges()) - - const track2Id = project.nextId - project = Project.upsertTrack(project, new Project.TrackMeterChanges()) - - project = Project.upsertTimedElement(project, new Project.KeyChange(track1Id, new Rational(0), Theory.Key.parse("D Major"))) - project = Project.upsertTimedElement(project, new Project.MeterChange(track2Id, new Rational(0), new Theory.Meter(4, 4))) - - const track3Id = project.nextId - project = Project.upsertTrack(project, new Project.TrackNotes()) - for (let i = 0; i < 16; i++) - project = Project.upsertRangedElement(project, new Project.Note(track3Id, Range.fromStartDuration(new Rational(i, 4), new Rational(1, 4)), 60 + i % 8)) - - /*const track4Id = project.nextId - project = Project.upsertTrack(project, new Project.TrackNotes()) - for (let i = 0; i < 16; i++) - project = Project.upsertRangedElement(project, new Project.Note(track4Id, Range.fromStartDuration(new Rational(i, 4), new Rational(1, 4)), 68 - (i % 8))) - */ - - return project - } - - - static upsertTrack(project: Project, track: Project.Track, remove: boolean = false): Project - { - let nextId = project.nextId - let tracks = project.tracks - - if (!track.id) - { - track = track.withChanges({ id: nextId }) - nextId++ - } - else - { - tracks = tracks.filter(t => t.id !== track.id) - } - - if (!remove) - { - const trackIndex = tracks.findIndex(t => t.id === track.id) - if (trackIndex < 0) - tracks = [...tracks, track] - else - tracks = [ - ...tracks.slice(0, trackIndex), - track, - ...tracks.slice(trackIndex + 1) - ] - } - - let elems = project.elems - if (!remove) - elems = elems.set(track.id, track) - - return Object.assign({}, project, { nextId, elems, tracks }) - } - - - static upsertTimedElement(project: Project, elem: Project.TimedElement, remove: boolean = false): Project - { - let nextId = project.nextId - - if (!elem.id) - { - elem = Project.Element.withChanges(elem, { id: nextId }) - nextId++ - } - - let timedList = project.timedLists.get(elem.parentId) || new ListOfPoints() - timedList = timedList.add(elem) - - let elems = project.elems.set(elem.id, elem) - let timedLists = project.timedLists.set(elem.parentId, timedList) - - return Object.assign({}, project, { nextId, elems, timedLists }) - } - - - static upsertRangedElement(project: Project, elem: Project.RangedElement, remove: boolean = false): Project - { - let nextId = project.nextId - - if (!elem.id) - { - elem = Project.Element.withChanges(elem, { id: nextId }) - nextId++ - } - - let rangedList = project.rangedLists.get(elem.parentId) || new ListOfRanges() - rangedList = rangedList.add(elem) - - let elems = project.elems.set(elem.id, elem) - let rangedLists = project.rangedLists.set(elem.parentId, rangedList) - - return Object.assign({}, project, { nextId, elems, rangedLists }) - } - - - static keyChangeTrackForTrack(project: Project, trackId: Project.ID): Project.ID - { - return 1 - } - - - static meterChangeTrackForTrack(project: Project, trackId: Project.ID): Project.ID - { - return 2 - } -} - - -export namespace Project -{ - export type ID = number - - - export enum ElementType - { - Track, - Note, - KeyChange, - MeterChange, - } - - - export class Element - { - type: Project.ElementType = -1 - id: Project.ID = 0 - parentId: Project.ID = 0 - - - constructor(type: Project.ElementType, parentId: Project.ID) - { - this.type = type - this.parentId = parentId - } - - - static withChanges(original: T, changes: any): T - { - return Object.assign({}, original, changes) - } - } - - - export enum TrackType - { - Notes, - KeyChanges, - MeterChanges, - } - - - export class Track extends Element - { - trackType: Project.TrackType = 0 - - - constructor(trackType: Project.TrackType) - { - super(ElementType.Track, 0) - this.trackType = trackType - } - - - withChanges(changes: any): this - { - return Object.assign({}, this, changes) - } - } - - - export enum TrackInstrumentType - { - Sflib, - } - - - export interface TrackInstrument - { - instrumentType: TrackInstrumentType - } - - - export interface TrackInstrumentSflib extends TrackInstrument - { - collectionId: string - instrumentId: string - } - - - export class TrackNotes extends Track - { - instrument: TrackInstrument - - - constructor() - { - super(Project.TrackType.Notes) - this.instrument = { - instrumentType: TrackInstrumentType.Sflib, - collectionId: "arachno", - instrumentId: "grand_piano", - } - } - } - - - export class TrackKeyChanges extends Track - { - constructor() - { - super(Project.TrackType.KeyChanges) - } - } - - - export class TrackMeterChanges extends Track - { - constructor() - { - super(Project.TrackType.MeterChanges) - } - } - - - export class TimedElement extends Element - { - time: Rational - - - constructor(type: Project.ElementType, parentId: Project.ID, time: Rational) - { - super(type, parentId) - this.time = time - } - } - - - export class RangedElement extends Element - { - range: Range - - - constructor(type: Project.ElementType, parentId: Project.ID, range: Range) - { - super(type, parentId) - this.range = range - } - } - - - export class Note extends RangedElement - { - pitch: number - - - constructor(parentId: Project.ID, range: Range, pitch: number) - { - super(Project.ElementType.Note, parentId, range) - this.pitch = pitch - } - } - - - export class KeyChange extends TimedElement - { - key: Theory.Key - - - constructor(parentId: Project.ID, time: Rational, key: Theory.Key) - { - super(Project.ElementType.KeyChange, parentId, time) - this.key = key - } - } - - - export class MeterChange extends TimedElement - { - meter: Theory.Meter - - - constructor(parentId: Project.ID, time: Rational, meter: Theory.Meter) - { - super(Project.ElementType.MeterChange, parentId, time) - this.meter = meter - } - } -} - - -export default Project \ No newline at end of file diff --git a/src_old/synth/instrument.js b/src_old/synth/instrument.js deleted file mode 100644 index dbe736e..0000000 --- a/src_old/synth/instrument.js +++ /dev/null @@ -1,168 +0,0 @@ -// `sampleDefs` is an array containing each sample's -// URL and frequency, sorted by frequency, like so: -// [ -// [440, "piano/a4.mp3"], -// [880, "piano/a5.mp3"] -// ] -export class Instrument -{ - constructor(synth, sampleDefs) - { - this.synth = synth - - this.samples = [] - for (let i = 0; i < sampleDefs.length; i++) - this.samples.push(this.load(synth, sampleDefs[i][1], sampleDefs[i][0])) - } - - - load(synth, url, frequency) - { - let sample = - { - buffer: null, - frequency - } - - let request = new XMLHttpRequest() - request.open("GET", url, true) - request.responseType = "arraybuffer" - - request.onload = () => - { - synth.audioCtx.decodeAudioData( - request.response, - (buffer) => { sample.buffer = buffer }, - () => { console.log("Error loading audio <" + url + ">") }) - } - - request.send() - return sample - } - - - playNote(desiredFreq, desiredVolume, duration) - { - // Find the primary sample whose frequency is nearest to the desired frequency. - let sample1 = 0 - let sample1Dist = Math.abs(this.samples[0].frequency - desiredFreq) - for (let i = 1; i < this.samples.length; i++) - { - const dist = Math.abs(this.samples[i].frequency - desiredFreq) - if (dist < sample1Dist) - { - sample1 = i - sample1Dist = dist - } - } - - // Find the sample whose frequency is second nearest to the desired frequency, - // unless the primary sample is already exactly a match. - // Then, calculate a cross-fade between the two. - // TODO: Probably should use an equal-power (logarithmic) cross-fade, - // but this seems good enough for now. - let sample2 = null - - let volume1 = 1 - let volume2 = 0 - - if (this.samples[sample1].frequency < desiredFreq) - { - if (sample1 + 1 < this.samples.length) - { - sample2 = sample1 + 1 - - const t = - (desiredFreq - this.samples[sample1].frequency) / - (this.samples[sample2].frequency - this.samples[sample1].frequency) - - volume1 = 1 - t - volume2 = t - } - } - else if (this.samples[sample1].frequency > desiredFreq) - { - if (sample1 - 1 >= 0) - { - sample2 = sample1 - 1 - - const t = - (desiredFreq - this.samples[sample2].frequency) / - (this.samples[sample1].frequency - this.samples[sample2].frequency) - - volume1 = t - volume2 = 1 - t - } - } - - // Play the samples. - const voices = [ this.playVoice(this.samples[sample1], desiredFreq, desiredVolume * volume1) ] - if (sample2 != null) - voices.push(this.playVoice(this.samples[sample2], desiredFreq, desiredVolume * volume2)) - - const data = - { - duration, - released: false, - voices - } - - return data - } - - - playVoice(sample, frequency, volume) - { - let sourceNode = this.synth.audioCtx.createBufferSource() - sourceNode.playbackRate.value = frequency / sample.frequency - sourceNode.buffer = sample.buffer - sourceNode.start(0) - - let envelopeNode = this.synth.audioCtx.createGain() - envelopeNode.gain.setValueAtTime(1, this.synth.audioCtx.currentTime) - - let volumeNode = this.synth.audioCtx.createGain() - volumeNode.gain.value = volume - - sourceNode.connect(envelopeNode) - envelopeNode.connect(volumeNode) - volumeNode.connect(this.synth.audioCtxOutput) - - return { source: sourceNode, envelope: envelopeNode, volume: volumeNode } - } - - - processNote(noteData, deltaTime) - { - noteData.duration -= deltaTime - - const releaseFalloffDuration = 0.1 - - if (noteData.duration <= 0 && !noteData.released) - { - noteData.released = true - for (let i = 0; i < noteData.voices.length; i++) - { - noteData.voices[i].envelope.gain.setValueAtTime(1, this.synth.audioCtx.currentTime) - noteData.voices[i].envelope.gain.linearRampToValueAtTime(0, this.synth.audioCtx.currentTime + releaseFalloffDuration) - } - } - - if (noteData.duration <= -releaseFalloffDuration * 2) - { - for (let i = 0; i < noteData.voices.length; i++) - noteData.voices[i].source.stop() - - return true - } - - return false - } - - - stopNote(noteData) - { - for (let i = 0; i < noteData.voices.length; i++) - noteData.voices[i].source.stop() - } -} \ No newline at end of file diff --git a/src_old/synth/synth.js b/src_old/synth/synth.js deleted file mode 100644 index 9861eb3..0000000 --- a/src_old/synth/synth.js +++ /dev/null @@ -1,111 +0,0 @@ -import { Instrument } from "./instrument.js" - - -export class Synth -{ - constructor() - { - this.audioCtx = new AudioContext() - this.time = 0 - this.noteEvents = [] - this.playingNotes = [] - - this.audioCtxOutput = this.audioCtx.createGain() - this.audioCtxOutput.connect(this.audioCtx.destination) - this.audioCtxOutput.gain.value = 0.5 - - const piano = new Instrument(this, - [ - [ 55.0, "audio/piano/a1.mp3"], - [ 110.0, "audio/piano/a2.mp3"], - [ 220.0, "audio/piano/a3.mp3"], - [ 440.0, "audio/piano/a4.mp3"], - [ 880.0, "audio/piano/a5.mp3"], - [1760.0, "audio/piano/a6.mp3"], - //[3520.0, "audio/piano/a7.mp3"] - ]) - - this.instruments = [piano, piano] - } - - - isFinished() - { - return this.noteEvents.length == 0 && this.playingNotes.length == 0 - } - - - play() - { - this.noteEvents.sort(function (a, b) { return a.time - b.time }) - } - - - stopAll() - { - this.time = 0 - this.noteEvents = [] - - for (let i = 0; i < this.playingNotes.length; i++) - this.playingNotes[i].stop() - - this.playingNotes = [] - } - - - process(deltaTime) - { - this.time += deltaTime - - // Process pending note events up to the current time. - let noteEventsProcessed = 0 - while (noteEventsProcessed < this.noteEvents.length && - this.noteEvents[noteEventsProcessed].time <= this.time) - { - const ev = this.noteEvents[noteEventsProcessed] - noteEventsProcessed++ - - let noteData = - this.instruments[ev.instrumentIndex].playNote(ev.frequency, ev.volume, ev.duration) - - noteData.process = (deltaTime) => - { - return this.instruments[ev.instrumentIndex].processNote(noteData, deltaTime) - } - - noteData.stop = () => - { - this.instruments[ev.instrumentIndex].stopNote(noteData) - } - - this.playingNotes.push(noteData) - //console.log("play note " + ev.frequency) - } - - // Remove processed events. - this.noteEvents.splice(0, noteEventsProcessed) - - // Update audio output. - for (let i = this.playingNotes.length - 1; i >= 0; i--) - { - if (this.playingNotes[i].process(deltaTime)) - this.playingNotes.splice(i, 1) - } - } - - - addNoteEvent(time, instrumentIndex, frequency, volume, duration) - { - if (!isFinite(frequency)) - return - - this.noteEvents.push( - { - time: time + this.time, - instrumentIndex, - frequency, - volume, - duration - }) - } -} \ No newline at end of file diff --git a/src_old/test.ts b/src_old/test.ts deleted file mode 100644 index 968af28..0000000 --- a/src_old/test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import ListOfPoints from "./util/listOfPoints" -import ListOfRanges from "./util/listOfRanges" -import { test as testTheory } from "./theory/test" - - -ListOfPoints.test() -ListOfRanges.test() -testTheory() \ No newline at end of file diff --git a/src_old/timeline/Element.tsx b/src_old/timeline/Element.tsx new file mode 100644 index 0000000..53b28b2 --- /dev/null +++ b/src_old/timeline/Element.tsx @@ -0,0 +1,393 @@ +import React from "react" +import * as Timeline from "./index" +import * as Project from "../project" +import * as Playback from "../playback" +import * as Prefs from "../prefs" +import * as Popup from "../popup" +import * as Dockable from "../dockable" +import * as UI from "../ui" +import { useRefState, RefState } from "../util/refState" +import Rect from "../util/rect" +import styled from "styled-components" + + +const StyledTrackButton = styled.button` + pointer-events: auto; + color: #fff; + border: 1px solid #888; + border-radius: 0.5em; + background-color: #2f3136; + padding: 0.1em 0.3em; + cursor: pointer; + + &:hover + { + border: 1px solid #fff; + } +` + + +export function TimelineElement(props: { state?: RefState }) +{ + const refDiv = React.useRef(null) + const refCanvas = React.useRef(null) + + const editorState = props.state ?? useRefState(() => Timeline.init()) + const dockableWindow = Dockable.useWindow() + + const lastTimelineRenderRef = React.useRef(0) + + const render = (force?: boolean) => + { + if (!refCanvas.current) + return + + const now = new Date().getTime() + if (!force && now < lastTimelineRenderRef.current + 15) + return + + //console.log("Timeline render") + lastTimelineRenderRef.current = now + + Timeline.render( + editorState.ref.current, + refCanvas.current.getContext("2d")!) + } + + const onResize = () => + { + if (!refDiv.current || !refCanvas.current) + return + + const pixelRatio = window.devicePixelRatio || 1 + + const rect = refDiv.current.getBoundingClientRect() + const x = Math.floor(rect.x) + const y = Math.floor(rect.y) + const w = Math.floor(rect.width * pixelRatio) + const h = Math.floor(rect.height * pixelRatio) + + refCanvas.current.style.width = Math.floor(rect.width) + "px" + refCanvas.current.style.height = Math.floor(rect.height) + "px" + refCanvas.current.width = w + refCanvas.current.height = h + + const renderRect = new Rect(x, y, w, h) + + Timeline.resize(editorState.ref.current, pixelRatio, renderRect) + render(true) + } + + React.useLayoutEffect(() => + { + if (!refDiv.current) + return + + const observer = new ResizeObserver(entries => + { + onResize() + }) + + const elem = refDiv.current + observer.observe(elem) + + onResize() + return () => observer.unobserve(elem) + + }, [refDiv.current]) + + React.useEffect(() => + { + Timeline.scrollPlaybackTimeIntoView(editorState.ref.current) + render(!Playback.global.playing) + + }, [Playback.globalObservable.updateToken]) + + React.useEffect(() => + { + Timeline.refreshTracks(editorState.ref.current) + render(true) + + }, [Project.global.project.tracks]) + + React.useEffect(() => + { + const refCanvasCurrent = refCanvas.current + if (!refCanvasCurrent) + return + + const transformMousePos = (canvas: HTMLCanvasElement, ev: MouseEvent) => + { + const rect = canvas.getBoundingClientRect() + return { + x: (ev.clientX - rect.left) * editorState.ref.current.pixelRatio, + y: (ev.clientY - rect.top) * editorState.ref.current.pixelRatio, + } + } + + const preventDefault = (ev: Event) => ev.preventDefault() + + const setCursor = (state: Timeline.State) => + { + let cursor = "text" + const mouseAction = + state.mouse.down ? state.mouse.action : + state.hoverControl != Timeline.TrackControl.None ? Timeline.MouseAction.DragTrackControl : + state.hover ? state.hover.action : + Timeline.MouseAction.None + + if (state.tracks.some((tr: any) => !!tr.pencil) || + mouseAction == Timeline.MouseAction.Pencil) + cursor = "crosshair" + else if (mouseAction == Timeline.MouseAction.DragTrackControl) + cursor = "crosshair" + else if (mouseAction == Timeline.MouseAction.DragClone) + cursor = "crosshair" + else if (mouseAction == Timeline.MouseAction.DragTime || + mouseAction == Timeline.MouseAction.DragTimeAndRow || + mouseAction == Timeline.MouseAction.DragRow || + mouseAction == Timeline.MouseAction.DragTrackHeader) + cursor = (state.mouse.down ? "grabbing" : "grab") + else if (mouseAction == Timeline.MouseAction.Pan) + cursor = "move" + else if (mouseAction == Timeline.MouseAction.StretchTimeStart || + mouseAction == Timeline.MouseAction.StretchTimeEnd) + cursor = "col-resize" + + refCanvasCurrent.style.cursor = cursor + } + + const onMouseMove = (ev: MouseEvent) => + { + const state = editorState.ref.current + if (state.mouse.down) + ev.preventDefault() + + const pos = transformMousePos(refCanvasCurrent, ev) + const projectBefore = Project.global.project + const needsRender1 = Timeline.mouseMove(state, pos) + const needsRender2 = Timeline.mouseDrag(state, pos, false) + + if (projectBefore !== Project.global.project) + Project.notifyObservers() + + if (needsRender1 || needsRender2) + render(true) + + setCursor(state) + } + + const onMouseDown = (ev: MouseEvent) => + { + if (document.activeElement instanceof HTMLElement) + document.activeElement.blur() + + ev.preventDefault() + + Dockable.removeEphemerals(Dockable.global.state) + Dockable.notifyObservers() + + const state = editorState.ref.current + const pos = transformMousePos(refCanvasCurrent, ev) + const projectBefore = Project.global.project + Timeline.mouseMove(state, pos) + Timeline.mouseDown(state, ev.button != 0) + Timeline.mouseDrag(state, pos, true) + + if (projectBefore !== Project.global.project) + Project.notifyObservers() + + render() + setCursor(state) + editorState.commit() + } + + const onMouseUp = (ev: MouseEvent) => + { + const state = editorState.ref.current + if (state.mouse.down) + ev.preventDefault() + + const pos = transformMousePos(refCanvasCurrent, ev) + const projectBefore = Project.global.project + Timeline.mouseUp(state) + + if (projectBefore !== Project.global.project) + { + Project.notifyObservers() + window.dispatchEvent(new Event("timelineRefresh")) + } + + render() + setCursor(state) + editorState.commit() + } + + const onMouseWheel = (ev: WheelEvent) => + { + Timeline.mouseWheel(editorState.ref.current, ev.deltaX, ev.deltaY) + render() + } + + const onKeyDown = (ev: KeyboardEvent) => + { + const isActiveWindow = + Dockable.global.state.activePanel === dockableWindow.panel && + (!document.activeElement || document.activeElement.tagName != "INPUT") + + const projectBefore = Project.global.project + Timeline.keyDown(editorState.ref.current, isActiveWindow, ev.key.toLowerCase()) + + if (projectBefore !== Project.global.project) + Project.notifyObservers() + + render() + editorState.commit() + } + + const onKeyUp = (ev: KeyboardEvent) => + { + Timeline.keyUp(editorState.ref.current, ev.key.toLowerCase()) + } + + const onRewind = (ev: Event) => + { + Timeline.rewind(editorState.ref.current) + editorState.commit() + render(true) + } + + const onRefresh = (ev: Event) => + { + Timeline.refreshTracks(editorState.ref.current) + editorState.commit() + render(true) + } + + const onReset = (ev: Event) => + { + const state = editorState.ref.current + Timeline.modeStackPop(state, 0) + Timeline.reset(state) + Timeline.rewind(state) + Timeline.refreshTracks(state) + editorState.commit() + render(true) + } + + refCanvasCurrent.addEventListener("mousedown", onMouseDown) + refCanvasCurrent.addEventListener("wheel", onMouseWheel) + refCanvasCurrent.addEventListener("contextmenu", preventDefault) + + window.addEventListener("mousemove", onMouseMove) + window.addEventListener("mouseup", onMouseUp) + window.addEventListener("keydown", onKeyDown) + window.addEventListener("keyup", onKeyUp) + + window.addEventListener("timelineRewind", onRewind) + window.addEventListener("timelineRefresh", onRefresh) + window.addEventListener("timelineReset", onReset) + + return () => + { + refCanvasCurrent.removeEventListener("mousedown", onMouseDown) + refCanvasCurrent.removeEventListener("wheel", onMouseWheel) + refCanvasCurrent.removeEventListener("contextmenu", preventDefault) + + window.removeEventListener("mousemove", onMouseMove) + window.removeEventListener("mouseup", onMouseUp) + window.removeEventListener("keydown", onKeyDown) + window.removeEventListener("keyup", onKeyUp) + + window.removeEventListener("timelineRewind", onRewind) + window.removeEventListener("timelineRefresh", onRefresh) + window.removeEventListener("timelineReset", onReset) + } + + }, [refCanvas.current]) + + + const yTrackEnd = + editorState.ref.current.tracks.length == 0 ? + 0 : + editorState.ref.current.tracks[editorState.ref.current.tracks.length - 1].renderRect.y2 + + const onAddTrack = () => + { + let proj = Project.global.project + proj = Project.upsertTrack(proj, Project.makeTrackNotes()) + Project.global.project = proj + Project.splitUndoPoint() + Project.addUndoPoint("addTrack") + Project.notifyObservers() + + window.dispatchEvent(new Event("timelineRefresh")) + } + + const onTrackSettings = (ev: React.MouseEvent, trackIndex: number) => + { + Popup.global.elem = () => + { + return + + + } + Popup.global.rect = Rect.fromElement(ev.target as HTMLElement) + Popup.notifyObservers() + } + + const pixelRatio = window.devicePixelRatio || 1 + + return React.useMemo(() => +
+ + +
+ + +{/*➕*/} + +
+
+ , [ + Project.global.project.tracks, + editorState.ref.current.tracks, + editorState.ref.current.trackScroll, + ]) +} + + +export function sendEventRefresh() +{ + window.dispatchEvent(new Event("timelineRefresh")) +} + + +export function sendEventReset() +{ + window.dispatchEvent(new Event("timelineReset")) +} \ No newline at end of file diff --git a/src_old/timeline/index.ts b/src_old/timeline/index.ts new file mode 100644 index 0000000..924bf5f --- /dev/null +++ b/src_old/timeline/index.ts @@ -0,0 +1,18 @@ +export * from "./state" +export * from "./state_keyDown" +export * from "./state_keyUp" +export * from "./state_mouseMove" +export * from "./state_mouseDown" +export * from "./state_mouseUp" +export * from "./state_mouseDrag" +export * from "./state_mouseWheel" +export * from "./state_render" +export * from "./track" +export * from "./trackKeyChanges" +export * from "./trackMeterChanges" +export * from "./trackNoteBlocks" +export * from "./trackChords" +export * from "./trackNotes" +export * from "./trackNoteVolumes" +export * from "./trackNoteVelocities" +export * from "./Element" \ No newline at end of file diff --git a/src/timeline/state.ts b/src_old/timeline/state.ts similarity index 100% rename from src/timeline/state.ts rename to src_old/timeline/state.ts diff --git a/src/timeline/state_keyDown.ts b/src_old/timeline/state_keyDown.ts similarity index 100% rename from src/timeline/state_keyDown.ts rename to src_old/timeline/state_keyDown.ts diff --git a/src/timeline/state_keyUp.ts b/src_old/timeline/state_keyUp.ts similarity index 100% rename from src/timeline/state_keyUp.ts rename to src_old/timeline/state_keyUp.ts diff --git a/src/timeline/state_mouseDown.ts b/src_old/timeline/state_mouseDown.ts similarity index 100% rename from src/timeline/state_mouseDown.ts rename to src_old/timeline/state_mouseDown.ts diff --git a/src/timeline/state_mouseDrag.ts b/src_old/timeline/state_mouseDrag.ts similarity index 100% rename from src/timeline/state_mouseDrag.ts rename to src_old/timeline/state_mouseDrag.ts diff --git a/src/timeline/state_mouseMove.ts b/src_old/timeline/state_mouseMove.ts similarity index 100% rename from src/timeline/state_mouseMove.ts rename to src_old/timeline/state_mouseMove.ts diff --git a/src/timeline/state_mouseUp.tsx b/src_old/timeline/state_mouseUp.tsx similarity index 100% rename from src/timeline/state_mouseUp.tsx rename to src_old/timeline/state_mouseUp.tsx diff --git a/src/timeline/state_mouseWheel.ts b/src_old/timeline/state_mouseWheel.ts similarity index 100% rename from src/timeline/state_mouseWheel.ts rename to src_old/timeline/state_mouseWheel.ts diff --git a/src/timeline/state_render.ts b/src_old/timeline/state_render.ts similarity index 100% rename from src/timeline/state_render.ts rename to src_old/timeline/state_render.ts diff --git a/src/timeline/track.tsx b/src_old/timeline/track.tsx similarity index 100% rename from src/timeline/track.tsx rename to src_old/timeline/track.tsx diff --git a/src/timeline/trackChords.tsx b/src_old/timeline/trackChords.tsx similarity index 100% rename from src/timeline/trackChords.tsx rename to src_old/timeline/trackChords.tsx diff --git a/src/timeline/trackKeyChanges.tsx b/src_old/timeline/trackKeyChanges.tsx similarity index 100% rename from src/timeline/trackKeyChanges.tsx rename to src_old/timeline/trackKeyChanges.tsx diff --git a/src/timeline/trackMeterChanges.tsx b/src_old/timeline/trackMeterChanges.tsx similarity index 100% rename from src/timeline/trackMeterChanges.tsx rename to src_old/timeline/trackMeterChanges.tsx diff --git a/src/timeline/trackNoteBlocks.tsx b/src_old/timeline/trackNoteBlocks.tsx similarity index 100% rename from src/timeline/trackNoteBlocks.tsx rename to src_old/timeline/trackNoteBlocks.tsx diff --git a/src/timeline/trackNoteVelocities.tsx b/src_old/timeline/trackNoteVelocities.tsx similarity index 100% rename from src/timeline/trackNoteVelocities.tsx rename to src_old/timeline/trackNoteVelocities.tsx diff --git a/src/timeline/trackNoteVolumes.tsx b/src_old/timeline/trackNoteVolumes.tsx similarity index 100% rename from src/timeline/trackNoteVolumes.tsx rename to src_old/timeline/trackNoteVolumes.tsx diff --git a/src/timeline/trackNotes.tsx b/src_old/timeline/trackNotes.tsx similarity index 100% rename from src/timeline/trackNotes.tsx rename to src_old/timeline/trackNotes.tsx diff --git a/src_old/toolbox/App.js b/src_old/toolbox/App.js deleted file mode 100644 index 8d2ba0e..0000000 --- a/src_old/toolbox/App.js +++ /dev/null @@ -1,256 +0,0 @@ -import React from "react" -import Editor from "../editor/editor.js" -import Project from "../project/project.js" -import { usePlaybackController } from "./PlaybackController.js" -import ToolboxPlayback from "./ToolboxPlayback.js" -import ToolboxFile from "./ToolboxFile.js" -import ToolboxEdit from "./ToolboxEdit.js" -import ToolboxInput from "./ToolboxInput.js" -import Ribbon from "./Ribbon.js" - - -function EditorComponent(props) -{ - const refCanvas = React.useRef() - const needsResetView = React.useRef(true) - - const transformMousePos = (canvas, ev) => - { - const rect = canvas.getBoundingClientRect() - return { - x: (ev.clientX - rect.left), - y: (ev.clientY - rect.top) - } - } - - React.useEffect(() => - { - const preventDefault = (ev) => ev.preventDefault() - - const onResize = (ev) => - { - const rect = refCanvas.current.getBoundingClientRect() - refCanvas.current.width = rect.width - refCanvas.current.height = rect.height - - props.dispatch({ - type: "resize", - w: rect.width, - h: rect.height, - }) - - if (needsResetView.current) - { - needsResetView.current = false - props.dispatch({ type: "rewind" }) - } - } - - const onMouseMove = (ev) => - { - const p = transformMousePos(refCanvas.current, ev) - props.dispatch({ type: "mouseMove", ...p }) - } - - const onMouseDown = (ev) => - { - if (window.document.activeElement) - window.document.activeElement.blur() - - ev.preventDefault() - const p = transformMousePos(refCanvas.current, ev) - props.dispatch({ type: "mouseMove", ...p }) - props.dispatch({ type: "mouseDown", rightButton: ev.button != 0, ctrlKey: ev.ctrlKey }) - } - - const onMouseUp = (ev) => - { - const p = transformMousePos(refCanvas.current, ev) - props.dispatch({ type: "mouseMove", ...p }) - props.dispatch({ type: "mouseUp" }) - props.dispatch({ type: "mouseMove", ...p }) - } - - const onMouseWheel = (ev) => - { - ev.preventDefault() - props.dispatch({ type: "mouseWheel", deltaX: ev.deltaX, deltaY: ev.deltaY }) - } - - const onKeyDown = (ev) => - { - props.dispatch({ type: "keyDown", key: ev.key.toLowerCase() }) - props.dispatch({ - type: "keyCommand", - ev, - key: ev.key.toLowerCase(), - ctrlKey: ev.ctrlKey, - shiftKey: ev.shiftKey, - }) - } - - const onKeyUp = (ev) => - { - props.dispatch({ type: "keyUp", key: ev.key.toLowerCase() }) - } - - onResize() - - window.addEventListener("resize", onResize) - window.addEventListener("mousemove", onMouseMove) - window.addEventListener("mouseup", onMouseUp) - window.addEventListener("keydown", onKeyDown) - window.addEventListener("keyup", onKeyUp) - - refCanvas.current.addEventListener("mousedown", onMouseDown) - refCanvas.current.addEventListener("wheel", onMouseWheel) - refCanvas.current.addEventListener("mouseup", preventDefault) - refCanvas.current.addEventListener("contextmenu", preventDefault) - - return () => - { - window.removeEventListener("resize", onResize) - window.removeEventListener("mousemove", onMouseMove) - window.removeEventListener("mouseup", onMouseUp) - window.removeEventListener("keydown", onKeyDown) - window.removeEventListener("keyup", onKeyUp) - - refCanvas.current.removeEventListener("mousedown", onMouseDown) - refCanvas.current.removeEventListener("wheel", onMouseWheel) - refCanvas.current.removeEventListener("mouseup", preventDefault) - refCanvas.current.removeEventListener("contextmenu", preventDefault) - } - - }, []) - - React.useEffect(() => - { - Editor.render(props.state, refCanvas.current.getContext("2d")) - - const mouseAction = props.state.mouse.action || (props.state.mouse.hover && props.state.mouse.hover.action) - - if (props.state.mouse.draw || mouseAction & (Editor.actionDraw)) - refCanvas.current.style.cursor = "crosshair" - else if (mouseAction & (Editor.actionDragTime)) - refCanvas.current.style.cursor = (props.state.mouse.down ? "grabbing" : "grab") - else if (mouseAction & (Editor.actionPan)) - refCanvas.current.style.cursor = "move" - else if (mouseAction & (Editor.actionStretchTimeStart | Editor.actionStretchTimeEnd)) - refCanvas.current.style.cursor = "col-resize" - else - refCanvas.current.style.cursor = "text" - - }, [props.state]) - - return ( -
- -
- ) -} - - -export default function App(props) -{ - const initEditor = (state) => - { - state = Editor.reduce(state, { type: "init", project: Project.getDefault() }) - state = Editor.reduce(state, { type: "trackAdd", kind: "markers" }) - state = Editor.reduce(state, { type: "trackAdd", kind: "chords" }) - state = Editor.reduce(state, { type: "trackAdd", kind: "notes" }) - state = Editor.reduce(state, { type: "clearUndoStack" }) - - const urlData = - getQueryParam(window.location.search, "data") || - getQueryParam(window.location.search, "song") - - if (urlData) - { - try - { - state = Editor.reduce(state, { type: "projectLoad", project: Project.fromCompressedStr(urlData) }) - } - catch (e) - { - window.alert("An error occurred loading the project from the URL.\n\n" + e.toString()) - console.error(e) - } - } - - return state - } - - const [state, dispatch] = React.useReducer(Editor.reduce, {}, initEditor) - - const playbackController = usePlaybackController(state, dispatch, props.synth) - - return ( -
-
- - { ToolboxPlayback({ state, dispatch, playbackController }) } - - - - { ToolboxFile({ state, dispatch }) } - { ToolboxEdit({ state, dispatch }) } - { ToolboxInput({ state, dispatch }) } - - - {/* - - */} -
- - -
- ) -} - - -function getQueryParam(url, name) -{ - name = name.replace(/[\[\]]/g, "\\$&") - - const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)") - const results = regex.exec(url) - - if (!results) - return null - - if (!results[2]) - return "" - - return decodeURIComponent(results[2].replace(/\+/g, " ")) -} \ No newline at end of file diff --git a/src_old/toolbox/ButtonList.tsx b/src_old/toolbox/ButtonList.tsx deleted file mode 100644 index c3952e3..0000000 --- a/src_old/toolbox/ButtonList.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from "react" - - -interface ButtonListProps -{ - column?: boolean - multiple?: boolean - selected: any - - items: - { - label: any - value: any - bkgColor?: string - width?: string - }[] - - onChange: (item: any, index: number) => void -} - - -export default function ButtonList(props: ButtonListProps) -{ - const isSelected = (value: any) => - { - if (props.multiple && Array.isArray(props.selected)) - return props.selected.some(v => v == value) - else - return props.selected == value - } - - - return
- - { props.items.map((item, i) => - - )} - -
-} \ No newline at end of file diff --git a/src_old/toolbox/DropdownMenu.tsx b/src_old/toolbox/DropdownMenu.tsx deleted file mode 100644 index a4ae761..0000000 --- a/src_old/toolbox/DropdownMenu.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from "react" - - -interface DropdownMenuProps -{ - selected: any - - items: - { - label: any - value: any - }[] - - onChange: (item: any, index: number) => void - - style?: any -} - - -export default function DropdownMenu(props: DropdownMenuProps) -{ - //const selectedIndex = props.items.findIndex(i => i.value == props.selected) - - - return -} \ No newline at end of file diff --git a/src_old/toolbox/ListBox.tsx b/src_old/toolbox/ListBox.tsx deleted file mode 100644 index e95794a..0000000 --- a/src_old/toolbox/ListBox.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from "react" - - -interface ListBoxProps -{ - selected: any - - items: - { - label: any - value: any - }[] - - onChange: (item: any, index: number) => void - - style?: any -} - - -export default function ListBox(props: ListBoxProps) -{ - //const selectedIndex = props.items.findIndex(i => i.value == props.selected) - - - return
-
- - { props.items.map((item, i) => - - )} - -
-
-} \ No newline at end of file diff --git a/src_old/toolbox/PlaybackController.js b/src_old/toolbox/PlaybackController.js deleted file mode 100644 index 68cd238..0000000 --- a/src_old/toolbox/PlaybackController.js +++ /dev/null @@ -1,129 +0,0 @@ -import React from "react" -import PlaybackSynth from "../project/playbackSynth.js" -import Rational from "../util/rational.js" -import MathUtils from "../util/math.js" - - -export function usePlaybackController(state, dispatch, synth) -{ - const stateRef = React.useRef(state) - stateRef.current = state - - const playbackPrevStart = React.useRef(null) - const playbackPrevTimestamp = React.useRef(-1) - const playbackInterval = React.useRef(null) - - const sampleInterval = React.useRef(null) - const samplePrevTimestamp = React.useRef(null) - - function cancelPlaybackInterval() - { - if (playbackInterval.current) - { - window.cancelAnimationFrame(playbackInterval.current) - playbackInterval.current = null - } - } - - function cancelSampleInterval() - { - if (sampleInterval.current) - { - window.cancelAnimationFrame(sampleInterval.current) - sampleInterval.current = null - } - } - - function playbackStep(timestamp) - { - const deltaTime = (playbackPrevTimestamp.current < 0 ? 0 : timestamp - playbackPrevTimestamp.current) - playbackPrevTimestamp.current = timestamp - - if (deltaTime > 0 && deltaTime < 250) - { - const measuresPerSecond = (stateRef.current.project.baseBpm / 4 / 60) - - synth.process(deltaTime / 1000) - const timeAsFloat = stateRef.current.playback.timeAsFloat + deltaTime / 1000 * measuresPerSecond - dispatch({ type: "playbackStep", timeAsFloat }) - } - - if (stateRef.current.playback.time.compare(stateRef.current.project.range.end.add(new Rational(1, 4))) > 0) - dispatch({ type: "playbackSet", playing: false }) - - if (stateRef.current.playback.playing) - playbackInterval.current = window.requestAnimationFrame(playbackStep) - } - - function playbackSet(playing) - { - const time = state.cursor.time1.min(state.cursor.time2) - dispatch({ type: "playbackSet", playing, time }) - } - - function sampleStep(timestamp) - { - const deltaTime = (samplePrevTimestamp.current < 0 ? 0 : timestamp - samplePrevTimestamp.current) - samplePrevTimestamp.current = timestamp - - if (deltaTime > 0 && deltaTime < 250) - synth.process(deltaTime / 1000) - - if (!synth.isFinished()) - sampleInterval.current = window.requestAnimationFrame(sampleStep) - } - - if (state.playback.playing) - { - if (!playbackPrevStart.current || state.playback.startTime.compare(playbackPrevStart.current) != 0) - { - cancelSampleInterval() - cancelPlaybackInterval() - synth.stopAll() - - PlaybackSynth.feedToSynth(state.project, synth, state.playback.startTime) - synth.play() - - playbackPrevStart.current = state.playback.startTime - playbackPrevTimestamp.current = -1 - playbackInterval.current = window.requestAnimationFrame(playbackStep) - } - } - else - { - if (playbackInterval.current) - { - cancelPlaybackInterval() - synth.stopAll() - playbackPrevStart.current = null - } - } - - if (state.soundPreview) - { - cancelSampleInterval() - synth.stopAll() - - if (state.soundPreview.kind == "note") - synth.addNoteEvent(0, 0, MathUtils.midiToHertz(state.soundPreview.pitch), 1, 0.5) - else if (state.soundPreview.kind == "chord") - { - for (const pitch of state.soundPreview.chord.strummingPitches) - synth.addNoteEvent(0, 0, MathUtils.midiToHertz(pitch), 1, 0.75) - } - - synth.play() - samplePrevTimestamp.current = -1 - sampleInterval.current = window.requestAnimationFrame(sampleStep) - dispatch({ type: "clearSoundPreview" }) - } - - const togglePlaying = () => - { - playbackSet(!state.playback.playing) - } - - return { - togglePlaying, - } -} \ No newline at end of file diff --git a/src_old/toolbox/Ribbon.js b/src_old/toolbox/Ribbon.js deleted file mode 100644 index 5c77e91..0000000 --- a/src_old/toolbox/Ribbon.js +++ /dev/null @@ -1,400 +0,0 @@ -import React from "react" - - -function Tab(props) -{ - return null -} - - -function FixedTab(props) -{ - return null -} - - -function Group(props) -{ - return null -} - - -function CustomGroup(props) -{ - return null -} - - -function TallBox(props) -{ - return null -} - - -function SlotLayout(props) -{ - if (props.tall) - { - const iconSize = (typeof props.icon === "string" ? "3em" : undefined) - - return <> -
-
{ props.icon }
-
{ props.label }
-
- - } - else - { - const iconSlotWidth = (props.icon ? "auto" : "0") - const sepSlotWidth = (props.icon ? "0.25em" : "0") - const iconSize = (typeof props.icon === "string" ? "1.15em" : undefined) - - return <> -
-
{ props.icon }
-
{ props.label }
-
- - } -} - - -function Slot(props) -{ - const baseStyle = - { - borderRadius: "0.5em", - boxSizing: "border-box", - } - - const className = - (props.supressClass ? "" : "ribbonSlot") + " " + - (props.selected ? "ribbonButtonSelected" : "") - - if (props.tall) - { - return <> -
- { props.children } -
- - } - else - { - return <> -
- { props.children } -
- - } -} - - -function Button(props) -{ - const className = - "ribbonButton " + - (props.selected ? "ribbonButtonSelected" : "") - - return <> - - -} - - -function SlotButton(props) -{ - return <> - - - - -} - - -function SlotRadioGroup(props) -{ - return <> - { props.options.map(option => - props.onChange(option.value) } - style={{ - height: "100%", - padding: "0.25em 0.5em", - margin: "0 0.125em", - }} - label={ option.render } - /> - )} - -} - - -function InlineRadioGroup(props) -{ - return <> - { props.options.map(option => - - )} - -} - - -function Input(props) -{ - return ev.stopPropagation() } - style={{ - gridRow: 1, - gridColumn: 1, - width: "4em", - border: 0, - fontFamily: "inherit", - textAlign: "left", - padding: "0.25em", - borderRadius: "0.25em", - ...props.style, - }}/> -} - - -function Select(props) -{ - return -} - - -function getChildren(children) -{ - if (!children) - children = [] - - else if (!Array.isArray(children)) - children = [children] - - return children.filter(c => !!c) -} - - -function flattenChildrenOfType(children, type) -{ - const res = [] - - for (const c of getChildren(children)) - { - if (c.type === type) - res.push(c) - else if (c.props && c.props.children) - { - for (const c2 of flattenChildrenOfType(c.props.children, type)) - res.push(c2) - } - } - - return res -} - - -function Toolbar(props) -{ - const [curTab, setCurTab] = React.useState(0) - - //console.log(props.children) - const tabs = flattenChildrenOfType(props.children, Tab) - const fixedTab = flattenChildrenOfType(props.children, FixedTab) - const groups = flattenChildrenOfType(tabs[curTab].props.children, Group) - const fixedGroups = []//fixedTab.length == 0 ? [] : flattenChildrenOfType(fixedTab[0].props.children, Group) - - return <> -
-
- { tabs.map((tab, i) => - - )} -
- -
- { [...fixedGroups, ...groups].map((group, i) => - -
-
- { group.props.children } -
-
-
- { group.props.label } -
-
- )} -
-
- -} - - -export default -{ - Toolbar, - Tab, - FixedTab, - Group, - TallBox, - Slot, - SlotLayout, - Button, - SlotButton, - InlineRadioGroup, - SlotRadioGroup, - Input, - Select, -} \ No newline at end of file diff --git a/src_old/toolbox/ToolboxChord.js b/src_old/toolbox/ToolboxChord.js deleted file mode 100644 index eaa88c0..0000000 --- a/src_old/toolbox/ToolboxChord.js +++ /dev/null @@ -1,178 +0,0 @@ -import React from "react" -import Editor from "../editor/editor.js" -import CanvasUtils from "../util/canvas.js" -import Theory from "../theory.ts" -import Ribbon from "./Ribbon.js" - - -function ChordButton(props) -{ - const refCanvas = React.useRef(null) - - const w = 60 - const h = 54 - - const key = props.theoryKey - const chord = props.chord - - React.useEffect(() => - { - const ctx = refCanvas.current.getContext("2d") - CanvasUtils.drawChord(ctx, 0, 0, w, h, chord, key) - - }, [props.theoryKey, props.chord]) - - const onClick = () => - { - const time = Editor.insertionTime(props.state) - props.dispatch({ type: "insertChord", chord, time }) - } - - return } - label="" - /> -} - - -function KindDropdown(props) -{ - return props.onChange(ev.target.value) } - style={{ height:"2em" }} - > - - { Theory.Chord.kinds.map((chord, index) => - - { chord.startGroup ? : null } - - - )} - - -} - - -export default function ToolboxChord(props) -{ - const state = props.state - const dispatch = props.dispatch - const [kindGroup, setKindGroup] = React.useState("inkey") - const [inKeyType, setInKeyType] = React.useState(5) - const [kindCustomId, setKindCustomId] = React.useState("M") - const [accidental, setAccidental] = React.useState(0) - - const time = Editor.insertionTime(props.state) - const cursorKeyCh = state.project.keyChanges.findActiveAt(time) - const key = cursorKeyCh ? cursorKeyCh.key : Editor.defaultKey() - - let title = null - let chordButtons = null - switch (kindGroup) - { - case "inkey": - switch (inKeyType) - { - case 5: title = "Triads in " + key.str; break - case 7: title = "Seventh chords in " + key.str; break - case 9: title = "Ninth chords in " + key.str; break - case 11: title = "Eleventh chords in " + key.str; break - case 13: title = "Thirteenth chords in " + key.str; break - } - - chordButtons = [0, 1, 2, 3, 4, 5, 6].map(degree => - { - const root = key.midiForDegree(degree) - - let pitches = [0] - pitches.push(key.midiForDegree(degree + 2) - root) - pitches.push(key.midiForDegree(degree + 4) - root) - - if (inKeyType >= 7) - pitches.push(key.midiForDegree(degree + 6) - root) - - if (inKeyType >= 9) - pitches.push(key.midiForDegree(degree + 8) - root) - - if (inKeyType >= 11) - pitches.push(key.midiForDegree(degree + 10) - root) - - if (inKeyType >= 13) - pitches.push(key.midiForDegree(degree + 12) - root) - - const kind = Theory.Chord.kindFromPitches(pitches) - const chord = new Theory.Chord(root, accidental, kind, 0, []) - - return - }) - break - - case "custom": - title = Theory.Chord.kinds[Theory.Chord.kindFromId(kindCustomId)].name + " chords" - chordButtons = [0, 1, 2, 3, 4, 5, 6].map(degree => { - const kind = Theory.Chord.kindFromId(kindCustomId) - const root = key.midiForDegree(degree) - const chord = new Theory.Chord(root, accidental, kind, 0, []) - - return - }) - break - } - - return <> - - { chordButtons } - - - - setKindGroup("inkey") }> - In-Key } - label={ - } - /> - - - setKindGroup("custom") } - > - Other } - label={ } - /> - - - - - - - - -} \ No newline at end of file diff --git a/src_old/toolbox/ToolboxEdit.js b/src_old/toolbox/ToolboxEdit.js deleted file mode 100644 index dd2f9b7..0000000 --- a/src_old/toolbox/ToolboxEdit.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from "react" -import Ribbon from "./Ribbon.js" -import Editor from "../editor/editor.js" - - -export default function ToolboxEdit(props) -{ - return - - - props.dispatch({ type: "undo" })} - /> - props.dispatch({ type: "redo" })} - /> - - - - props.dispatch({ type: "paste" })} - /> - props.dispatch({ type: "copy" })} - /> - props.dispatch({ type: "cut" })} - /> - - - -} \ No newline at end of file diff --git a/src_old/toolbox/ToolboxFile.js b/src_old/toolbox/ToolboxFile.js deleted file mode 100644 index fe1b4cb..0000000 --- a/src_old/toolbox/ToolboxFile.js +++ /dev/null @@ -1,180 +0,0 @@ -import React from "react" -import Ribbon from "./Ribbon.js" -import Project from "../project/project.js" - - -function loadFile(dispatch, elem) -{ - if (elem.files.length != 1) - return - - let reader = new FileReader() - reader.readAsArrayBuffer(elem.files[0]) - reader.onload = () => - { - const bytes = new Uint8Array(reader.result) - if (bytes[0] == "M".charCodeAt(0) && - bytes[1] == "T".charCodeAt(0) && - bytes[2] == "h".charCodeAt(0) && - bytes[3] == "d".charCodeAt(0)) - loadFileMidi(dispatch, elem) - else - loadFileJson(dispatch, elem) - } -} - - -function loadFileJson(dispatch, elem) -{ - if (elem.files.length != 1) - return - - let reader = new FileReader() - reader.readAsText(elem.files[0]) - reader.onload = () => - { - const json = JSON.parse(reader.result) - dispatch({ type: "projectLoad", project: Project.fromJson(json) }) - } -} - - -function loadFileMidi(dispatch, elem) -{ - if (elem.files.length != 1) - return - - let reader = new FileReader() - reader.readAsArrayBuffer(elem.files[0]) - reader.onload = () => - { - const bytes = new Uint8Array(reader.result) - dispatch({ type: "projectLoad", project: Project.fromMidi(bytes) }) - } -} - - -function loadJson(dispatch) -{ - const json = window.prompt("Paste JSON song data:", "") - if (json) - dispatch({ type: "projectLoad", project: Project.fromJson(JSON.parse(json)) }) -} - - -function saveJson(project) -{ - const json = project.toJson() - const newWindow = window.open() - newWindow.document.write("") - newWindow.document.write(json) - newWindow.document.write("") -} - - -function saveAndSetUrl(project) -{ - const str = project.toCompressedStr() - window.location = location.protocol + "//" + location.host + location.pathname + "?data=" + str -} - - -export default function ToolboxFile(props) -{ - const state = props.state - - const inputFileRef = React.useRef(null) - const performInputFileClick = () => inputFileRef.current.click() - - const newProject = () => - { - props.dispatch({ type: "projectSet", project: Project.getDefault() }) - props.dispatch({ type: "rewind" }) - } - - const examples = - [ - "mozartk453", - "letitgo", - "rollercoaster", - "adventure", - "isaac", - "dontstarve", - "chronotrigger", - "jimmyneutron", - "whensomebodylovedme", - ] - - const loadExample = (filename) => - { - if (filename == "") - return - - fetch("examples/" + filename + ".json") - .then(res => res.json()) - .then(json => - { - props.dispatch({ type: "projectLoad", project: Project.fromJson(json) }) - }) - } - - return - - - - - - - saveAndSetUrl(state.project) } - /> - - - - saveJson(state.project) } - /> - loadJson(props.dispatch) } - /> - - loadFile(props.dispatch, ev.target) } - style={{ display:"none", width:"1em" }} - /> - - - - - - loadExample(ev.target.value) }> - - { examples.map(ex => ) } - - } - /> - - - - -} \ No newline at end of file diff --git a/src_old/toolbox/ToolboxInput.js b/src_old/toolbox/ToolboxInput.js deleted file mode 100644 index 34afe71..0000000 --- a/src_old/toolbox/ToolboxInput.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react" -import Editor from "../editor/editor.js" -import Ribbon from "./Ribbon.js" -import ToolboxNote from "./ToolboxNote.js" -import ToolboxChord from "./ToolboxChord.js" -import ToolboxMarkers from "./ToolboxMarkers.js" - - -export default function ToolboxInput(props) -{ - const state = props.state - const dispatch = props.dispatch - - const track = Editor.insertionTrack(state) - const trackKind = state.tracks[track].kind - - const toolboxNote = ToolboxNote({ state, dispatch }) - const toolboxChord = ToolboxChord({ state, dispatch }) - const toolboxMarkers = ToolboxMarkers({ state, dispatch }) - - return - { trackKind != "notes" ? null : toolboxNote } - { trackKind != "chords" ? null : toolboxChord } - { trackKind != "markers" ? null : toolboxMarkers } - -} \ No newline at end of file diff --git a/src_old/toolbox/ToolboxMarkers.js b/src_old/toolbox/ToolboxMarkers.js deleted file mode 100644 index 1a530c8..0000000 --- a/src_old/toolbox/ToolboxMarkers.js +++ /dev/null @@ -1,170 +0,0 @@ -import React from "react" -import Editor from "../editor/editor.js" -import Theory from "../theory.ts" -import Ribbon from "./Ribbon.js" - - -function KeyChangeToolbox(props) -{ - const state = props.state - const dispatch = props.dispatch - - const time = Editor.insertionTime(props.state) - const cursorKeyCh = state.project.keyChanges.findActiveAt(time) - const key = cursorKeyCh ? cursorKeyCh.key : Editor.defaultKey() - - const accidentalNames = ["♭♭", "♭", "♮", "♯", "♯♯"] - - const setTonicLetter = (letter) => - { - if (!cursorKeyCh) - return - - const newKey = new Theory.Key(new Theory.PitchName(letter, key.tonic.accidental), key.scale) - const newKeyCh = cursorKeyCh.withChanges({ key: newKey }) - const project = state.project.upsertKeyChange(newKeyCh) - dispatch({ type: "projectSet", project }) - } - - const setTonicAccidental = (acc) => - { - if (!cursorKeyCh) - return - - const newKey = new Theory.Key(new Theory.PitchName(key.tonic.letter, acc), key.scale) - const newKeyCh = cursorKeyCh.withChanges({ key: newKey }) - const project = state.project.upsertKeyChange(newKeyCh) - dispatch({ type: "projectSet", project }) - } - - const setScale = (scale) => - { - if (!cursorKeyCh) - return - - const newKey = new Theory.Key(key.tonic, scale) - const newKeyCh = cursorKeyCh.withChanges({ key: newKey }) - const project = state.project.upsertKeyChange(newKeyCh) - dispatch({ type: "projectSet", project }) - } - - return - - - setTonicLetter(parseInt(ev.target.value)) } - > - { [0, 1, 2, 3, 4, 5, 6].map(i => ) } - - setTonicAccidental(parseInt(ev.target.value)) } - > - { [2, 1, 0, -1, -2].map(acc => ) } - - setScale(Theory.Scale.fromId(ev.target.value)) } - > - { Theory.Scale.list.map(scale => ) } - - } - /> - - -} - - -function MeterChangeToolbox(props) -{ - const state = props.state - const dispatch = props.dispatch - - const time = Editor.insertionTime(props.state) - const cursorMeterCh = state.project.meterChanges.findActiveAt(time) - const meter = cursorMeterCh ? cursorMeterCh.meter : Editor.defaultMeter() - - const meterDenominators = [1, 2, 4, 8, 16, 32, 64] - - const setNumerator = (numerator) => - { - if (!cursorMeterCh) - return - - const newMeter = new Theory.Meter(numerator, meter.denominator) - const newMeterCh = cursorMeterCh.withChanges({ meter: newMeter }) - const project = state.project.upsertMeterChange(newMeterCh) - dispatch({ type: "projectSet", project }) - } - - const setDenominator = (denominator) => - { - if (!cursorMeterCh) - return - - const newMeter = new Theory.Meter(meter.numerator, denominator) - const newMeterCh = cursorMeterCh.withChanges({ meter: newMeter }) - const project = state.project.upsertMeterChange(newMeterCh) - dispatch({ type: "projectSet", project }) - } - - return - - - setNumerator(Math.max(1, Math.min(64, ev.target.value))) } - onKeyDown={ ev => ev.stopPropagation() } - min="1" max="64" - style={{ width:"3em" }} - /> - { " / " } - setDenominator(parseInt(ev.target.value)) } - style={{ height:"2em" }} - > - { meterDenominators.map(d => ) } - - } - /> - - -} - - -export default function ToolboxMarkers(props) -{ - const state = props.state - const dispatch = props.dispatch - - const time = Editor.insertionTime(props.state) - const insertKeyChange = () => dispatch({ type: "insertKeyChange", time, key: Editor.defaultKey() }) - const insertMeterChange = () => dispatch({ type: "insertMeterChange", time, meter: Editor.defaultMeter() }) - - const insertIconStyle = - { - fontWeight: "bold", - } - - return <> - - + } - label="Key Change" - /> - + } - label="Meter Change" - /> - - - { KeyChangeToolbox({ state, dispatch }) } - { MeterChangeToolbox({ state, dispatch }) } - -} \ No newline at end of file diff --git a/src_old/toolbox/ToolboxNote.js b/src_old/toolbox/ToolboxNote.js deleted file mode 100644 index 6db0967..0000000 --- a/src_old/toolbox/ToolboxNote.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from "react" -import Ribbon from "./Ribbon.js" -import Editor from "../editor/editor.js" -import CanvasUtils from "../util/canvas.js" - - -function NoteButton(props) -{ - const refCanvas = React.useRef(null) - - const w = 30 - const h = 50 - - const key = props.theoryKey - const midi = props.noteIndex + key.tonic.midi - const degree = key.degreeForMidi(midi) + key.scale.metadata.mode - - React.useEffect(() => - { - const ctx = refCanvas.current.getContext("2d") - ctx.fillStyle = CanvasUtils.fillStyleForDegree(ctx, degree) - ctx.fillRect(0, 0, w, h) - - }, [props.theoryKey, props.noteIndex]) - - const onClick = () => - { - const time = Editor.insertionTime(props.state) - props.dispatch({ type: "insertNote", chroma: midi, time }) - } - - return } - label={ key.nameForMidi(midi).str } - /> -} - - -export default function ToolboxNote(props) -{ - const state = props.state - const dispatch = props.dispatch - - const time = Editor.insertionTime(props.state) - const cursorKeyCh = state.project.keyChanges.findActiveAt(time) - const key = cursorKeyCh ? cursorKeyCh.key : Editor.defaultKey() - - return - { [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(noteIndex => - - )} - -} \ No newline at end of file diff --git a/src_old/toolbox/ToolboxPlayback.js b/src_old/toolbox/ToolboxPlayback.js deleted file mode 100644 index 9647698..0000000 --- a/src_old/toolbox/ToolboxPlayback.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react" -import Ribbon from "./Ribbon.js" - - -export default function ToolboxPlayback(props) -{ - const state = props.state - const playbackController = props.playbackController - - const rewind = () => - { - props.dispatch({ type: "rewind" }) - } - - const setBaseBpm = (value) => - { - props.dispatch({ type: "set", state: { project: state.project.withChanges({ baseBpm: value }) } }) - } - - return - - - - - Base BPM} - label={ setBaseBpm(ev.target.value) } - />} - /> - - - -} \ No newline at end of file diff --git a/src/types.ts b/src_old/types.ts similarity index 100% rename from src/types.ts rename to src_old/types.ts diff --git a/src/ui/Button.tsx b/src_old/ui/Button.tsx similarity index 100% rename from src/ui/Button.tsx rename to src_old/ui/Button.tsx diff --git a/src/ui/ButtonList.tsx b/src_old/ui/ButtonList.tsx similarity index 100% rename from src/ui/ButtonList.tsx rename to src_old/ui/ButtonList.tsx diff --git a/src/ui/ChordButton.tsx b/src_old/ui/ChordButton.tsx similarity index 100% rename from src/ui/ChordButton.tsx rename to src_old/ui/ChordButton.tsx diff --git a/src/ui/Dial.tsx b/src_old/ui/Dial.tsx similarity index 100% rename from src/ui/Dial.tsx rename to src_old/ui/Dial.tsx diff --git a/src/ui/DropdownMenu.tsx b/src_old/ui/DropdownMenu.tsx similarity index 100% rename from src/ui/DropdownMenu.tsx rename to src_old/ui/DropdownMenu.tsx diff --git a/src/ui/Input.tsx b/src_old/ui/Input.tsx similarity index 100% rename from src/ui/Input.tsx rename to src_old/ui/Input.tsx diff --git a/src/ui/ListBox.tsx b/src_old/ui/ListBox.tsx similarity index 100% rename from src/ui/ListBox.tsx rename to src_old/ui/ListBox.tsx diff --git a/src/ui/LoadingBar.tsx b/src_old/ui/LoadingBar.tsx similarity index 100% rename from src/ui/LoadingBar.tsx rename to src_old/ui/LoadingBar.tsx diff --git a/src_old/ui/index.ts b/src_old/ui/index.ts new file mode 100644 index 0000000..7a475af --- /dev/null +++ b/src_old/ui/index.ts @@ -0,0 +1,8 @@ +export * from "./Button" +export * from "./ButtonList" +export * from "./ChordButton" +export * from "./Input" +export * from "./ListBox" +export * from "./Dial" +export * from "./DropdownMenu" +export * from "./LoadingBar" \ No newline at end of file diff --git a/src/util/async.ts b/src_old/util/async.ts similarity index 100% rename from src/util/async.ts rename to src_old/util/async.ts diff --git a/src/util/binaryReader.ts b/src_old/util/binaryReader.ts similarity index 100% rename from src/util/binaryReader.ts rename to src_old/util/binaryReader.ts diff --git a/src/util/binaryWriter.ts b/src_old/util/binaryWriter.ts similarity index 100% rename from src/util/binaryWriter.ts rename to src_old/util/binaryWriter.ts diff --git a/src/util/canvasUtils.ts b/src_old/util/canvasUtils.ts similarity index 100% rename from src/util/canvasUtils.ts rename to src_old/util/canvasUtils.ts diff --git a/src/util/globalObservable.ts b/src_old/util/globalObservable.ts similarity index 100% rename from src/util/globalObservable.ts rename to src_old/util/globalObservable.ts diff --git a/src/util/misc.ts b/src_old/util/misc.ts similarity index 100% rename from src/util/misc.ts rename to src_old/util/misc.ts diff --git a/src/util/refState.ts b/src_old/util/refState.ts similarity index 100% rename from src/util/refState.ts rename to src_old/util/refState.ts diff --git a/src/util/wavEncode.ts b/src_old/util/wavEncode.ts similarity index 100% rename from src/util/wavEncode.ts rename to src_old/util/wavEncode.ts diff --git a/src/windows/Inspector.tsx b/src_old/windows/Inspector.tsx similarity index 100% rename from src/windows/Inspector.tsx rename to src_old/windows/Inspector.tsx diff --git a/src/windows/InspectorChord.tsx b/src_old/windows/InspectorChord.tsx similarity index 100% rename from src/windows/InspectorChord.tsx rename to src_old/windows/InspectorChord.tsx diff --git a/src/windows/InspectorKeyChange.tsx b/src_old/windows/InspectorKeyChange.tsx similarity index 100% rename from src/windows/InspectorKeyChange.tsx rename to src_old/windows/InspectorKeyChange.tsx diff --git a/src/windows/InspectorMeterChange.tsx b/src_old/windows/InspectorMeterChange.tsx similarity index 100% rename from src/windows/InspectorMeterChange.tsx rename to src_old/windows/InspectorMeterChange.tsx diff --git a/src/windows/InspectorMultitype.tsx b/src_old/windows/InspectorMultitype.tsx similarity index 100% rename from src/windows/InspectorMultitype.tsx rename to src_old/windows/InspectorMultitype.tsx diff --git a/src/windows/InspectorTrack.tsx b/src_old/windows/InspectorTrack.tsx similarity index 100% rename from src/windows/InspectorTrack.tsx rename to src_old/windows/InspectorTrack.tsx diff --git a/src/windows/InstrumentSelect.tsx b/src_old/windows/InstrumentSelect.tsx similarity index 100% rename from src/windows/InstrumentSelect.tsx rename to src_old/windows/InstrumentSelect.tsx diff --git a/src/windows/Render.tsx b/src_old/windows/Render.tsx similarity index 100% rename from src/windows/Render.tsx rename to src_old/windows/Render.tsx diff --git a/src/windows/Test.tsx b/src_old/windows/Test.tsx similarity index 100% rename from src/windows/Test.tsx rename to src_old/windows/Test.tsx diff --git a/src/windows/Timeline.tsx b/src_old/windows/Timeline.tsx similarity index 100% rename from src/windows/Timeline.tsx rename to src_old/windows/Timeline.tsx diff --git a/src/windows/index.ts b/src_old/windows/index.ts similarity index 100% rename from src/windows/index.ts rename to src_old/windows/index.ts diff --git a/tsconfig.json b/tsconfig.json index 01c2a6b..bd2e131 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,22 @@ { "compilerOptions": { - "sourceMap": true, - "target": "esnext", - "module": "esnext", - "moduleResolution": "node", - "allowSyntheticDefaultImports": true, - "strict": true, - "jsx": "react" + "baseUrl": "./src/", + "jsx": "preserve", + "jsxImportSource": "solid-js", + "module": "esnext", + "moduleResolution": "bundler", + "target": "es2022", + "allowImportingTsExtensions": true, + "noEmit": true, + "strictNullChecks": true, + "noImplicitAny": true }, - "exclude": ["src_old"] + "include": [ + "./src/*.ts", + "./src/*.tsx" + ], + "exclude": [ + "src_old", + "src_old2" + ] } \ No newline at end of file diff --git a/watch.bat b/watch.bat deleted file mode 100644 index 7946708..0000000 --- a/watch.bat +++ /dev/null @@ -1 +0,0 @@ -npm run watch \ No newline at end of file diff --git a/webpack.config.cjs b/webpack.config.cjs index 7ec9698..6cd31f6 100644 --- a/webpack.config.cjs +++ b/webpack.config.cjs @@ -1,53 +1,43 @@ const path = require("path") -module.exports = -{ - mode: "production", - entry: - { - main: path.resolve(__dirname, "src/main.js"), +module.exports = { + mode: "production", + devtool: "source-map", + + entry: { + main: path.resolve(__dirname, "src/main.tsx"), }, - output: - { + output: { filename: "[name].js", - path: path.resolve(__dirname, "build") + path: path.resolve(__dirname, ".build"), + publicPath: "/.build/", }, - - resolve: { - extensions: [".ts", ".tsx", ".js", ".json"], + + resolve: { + extensions: [".ts", ".tsx"], fallback: { "assert": false, - /*"zlib": require.resolve("browserify-zlib"), - "util": require.resolve("util/"), - "buffer": require.resolve("buffer/"), - "stream": require.resolve("stream-browserify"),*/ }, - }, - - module: - { - rules: - [ - { - test: /\.tsx?$/, - exclude: /src_old/, - loader: "ts-loader", - }, - { test: /\.js$/, loader: "source-map-loader" }, + }, + + module: { + rules: [ { - test: /\.(js|jsx)$/, + test: /\.(ts|tsx)$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { - plugins: ["@babel/plugin-proposal-class-properties"], - presets: ["@babel/preset-env", "@babel/preset-react"] + presets: [ + "@babel/preset-typescript", + "babel-preset-solid", + ] } } } ] - } + }, } \ No newline at end of file