diff --git a/package-lock.json b/package-lock.json index 7483b76..0a34a57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,90 +8,95 @@ "name": "create-cpu", "version": "0.0.0", "dependencies": { - "@emotion/react": "^11.11.1", - "@emotion/styled": "^11.11.0", + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", "@mui/icons-material": "^5.11.16", "@mui/material": "^5.13.5", - "@pixi/color": "^7.2.4", - "@pixi/graphics-extras": "^7.2.4", - "@pixi/math-extras": "^7.2.4", "eventemitter3": "^5.0.1", - "mnemonist": "^0.39.5", + "memoize-one": "^6.0.0", + "mnemonist": "^0.39.8", "nullthrows": "^1.1.1", - "pixi.js": "^7.2.4", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "tiny-invariant": "^1.3.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-use": "^17.5.1", + "tiny-invariant": "^1.3.3", + "transformation-matrix": "^2.16.1", "zustand": "^4.3.9" }, "devDependencies": { - "@tsconfig/esm": "^1.0.3", - "@tsconfig/strictest": "^2.0.1", - "@types/react": "^18.2.12", - "@types/react-dom": "^18.2.5", - "@vitejs/plugin-react": "^4.0.0", + "@tsconfig/esm": "^1.0.5", + "@tsconfig/strictest": "^2.0.5", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", "eslint": "^8.43.0", "eslint-config-airbnb-typescript-prettier": "^5.0.0", "prettier": "^2.8.8", "type-fest": "^3.12.0", - "typescript": "~5.0", + "typescript": "^5.6.3", "vite": "^4.3.9" } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "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.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@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==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", - "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, + "license": "MIT", "dependencies": { "@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", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -101,194 +106,137 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "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.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "browserslist": "^4.21.3", + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" + "semver": "^6.3.1" }, "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.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "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.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dev": true, + "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.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.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.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, + "license": "MIT", "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.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "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.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "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==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "dev": true, + "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.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "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.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" + "@babel/types": "^7.26.0" }, - "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==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -297,12 +245,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz", - "integrity": "sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -312,12 +261,13 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz", - "integrity": "sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -338,34 +288,31 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", - "dev": true, + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.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==", - "dev": true, - "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", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -373,28 +320,29 @@ } }, "node_modules/@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "license": "MIT", "dependencies": { - "@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.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", "babel-plugin-macros": "^3.1.0", "convert-source-map": "^1.5.0", "escape-string-regexp": "^4.0.0", @@ -404,47 +352,52 @@ } }, "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", "stylis": "4.2.0" } }, "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" }, "node_modules/@emotion/is-prop-valid": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", - "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", "dependencies": { - "@emotion/memoize": "^0.8.1" + "@emotion/memoize": "^0.9.0" } }, "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" }, "node_modules/@emotion/react": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", - "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.2", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", "hoist-non-react-statics": "^3.3.1" }, "peerDependencies": { @@ -457,33 +410,36 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", - "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", - "dependencies": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", + "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.1", "csstype": "^3.0.2" } }, "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" }, "node_modules/@emotion/styled": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", - "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", + "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/is-prop-valid": "^1.2.1", - "@emotion/serialize": "^1.1.2", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1" + "@emotion/babel-plugin": "^11.12.0", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0" }, "peerDependencies": { "@emotion/react": "^11.0.0-rc.0", @@ -496,27 +452,31 @@ } }, "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" }, "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "license": "MIT", "peerDependencies": { "react": ">=16.8.0" } }, "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==", + "license": "MIT" }, "node_modules/@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" }, "node_modules/@esbuild/android-arm": { "version": "0.17.19", @@ -987,33 +947,33 @@ "dev": true }, "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==", - "dev": true, + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.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==", - "dev": true, + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "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==", - "dev": true, + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -1021,25 +981,18 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "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==", - "dev": true, + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/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==", - "dev": true - }, "node_modules/@mui/base": { "version": "5.0.0-beta.4", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.4.tgz", @@ -1316,398 +1269,80 @@ "node": ">= 8" } }, - "node_modules/@pixi/accessibility": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/accessibility/-/accessibility-7.2.4.tgz", - "integrity": "sha512-EVjuqUqv9FeYFXCv0S0qj1hgCtbAMNBPCbOGEtiMogpM++/IySxBZvcOYg3rRgo9inwt2s4Bi7kUiqMPD8hItw==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4", - "@pixi/events": "7.2.4" - } - }, - "node_modules/@pixi/app": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/app/-/app-7.2.4.tgz", - "integrity": "sha512-eJ2jpu5P28ip07nLItw6sETXn45P4KR/leMJ6zPHRlhT1m8t5zTsWr3jK4Uj8LF2E+6KlPNzLQh5Alf/unn/aQ==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4" - } - }, - "node_modules/@pixi/assets": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/assets/-/assets-7.2.4.tgz", - "integrity": "sha512-7199re3wvMAlVqXLaCyAr8IkJSXqkeVAxcYyB2rBu4Id5m2hhlGX1dQsdMBiCXLwu6/LLVqDvJggSNVQBzL6ZQ==", - "dependencies": { - "@types/css-font-loading-module": "^0.0.7" - }, - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/utils": "7.2.4" - } - }, - "node_modules/@pixi/color": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.2.4.tgz", - "integrity": "sha512-B/+9JRcXe2uE8wQfsueFRPZVayF2VEMRB7XGeRAsWCryOX19nmWhv0Nt3nOU2rvzI0niz9XgugJXsB6vVmDFSg==", - "dependencies": { - "colord": "^2.9.3" - } - }, - "node_modules/@pixi/compressed-textures": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/compressed-textures/-/compressed-textures-7.2.4.tgz", - "integrity": "sha512-atnWyw/ot/Wg69qhgskKiuTYCZx15IxV35sa0KyXMthyjyvDLCIvOn0nczM6wCBy9H96SjJbfgynVWhVrip6qw==", - "peerDependencies": { - "@pixi/assets": "7.2.4", - "@pixi/core": "7.2.4" - } - }, - "node_modules/@pixi/constants": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.2.4.tgz", - "integrity": "sha512-hKuHBWR6N4Q0Sf5MGF3/9l+POg/G5rqhueHfzofiuelnKg7aBs3BVjjZ+6hZbd6M++vOUmxYelEX/NEFBxrheA==" - }, - "node_modules/@pixi/core": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.2.4.tgz", - "integrity": "sha512-0XtvrfxHlS2T+beBBSpo7GI8+QLyyTqMVQpNmPqB4woYxzrOEJ9JaUFBaBfCvycLeUkfVih1u6HAbtF+2d1EjQ==", - "dependencies": { - "@pixi/color": "7.2.4", - "@pixi/constants": "7.2.4", - "@pixi/extensions": "7.2.4", - "@pixi/math": "7.2.4", - "@pixi/runner": "7.2.4", - "@pixi/settings": "7.2.4", - "@pixi/ticker": "7.2.4", - "@pixi/utils": "7.2.4", - "@types/offscreencanvas": "^2019.6.4" - }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "funding": { "type": "opencollective", - "url": "https://opencollective.com/pixijs" - } - }, - "node_modules/@pixi/display": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/display/-/display-7.2.4.tgz", - "integrity": "sha512-w5tqb8cWEO5qIDaO9GEqRvxYhL0iMk0Wsngw23bbLm1gLEQmrFkB2tpJlRAqd7H82C3DrDDeWvkrrxW6+m4apg==", - "peerDependencies": { - "@pixi/core": "7.2.4" - } - }, - "node_modules/@pixi/events": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/events/-/events-7.2.4.tgz", - "integrity": "sha512-/JtmoB98fzIU8giN9xvlRvmvOi6u4MaD2DnKNOMHkQ1MBraj3pmrXM9fZ0JbNzi+324GraAAY76QidgHjIYoYQ==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4" - } - }, - "node_modules/@pixi/extensions": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.2.4.tgz", - "integrity": "sha512-Mnqv9scbL1ARD3QFKfOWs2aSVJJfP1dL8g5UiqGImYO3rZbz/9QCzXOeMVIZ5n3iaRyKMNhFFr84/zUja2H7Dw==" - }, - "node_modules/@pixi/extract": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/extract/-/extract-7.2.4.tgz", - "integrity": "sha512-wlXZg+J2L/1jQhRi5nZQP/cXshovhjksjss91eAKMvY5aGxNAQovCP4xotJ/XJjfTvPMpeRzHPFYzm3PrOPQ7g==", - "peerDependencies": { - "@pixi/core": "7.2.4" - } - }, - "node_modules/@pixi/filter-alpha": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.2.4.tgz", - "integrity": "sha512-UTUMSGyktUr+I9vmigqJo9iUhb0nwGyqTTME2xBWZvVGCnl5z+/wHxvIBBCe5pNZ66IM15pGXQ4cDcfqCuP2kA==", - "peerDependencies": { - "@pixi/core": "7.2.4" - } - }, - "node_modules/@pixi/filter-blur": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/filter-blur/-/filter-blur-7.2.4.tgz", - "integrity": "sha512-aLyXIoxy14bTansCPtbY8x7Sdn2OrrqkF/pcKiRXHJGGhi7wPacvB/NcmYJdnI/n2ExQ6V5Njuj/nfrsejVwcA==", - "peerDependencies": { - "@pixi/core": "7.2.4" - } - }, - "node_modules/@pixi/filter-color-matrix": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/filter-color-matrix/-/filter-color-matrix-7.2.4.tgz", - "integrity": "sha512-DFtayybYXoUh73eHUFRK5REbi1t3FZuVUnaQTj+euHKF9L7EaYc3Q9wctpx1WPRcwkqEX50M4SNFhxpA7Pxtaw==", - "peerDependencies": { - "@pixi/core": "7.2.4" - } - }, - "node_modules/@pixi/filter-displacement": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/filter-displacement/-/filter-displacement-7.2.4.tgz", - "integrity": "sha512-Simq3IBJKt7+Gvk4kK7OFkfoeYUMhNhIyATCdeT+Jkdkq5WV7pYnH5hqO0YW7eAHrgjV13yn6t4H/GC4+6LhEA==", - "peerDependencies": { - "@pixi/core": "7.2.4" - } - }, - "node_modules/@pixi/filter-fxaa": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/filter-fxaa/-/filter-fxaa-7.2.4.tgz", - "integrity": "sha512-qzKjdL+Ih18uGTJLg8tT/H+YCsTeGkw2uF7lyKnw/lxGLJQhLWIhM95M9qSNgxbXyW1vp7SbG81a9aAEz2HAhA==", - "peerDependencies": { - "@pixi/core": "7.2.4" - } - }, - "node_modules/@pixi/filter-noise": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/filter-noise/-/filter-noise-7.2.4.tgz", - "integrity": "sha512-QAU9Ybj2ZQrWM9ZEjTTC0iLnQcuyNoZNRinxSbg1G0yacpmsSb9wvV5ltIZ66+hfY+90+u2Nudt/v9g6pvOdGg==", - "peerDependencies": { - "@pixi/core": "7.2.4" - } - }, - "node_modules/@pixi/graphics": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/graphics/-/graphics-7.2.4.tgz", - "integrity": "sha512-3A2EumTjWJgXlDLOyuBrl9b6v1Za/E+/IjOGUIX843HH4NYaf1a2sfDfljx6r3oiDvy+VhuBFmgynRcV5IyA0Q==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4", - "@pixi/sprite": "7.2.4" - } - }, - "node_modules/@pixi/graphics-extras": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/graphics-extras/-/graphics-extras-7.2.4.tgz", - "integrity": "sha512-0yT91yqF3KLiZI/iLRcfcYlTVpkVyWsfGtWEIorZs0eX+/zYx7um7EJ2h7tFORI/1FxA2maR4td5vpgCwOLJAQ==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/graphics": "7.2.4" - } - }, - "node_modules/@pixi/math": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.2.4.tgz", - "integrity": "sha512-LJB+mozyEPllxa0EssFZrKNfVwysfaBun4b2dJKQQInp0DafgbA0j7A+WVg0oe51KhFULTJMpDqbLn/ITFc41A==" - }, - "node_modules/@pixi/math-extras": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/math-extras/-/math-extras-7.2.4.tgz", - "integrity": "sha512-YBM4UBQbbGO01vcs4o8IiRBKXlvtjMMp0PS6kRNbe4kLhl66w5QUct4/nZT5MBUEWaIaA673Q+TWv/qTbOtYIQ==", - "peerDependencies": { - "@pixi/core": "7.2.4" - } - }, - "node_modules/@pixi/mesh": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/mesh/-/mesh-7.2.4.tgz", - "integrity": "sha512-wiALIqcRKib2BqeH9kOA5fOKWN352nqAspgbDa8gA7OyWzmNwqIedIlElixd0oLFOrIN5jOZAdzeKnoYQlt9Aw==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4" - } - }, - "node_modules/@pixi/mesh-extras": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/mesh-extras/-/mesh-extras-7.2.4.tgz", - "integrity": "sha512-Lxqq/1E2EmDgjZX8KzjhBy3VvITIQ00arr2ikyHYF1d0XtQTKEYpr8VKzhchqZ5/9DuyTDbDMYGhcxoNXQmZrQ==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/mesh": "7.2.4" - } - }, - "node_modules/@pixi/mixin-cache-as-bitmap": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/mixin-cache-as-bitmap/-/mixin-cache-as-bitmap-7.2.4.tgz", - "integrity": "sha512-95L/9nzfLHw6GoeqqRl/RjSloKvRt0xrc2inCmjMZvMsFUEtHN2F8IWd1k5vcv0S+83NCreFkJg6nJm1m5AZqg==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4", - "@pixi/sprite": "7.2.4" - } - }, - "node_modules/@pixi/mixin-get-child-by-name": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/mixin-get-child-by-name/-/mixin-get-child-by-name-7.2.4.tgz", - "integrity": "sha512-9g17KgSBEEhkinnKk4dqmxagzHOCPSTvGB6lOopBq4yyXmr/2WVv+QGjuzE0O+p80szQeBJjPBQxzrfBILaSRw==", - "peerDependencies": { - "@pixi/display": "7.2.4" - } - }, - "node_modules/@pixi/mixin-get-global-position": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/mixin-get-global-position/-/mixin-get-global-position-7.2.4.tgz", - "integrity": "sha512-UrAUF2BXCeWtFgR2m+er41Ky7zShT7r228cZkB6ZfYwMeThhwqG5mH68UeCyP6p68JMpT1gjI2DPfeSRY3ecnA==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4" - } - }, - "node_modules/@pixi/particle-container": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/particle-container/-/particle-container-7.2.4.tgz", - "integrity": "sha512-tpSzilZGFtAoi8XhzL0TecLPNRQAbY8nWV9XNGXJDw+nxXp18GCe8L6eEmnHLlAug67BRHl65DtrdvTknPX+4g==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4", - "@pixi/sprite": "7.2.4" + "url": "https://opencollective.com/popperjs" } }, - "node_modules/@pixi/prepare": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/prepare/-/prepare-7.2.4.tgz", - "integrity": "sha512-Yff5Sh4kTLdKc5VkkM44LW9gpj7Izw8ns3P1TzWxqeGjzPZ3folr/tQujGL+Qw+8A9VESp+hX9MSIHyw+jpyrg==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4", - "@pixi/graphics": "7.2.4", - "@pixi/text": "7.2.4" - } + "node_modules/@tsconfig/esm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@tsconfig/esm/-/esm-1.0.5.tgz", + "integrity": "sha512-JzoZ0h299JRLPfV5VBsMq1TuMy+OmU9bdV/7NcjfRojL0eIcA1k5ESrtjWrDwJRJnk9B0QmgR0rq04LERbdfWw==", + "deprecated": "this package has been deprecated", + "dev": true, + "license": "MIT" }, - "node_modules/@pixi/runner": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.2.4.tgz", - "integrity": "sha512-YtyqPk1LA+0guEFKSFx6t/YSvbEQwajFwi4Ft8iDhioa6VK2MmTir1GjWwy7JQYLcDmYSAcQjnmFtVTZohyYSw==" + "node_modules/@tsconfig/strictest": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@tsconfig/strictest/-/strictest-2.0.5.tgz", + "integrity": "sha512-ec4tjL2Rr0pkZ5hww65c+EEPYwxOi4Ryv+0MtjeaSQRJyq322Q27eOQiFbuNgw2hpL4hB1/W/HBGk3VKS43osg==", + "dev": true, + "license": "MIT" }, - "node_modules/@pixi/settings": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.2.4.tgz", - "integrity": "sha512-ZPKRar9EwibijGmH8EViu4Greq1I/O7V/xQx2rNqN23XA7g09Qo6yfaeQpufu5xl8+/lZrjuHtQSnuY7OgG1CA==", + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", "dependencies": { - "@pixi/constants": "7.2.4", - "@types/css-font-loading-module": "^0.0.7", - "ismobilejs": "^1.1.0" - } - }, - "node_modules/@pixi/sprite": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/sprite/-/sprite-7.2.4.tgz", - "integrity": "sha512-DhR1B+/d0eXpxHIesJMXcVPrKFwQ+zRA1LvEIFfzewqfaRN3X6PMIuoKX8SIb6tl+Hq8Ba9Pe28zI7d2rmRzrA==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4" - } - }, - "node_modules/@pixi/sprite-animated": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/sprite-animated/-/sprite-animated-7.2.4.tgz", - "integrity": "sha512-9eRriPSC0QVS7U9zQlrG3uEI5+h3fi+mqofXy+yjk1sGCmXSIJME5p2wg2mzxoJk3qkSMagQA9QHtL26Fti8Iw==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/sprite": "7.2.4" - } - }, - "node_modules/@pixi/sprite-tiling": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/sprite-tiling/-/sprite-tiling-7.2.4.tgz", - "integrity": "sha512-nGfxQoACRx49dUN0oW1vFm3141M+7gkAbzoNJym2Pljd2dpLME9fb5E6Lyahu0yWMaPRhhGorn6z9VIGmTF3Jw==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4", - "@pixi/sprite": "7.2.4" - } - }, - "node_modules/@pixi/spritesheet": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/spritesheet/-/spritesheet-7.2.4.tgz", - "integrity": "sha512-LNmlavyiMQeCF0U4S+yhzxUYmPmat6EpLjLnkGukQTZV5CZkxDCVgXM9uKoRF2DvNydj4yuwZ6+JjK8QssHI8Q==", - "peerDependencies": { - "@pixi/assets": "7.2.4", - "@pixi/core": "7.2.4" - } - }, - "node_modules/@pixi/text": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/text/-/text-7.2.4.tgz", - "integrity": "sha512-DGu7ktpe+zHhqR2sG9NsJt4mgvSObv5EqXTtUxD4Z0li1gmqF7uktpLyn5I6vSg1TTEL4TECClRDClVDGiykWw==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/sprite": "7.2.4" - } - }, - "node_modules/@pixi/text-bitmap": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/text-bitmap/-/text-bitmap-7.2.4.tgz", - "integrity": "sha512-3u2CP4VN+muCaq/jtj7gn0hb3DET/X2S04zTBcgc2WVGufJc62yz+UDzS9jC+ellotVdt9c8U74++vpz3zJGfw==", - "peerDependencies": { - "@pixi/assets": "7.2.4", - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4", - "@pixi/mesh": "7.2.4", - "@pixi/text": "7.2.4" - } - }, - "node_modules/@pixi/text-html": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/text-html/-/text-html-7.2.4.tgz", - "integrity": "sha512-0NfLAE/w51ZtatxVqLvDS62iO0VLKsSdctqTAVv4Zlgdk9TKJmX1WUucHJboTvbm2SbDjNDGfZ6qXM5nAslIDQ==", - "peerDependencies": { - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4", - "@pixi/sprite": "7.2.4", - "@pixi/text": "7.2.4" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/@pixi/ticker": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.2.4.tgz", - "integrity": "sha512-hQQHIHvGeFsP4GNezZqjzuhUgNQEVgCH9+qU05UX1Mc5UHC9l6OJnY4VTVhhcHxZjA6RnyaY+1zBxCnoXuazpg==", + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", "dependencies": { - "@pixi/extensions": "7.2.4", - "@pixi/settings": "7.2.4", - "@pixi/utils": "7.2.4" + "@babel/types": "^7.0.0" } }, - "node_modules/@pixi/utils": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.2.4.tgz", - "integrity": "sha512-VUGQHBOINIS4ePzoqafwxaGPVRTa3oM/mEutIIHbNGI3b+QvSO+1Dnk40M0zcH6Bo+MxQZbOZK5X/wO9oU5+LQ==", + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", "dependencies": { - "@pixi/color": "7.2.4", - "@pixi/constants": "7.2.4", - "@pixi/settings": "7.2.4", - "@types/earcut": "^2.1.0", - "earcut": "^2.2.4", - "eventemitter3": "^4.0.0", - "url": "^0.11.0" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/@pixi/utils/node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" } }, - "node_modules/@tsconfig/esm": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/esm/-/esm-1.0.3.tgz", - "integrity": "sha512-Gp56rIc3R8ab032nXMUitmc7YIb4nAi8DQ6Qt47tuL0Ssn9LIOm+o2FQmqPu3jX4z0TsqgzWwkmVygxcq+yHYg==", - "dev": true - }, - "node_modules/@tsconfig/strictest": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/strictest/-/strictest-2.0.1.tgz", - "integrity": "sha512-7JHHCbyCsGUxLd0pDbp24yz3zjxw2t673W5oAP6HCEdr/UUhaRhYd3SSnUsGCk+VnPVJVA4mXROzbhI+nyIk+w==", - "dev": true - }, - "node_modules/@types/css-font-loading-module": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", - "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==" - }, - "node_modules/@types/earcut": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.1.tgz", - "integrity": "sha512-w8oigUCDjElRHRRrMvn/spybSMyX8MTkKA5Dv+tS1IE/TgmNZPqUYtvYBXGY8cieSE66gm+szeK+bnbxC2xHTQ==" + "node_modules/@types/js-cookie": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz", + "integrity": "sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==", + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.12", @@ -1721,15 +1356,11 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/offscreencanvas": { - "version": "2019.7.0", - "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", - "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==" - }, "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" }, "node_modules/@types/prop-types": { "version": "15.7.5", @@ -1737,20 +1368,21 @@ "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, "node_modules/@types/react": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.12.tgz", - "integrity": "sha512-ndmBMLCgn38v3SntMeoJaIrO6tGHYKMEBohCUmw8HoLLQdRMOIGXfeYaBTLe2lsFaSB3MOK1VXscYFnmLtTSmw==", + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "license": "MIT", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.5.tgz", - "integrity": "sha512-sRQsOS/sCLnpQhR4DSKGTtWFE3FZjpQa86KPVbhUqdYMRZ9FEFcfAytKhR/vUG2rH1oFbOOej6cuD7MFSobDRQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/react": "*" } @@ -1771,11 +1403,6 @@ "@types/react": "*" } }, - "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" - }, "node_modules/@types/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", @@ -2092,23 +1719,31 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.0.0.tgz", - "integrity": "sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", + "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/core": "^7.21.4", - "@babel/plugin-transform-react-jsx-self": "^7.21.0", - "@babel/plugin-transform-react-jsx-source": "^7.19.6", - "react-refresh": "^0.14.0" + "@babel/core": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" }, "engines": { "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "vite": "^4.2.0" + "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/@xobotyi/scrollbar-width": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz", + "integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==", + "license": "MIT" + }, "node_modules/acorn": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", @@ -2155,17 +1790,6 @@ "node": ">=8" } }, - "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/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2311,6 +1935,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -2350,9 +1975,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.9", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", - "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "dev": true, "funding": [ { @@ -2368,11 +1993,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001503", - "electron-to-chromium": "^1.4.431", - "node-releases": "^2.0.12", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -2385,6 +2011,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -2402,9 +2029,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001503", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001503.tgz", - "integrity": "sha512-Sf9NiF+wZxPfzv8Z3iS0rXM1Do+iOy2Lxvib38glFX+08TCYYYGR5fRJXk4d77C4AYwhUjgYgMsMudbh2TqCKw==", + "version": "1.0.30001680", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", + "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", "dev": true, "funding": [ { @@ -2419,28 +2046,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] - }, - "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==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/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" - } + ], + "license": "CC-BY-4.0" }, "node_modules/clsx": { "version": "1.2.1", @@ -2450,24 +2057,6 @@ "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/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2485,10 +2074,20 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -2514,6 +2113,37 @@ "node": ">= 8" } }, + "node_modules/css-in-js-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", + "integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.3" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/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/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -2529,7 +2159,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2606,16 +2235,12 @@ "csstype": "^3.0.2" } }, - "node_modules/earcut": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", - "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" - }, "node_modules/electron-to-chromium": { - "version": "1.4.433", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.433.tgz", - "integrity": "sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ==", - "dev": true + "version": "1.5.56", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.56.tgz", + "integrity": "sha512-7lXb9dAvimCFdvUMTyucD4mnIndt/xhRKFAlky0CyFogdnNmdPQNoHI23msF/2V4mpTxMzgMdjK4+YRlFlRQZw==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -2627,10 +2252,20 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, "node_modules/es-abstract": { "version": "1.21.2", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", @@ -2757,10 +2392,11 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3321,8 +2957,7 @@ "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==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-diff": { "version": "1.3.0", @@ -3370,6 +3005,17 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-shallow-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz", + "integrity": "sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==" + }, + "node_modules/fastest-stable-stringify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz", + "integrity": "sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==", + "license": "MIT" + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -3406,7 +3052,8 @@ "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", @@ -3509,6 +3156,7 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -3517,6 +3165,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -3588,7 +3237,7 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -3672,14 +3321,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, "node_modules/has-property-descriptors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", @@ -3696,6 +3337,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -3707,6 +3349,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -3742,6 +3385,12 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -3791,6 +3440,15 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/inline-style-prefixer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz", + "integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==", + "license": "MIT", + "dependencies": { + "css-in-js-utils": "^3.1.0" + } + }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -3822,7 +3480,8 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" }, "node_modules/is-bigint": { "version": "1.0.4", @@ -4051,10 +3710,11 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/ismobilejs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", - "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==" + "node_modules/js-cookie": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz", + "integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==", + "license": "MIT" }, "node_modules/js-tokens": { "version": "4.0.0", @@ -4074,21 +3734,22 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "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==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -4107,6 +3768,7 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -4158,7 +3820,8 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "node_modules/locate-path": { "version": "6.0.0", @@ -4197,10 +3860,23 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4245,9 +3921,10 @@ } }, "node_modules/mnemonist": { - "version": "0.39.5", - "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.5.tgz", - "integrity": "sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==", + "version": "0.39.8", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz", + "integrity": "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==", + "license": "MIT", "dependencies": { "obliterator": "^2.0.1" } @@ -4255,8 +3932,33 @@ "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==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nano-css": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/nano-css/-/nano-css-5.6.2.tgz", + "integrity": "sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw==", + "license": "Unlicense", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "css-tree": "^1.1.2", + "csstype": "^3.1.2", + "fastest-stable-stringify": "^2.0.2", + "inline-style-prefixer": "^7.0.1", + "rtl-css-js": "^1.16.1", + "stacktrace-js": "^2.0.2", + "stylis": "^4.3.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/nano-css/node_modules/stylis": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.4.tgz", + "integrity": "sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==", + "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.6", @@ -4289,10 +3991,11 @@ "dev": true }, "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==", - "dev": true + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" }, "node_modules/nullthrows": { "version": "1.1.1", @@ -4311,6 +4014,7 @@ "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4479,6 +4183,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -4533,10 +4238,10 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "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/picomatch": { "version": "2.3.1", @@ -4550,47 +4255,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pixi.js": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-7.2.4.tgz", - "integrity": "sha512-nBH60meoLnHxoMFz17HoMxXS4uJpG5jwIdL+Gx2S11TzWgP3iKF+/WLOTrkSdyuQoQSdIBxVqpnYii0Wiox15A==", - "dependencies": { - "@pixi/accessibility": "7.2.4", - "@pixi/app": "7.2.4", - "@pixi/assets": "7.2.4", - "@pixi/compressed-textures": "7.2.4", - "@pixi/core": "7.2.4", - "@pixi/display": "7.2.4", - "@pixi/events": "7.2.4", - "@pixi/extensions": "7.2.4", - "@pixi/extract": "7.2.4", - "@pixi/filter-alpha": "7.2.4", - "@pixi/filter-blur": "7.2.4", - "@pixi/filter-color-matrix": "7.2.4", - "@pixi/filter-displacement": "7.2.4", - "@pixi/filter-fxaa": "7.2.4", - "@pixi/filter-noise": "7.2.4", - "@pixi/graphics": "7.2.4", - "@pixi/mesh": "7.2.4", - "@pixi/mesh-extras": "7.2.4", - "@pixi/mixin-cache-as-bitmap": "7.2.4", - "@pixi/mixin-get-child-by-name": "7.2.4", - "@pixi/mixin-get-global-position": "7.2.4", - "@pixi/particle-container": "7.2.4", - "@pixi/prepare": "7.2.4", - "@pixi/sprite": "7.2.4", - "@pixi/sprite-animated": "7.2.4", - "@pixi/sprite-tiling": "7.2.4", - "@pixi/spritesheet": "7.2.4", - "@pixi/text": "7.2.4", - "@pixi/text-bitmap": "7.2.4", - "@pixi/text-html": "7.2.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/pixijs" - } - }, "node_modules/postcss": { "version": "8.4.24", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", @@ -4679,20 +4343,6 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4714,9 +4364,10 @@ ] }, "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -4725,15 +4376,16 @@ } }, "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==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^18.2.0" + "react": "^18.3.1" } }, "node_modules/react-is": { @@ -4742,10 +4394,11 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-refresh": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", - "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4765,6 +4418,47 @@ "react-dom": ">=16.6.0" } }, + "node_modules/react-universal-interface": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz", + "integrity": "sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==", + "peerDependencies": { + "react": "*", + "tslib": "*" + } + }, + "node_modules/react-use": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/react-use/-/react-use-17.5.1.tgz", + "integrity": "sha512-LG/uPEVRflLWMwi3j/sZqR00nF6JGqTTDblkXK2nzXsIvij06hXl1V/MZIlwj1OKIQUtlh1l9jK8gLsRyCQxMg==", + "license": "Unlicense", + "dependencies": { + "@types/js-cookie": "^2.2.6", + "@xobotyi/scrollbar-width": "^1.9.5", + "copy-to-clipboard": "^3.3.1", + "fast-deep-equal": "^3.1.3", + "fast-shallow-equal": "^1.0.0", + "js-cookie": "^2.2.1", + "nano-css": "^5.6.2", + "react-universal-interface": "^0.6.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.1.0", + "set-harmonic-interval": "^1.0.1", + "throttle-debounce": "^3.0.1", + "ts-easing": "^0.2.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/react-use/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" + }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", @@ -4787,6 +4481,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -4852,6 +4552,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/rtl-css-js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz", + "integrity": "sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4890,13 +4599,26 @@ } }, "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -4906,6 +4628,15 @@ "semver": "bin/semver.js" } }, + "node_modules/set-harmonic-interval": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz", + "integrity": "sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==", + "license": "Unlicense", + "engines": { + "node": ">=6.9" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4931,6 +4662,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -4953,6 +4685,7 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -4966,6 +4699,51 @@ "node": ">=0.10.0" } }, + "node_modules/stack-generator": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", + "integrity": "sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/stacktrace-gps": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz", + "integrity": "sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==", + "license": "MIT", + "dependencies": { + "source-map": "0.5.6", + "stackframe": "^1.3.4" + } + }, + "node_modules/stacktrace-gps/node_modules/source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stacktrace-js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", + "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", + "license": "MIT", + "dependencies": { + "error-stack-parser": "^2.0.6", + "stack-generator": "^2.0.5", + "stacktrace-gps": "^3.0.4" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", @@ -5068,17 +4846,6 @@ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" }, - "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", @@ -5096,19 +4863,21 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/tiny-invariant": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz", - "integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==" - }, - "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": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "node_modules/throttle-debounce": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", + "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=10" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5121,6 +4890,27 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT" + }, + "node_modules/transformation-matrix": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.16.1.tgz", + "integrity": "sha512-tdtC3wxVEuzU7X/ydL131Q3JU5cPMEn37oqVLITjRDSDsnSHVFzW2JiCLfZLIQEgWzZHdSy3J6bZzvKEN24jGA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/chrvadala" + } + }, + "node_modules/ts-easing": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ts-easing/-/ts-easing-0.2.0.tgz", + "integrity": "sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==", + "license": "Unlicense" + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -5148,8 +4938,7 @@ "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -5205,16 +4994,17 @@ } }, "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=12.20" + "node": ">=14.17" } }, "node_modules/unbox-primitive": { @@ -5233,9 +5023,9 @@ } }, "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==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -5251,9 +5041,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -5271,20 +5062,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.1.tgz", - "integrity": "sha512-rWS3H04/+mzzJkv0eZ7vEDGiQbgquI1fGfOad6zKvgYQi1SzMmhl7c/DdRGxhaWrVH6z0qWITo8rpnxK/RfEhA==", - "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.11.0" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, "node_modules/use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", @@ -5411,12 +5188,14 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", "engines": { "node": ">= 6" } diff --git a/package.json b/package.json index 1574018..982617a 100644 --- a/package.json +++ b/package.json @@ -13,33 +13,32 @@ "lint:fix": "eslint . --fix" }, "dependencies": { - "@emotion/react": "^11.11.1", - "@emotion/styled": "^11.11.0", + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", "@mui/icons-material": "^5.11.16", "@mui/material": "^5.13.5", - "@pixi/color": "^7.2.4", - "@pixi/graphics-extras": "^7.2.4", - "@pixi/math-extras": "^7.2.4", "eventemitter3": "^5.0.1", - "mnemonist": "^0.39.5", + "memoize-one": "^6.0.0", + "mnemonist": "^0.39.8", "nullthrows": "^1.1.1", - "pixi.js": "^7.2.4", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "tiny-invariant": "^1.3.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-use": "^17.5.1", + "tiny-invariant": "^1.3.3", + "transformation-matrix": "^2.16.1", "zustand": "^4.3.9" }, "devDependencies": { - "@tsconfig/esm": "^1.0.3", - "@tsconfig/strictest": "^2.0.1", - "@types/react": "^18.2.12", - "@types/react-dom": "^18.2.5", - "@vitejs/plugin-react": "^4.0.0", + "@tsconfig/esm": "^1.0.5", + "@tsconfig/strictest": "^2.0.5", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", "eslint": "^8.43.0", "eslint-config-airbnb-typescript-prettier": "^5.0.0", "prettier": "^2.8.8", "type-fest": "^3.12.0", - "typescript": "~5.0", + "typescript": "^5.6.3", "vite": "^4.3.9" } } diff --git a/src/App.tsx b/src/App.tsx index 26ece4b..103f6c0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,5 @@ import { Box } from "@mui/material"; import { useState } from "react"; -import "@pixi/math-extras"; import GlobalHeader from "./components/GlobalHeader"; import type { CCComponentId } from "./store/component"; import EditPage from "./pages/edit"; diff --git a/src/common/observable.ts b/src/common/observable.ts deleted file mode 100644 index ce82454..0000000 --- a/src/common/observable.ts +++ /dev/null @@ -1,40 +0,0 @@ -export interface IObserver { - (value: T): void; -} - -export interface IObservable { - readonly value: T; - - observe(listener: IObserver): void; -} - -export class Observable implements IObservable { - #value: T; - - #listeners: IObserver[]; - - get value() { - return this.#value; - } - - set value(value) { - this.#value = value; - this.#listeners.forEach((listener) => { - listener(value); - }); - } - - observe(listener: IObserver): void { - this.#listeners.push(listener); - } - - unobserve(listener: IObserver): void { - const index = this.#listeners.indexOf(listener); - if (index >= 0) this.#listeners.splice(index, 1); - } - - constructor(initialValue: T) { - this.#value = initialValue; - this.#listeners = []; - } -} diff --git a/src/common/perspective.ts b/src/common/perspective.ts deleted file mode 100644 index e0a8fb5..0000000 --- a/src/common/perspective.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type * as PIXI from "pixi.js"; - -export type Perspective = Readonly<{ - scale: number; - center: PIXI.Point; -}>; diff --git a/src/common/theme.ts b/src/common/theme.ts index 77e8d91..b1f1464 100644 --- a/src/common/theme.ts +++ b/src/common/theme.ts @@ -1,21 +1,20 @@ -import { Color } from "@pixi/color"; import { createTheme } from "@mui/material"; // See https://www.figma.com/file/M3dC0Gk98IGSGlxY901rBh/ -export const blackColor = 0x000000; -export const whiteColor = 0xffffff; +export const blackColor = "#000000"; +export const whiteColor = "#ffffff"; export const grayColor = { - main: 0x9e9e9e, - darken2: 0x616161, + main: "#9e9e9e", + darken2: "#616161", }; -export const primaryColor = 0x00d372; -export const activeColor = 0x00aaff; -export const errorColor = 0xff0000; -export const editorBackgroundColor = 0xf3f3f3; -export const editorGridColor = 0xdddddd; +export const primaryColor = "#00d372"; +export const activeColor = "#00aaff"; +export const errorColor = "#ff0000"; +export const editorBackgroundColor = "#f3f3f3"; +export const editorGridColor = "#dddddd"; export const theme = createTheme({ palette: { - primary: { main: new Color(primaryColor).toHex() }, + primary: { main: primaryColor }, }, }); diff --git a/src/common/types.ts b/src/common/types.ts new file mode 100644 index 0000000..5816552 --- /dev/null +++ b/src/common/types.ts @@ -0,0 +1 @@ +export type Point = { x: number; y: number }; diff --git a/src/pages/edit/Editor/components/ContextMenu.tsx b/src/pages/edit/Editor/components/ContextMenu.tsx new file mode 100644 index 0000000..b8b88b9 --- /dev/null +++ b/src/pages/edit/Editor/components/ContextMenu.tsx @@ -0,0 +1,176 @@ +import { + ClickAwayListener, + MenuList, + Paper, + MenuItem, + Divider, +} from "@mui/material"; +import nullthrows from "nullthrows"; +import invariant from "tiny-invariant"; +import { + CCComponentStore, + type CCComponentId, +} from "../../../../store/component"; +import { + type CCConnection, + CCConnectionStore, +} from "../../../../store/connection"; +import { + type CCNodeId, + type CCNode, + CCNodeStore, +} from "../../../../store/node"; +import { useComponentEditorStore } from "../store"; +import { useStore } from "../../../../store/react"; + +export type CCComponentEditorContextMenuProps = { + onEditComponent: (componentId: CCComponentId) => void; +}; + +export default function CCComponentEditorContextMenu({ + onEditComponent, +}: CCComponentEditorContextMenuProps) { + const { store } = useStore(); + const componentEditorState = useComponentEditorStore()(); + + if (!componentEditorState.contextMenuState) return null; + + return ( + + + + Create a node + + {componentEditorState.selectedNodeIds.size > 0 && ( + { + const oldNodes = [...componentEditorState.selectedNodeIds].map( + (nodeId) => { + const node = store.nodes.get(nodeId); + invariant(node); + return node; + } + ); + const oldConnections = [ + ...componentEditorState.selectedConnectionIds, + ].map((connectionId) => { + const connection = store.connections.get(connectionId); + invariant(connection); + return connection; + }); + const newComponent = CCComponentStore.create({ + name: "New Component", + }); + store.components.register(newComponent); + const oldToNewNodeIdMap = new Map(); + const newNodes = oldNodes.map((oldNode) => { + const newNode = CCNodeStore.create({ + parentComponentId: newComponent.id, + position: oldNode.position, + componentId: oldNode.componentId, + variablePins: [], + }); + oldToNewNodeIdMap.set(oldNode.id, newNode.id); + return newNode; + }); + for (const node of newNodes) store.nodes.register(node); + const newConnections = oldConnections.flatMap( + (oldConnection) => { + const oldFromNodePin = nullthrows( + store.nodePins.get(oldConnection.from) + ); + const oldToNodePin = nullthrows( + store.nodePins.get(oldConnection.to) + ); + const newFromNodeId = nullthrows( + oldToNewNodeIdMap.get(oldFromNodePin.nodeId) + ); + const newToNodeId = nullthrows( + oldToNewNodeIdMap.get(oldToNodePin.nodeId) + ); + return CCConnectionStore.create({ + parentComponentId: newComponent.id, + from: store.nodePins.getByImplementationNodeIdAndPinId( + newFromNodeId, + oldFromNodePin.componentPinId + ).id, + to: store.nodePins.getByImplementationNodeIdAndPinId( + newToNodeId, + oldToNodePin.componentPinId + ).id, + bentPortion: oldConnection.bentPortion, + }); + } + ); + for (const connection of newConnections) + store.connections.register(connection); + store.connections.unregister([ + ...componentEditorState.selectedConnectionIds, + ]); + store.nodes.unregister([...componentEditorState.selectedNodeIds]); + componentEditorState.closeContextMenu(); + onEditComponent(newComponent.id); + }} + > + Create a new component... + + )} + {(componentEditorState.selectedNodeIds.size > 0 || + componentEditorState.selectedConnectionIds.size > 0) && ( + { + if (componentEditorState.selectedNodeIds.size > 0) + store.nodes.unregister([ + ...componentEditorState.selectedNodeIds, + ]); + if (componentEditorState.selectedConnectionIds.size > 0) + store.connections.unregister([ + ...componentEditorState.selectedConnectionIds, + ]); + componentEditorState.selectNode([], true); + componentEditorState.selectConnection([], false); + componentEditorState.closeContextMenu(); + }} + > + Delete + + )} + {(() => { + if (componentEditorState.selectedNodeIds.size !== 1) return undefined; + const iteratorResult = componentEditorState.selectedNodeIds + .values() + .next(); + invariant(!iteratorResult.done); + const targetNode = store.nodes.get(iteratorResult.value); + invariant(targetNode); + const targetComponent = store.components.get(targetNode.componentId); + invariant(targetComponent); + if (targetComponent.isIntrinsic) return undefined; + return ( + <> + + { + invariant(targetNode); + componentEditorState.closeContextMenu(); + onEditComponent(targetNode.componentId); + }} + > + Edit... + + + ); + })()} + + + ); +} diff --git a/src/pages/edit/Editor/components/TitleBar.tsx b/src/pages/edit/Editor/components/TitleBar.tsx new file mode 100644 index 0000000..825a010 --- /dev/null +++ b/src/pages/edit/Editor/components/TitleBar.tsx @@ -0,0 +1,48 @@ +import { KeyboardDoubleArrowRight, Edit, Close } from "@mui/icons-material"; +import { Paper, Box, IconButton } from "@mui/material"; +import nullthrows from "nullthrows"; +import { useComponentEditorStore } from "../store"; +import { useStore } from "../../../../store/react"; + +export type CCComponentEditorTitleBarProps = { + onEditorClose: () => void; + onComponentPropertyDialogOpen: () => void; +}; + +export default function CCComponentEditorTitleBar({ + onEditorClose, + onComponentPropertyDialogOpen, +}: CCComponentEditorTitleBarProps) { + const componentEditorStore = useComponentEditorStore(); + const componentEditorState = componentEditorStore(); + const { store } = useStore(); + const component = nullthrows( + store.components.get(componentEditorState.componentId) + ); + + return ( + + Components + + {component.name} + + + +
+ + + + + ); +} diff --git a/src/pages/edit/Editor/components/ViewModeSwitcher.tsx b/src/pages/edit/Editor/components/ViewModeSwitcher.tsx new file mode 100644 index 0000000..1940d41 --- /dev/null +++ b/src/pages/edit/Editor/components/ViewModeSwitcher.tsx @@ -0,0 +1,33 @@ +import { Edit, PlayArrow, SkipNext } from "@mui/icons-material"; +import { Fab } from "@mui/material"; +import { useComponentEditorStore } from "../store"; + +export default function CCComponentEditorViewModeSwitcher() { + const componentEditorState = useComponentEditorStore()(); + + return ( + <> + { + componentEditorState.setEditorMode( + componentEditorState.editorMode === "edit" ? "play" : "edit" + ); + componentEditorState.resetTimeStep(); + }} + > + {componentEditorState.editorMode === "edit" ? : } + + {componentEditorState.editorMode === "play" && ( + componentEditorState.incrementTimeStep()} + > + + + )} + + ); +} diff --git a/src/pages/edit/Editor/index.tsx b/src/pages/edit/Editor/index.tsx index 6e562d9..26973c1 100644 --- a/src/pages/edit/Editor/index.tsx +++ b/src/pages/edit/Editor/index.tsx @@ -1,35 +1,14 @@ -import * as PIXI from "pixi.js"; -import { - Box, - ClickAwayListener, - Divider, - Fab, - IconButton, - MenuItem, - MenuList, - Paper, -} from "@mui/material"; -import SkipNextIcon from "@mui/icons-material/SkipNext"; -import { useEffect, useRef, useState } from "react"; -import invariant from "tiny-invariant"; -import { - Close, - Edit, - KeyboardDoubleArrowRight, - PlayArrow, -} from "@mui/icons-material"; +import { Box } from "@mui/material"; +import { useState } from "react"; import nullthrows from "nullthrows"; import { useStore } from "../../../store/react"; -import CCComponentEditorRenderer from "./renderer"; -import { CCComponentStore, type CCComponentId } from "../../../store/component"; -import { parseDataTransferAsComponent } from "../../../common/serialization"; -import { CCNodeStore, type CCNodeId, type CCNode } from "../../../store/node"; -import { ComponentEditorStoreProvider, useComponentEditorStore } from "./store"; -import { - CCConnectionStore, - type CCConnection, -} from "../../../store/connection"; +import { ComponentEditorStoreProvider } from "./store"; import { ComponentPropertyDialog } from "../../../components/ComponentPropertyDialog"; +import CCComponentEditorTitleBar from "./components/TitleBar"; +import CCComponentEditorViewModeSwitcher from "./components/ViewModeSwitcher"; +import CCComponentEditorContextMenu from "./components/ContextMenu"; +import type { CCComponentId } from "../../../store/component"; +import CCComponentEditorRenderer from "./renderer"; export type CCComponentEditorProps = { componentId: CCComponentId; @@ -42,285 +21,22 @@ function CCComponentEditorContent({ onEditComponent, onClose, }: CCComponentEditorProps) { - const rendererRef = useRef(); - const containerRef = useRef(null); - const overlayAreaRef = useRef(null); const { store } = useStore(); - const componentEditorStore = useComponentEditorStore(); - const componentEditorState = componentEditorStore(); - const component = store.components.get(componentId); - invariant(component); - - const [contextMenuPosition, setContextMenuPosition] = - useState(null); + const component = nullthrows(store.components.get(componentId)); const [isComponentPropertyDialogOpen, setIsComponentPropertyDialogOpen] = useState(false); - useEffect(() => { - invariant(containerRef.current && overlayAreaRef.current); - const app = new CCComponentEditorRenderer({ - context: { - store, - componentEditorStore, - overlayArea: overlayAreaRef.current, - }, - componentId, - htmlContainer: containerRef.current, - onContextMenu: setContextMenuPosition, - }); - rendererRef.current = app; - return () => app.destroy(); - }, [store, componentEditorStore, componentId]); - return ( -
{ - e.preventDefault(); - }} - onDragOver={(e) => { - e.preventDefault(); - }} - onDrop={(e) => { - invariant(rendererRef.current); - const droppedComponentId = parseDataTransferAsComponent( - e.dataTransfer - ); - if (!droppedComponentId || componentEditorState.editorMode === "play") - return; - store.nodes.register( - CCNodeStore.create({ - componentId: droppedComponentId, - parentComponentId: componentId, - position: componentEditorState.toWorldPosition( - new PIXI.Point(e.nativeEvent.offsetX, e.nativeEvent.offsetY) - ), - // TODO: implement - intrinsicVariablePinCount: null, - }) - ); - }} - /> -
+ + setIsComponentPropertyDialogOpen(true) + } + onEditorClose={onClose} /> - - Components - - {component.name} - { - setIsComponentPropertyDialogOpen(true); - }} - > - - -
- { - onClose(); - }} - > - - - - { - componentEditorState.setEditorMode( - componentEditorState.editorMode === "edit" ? "play" : "edit" - ); - componentEditorState.resetTimeStep(); - }} - > - {componentEditorState.editorMode === "edit" ? : } - - {componentEditorState.editorMode === "play" && ( - componentEditorState.incrementTimeStep()} - > - - - )} - {contextMenuPosition && ( - { - setContextMenuPosition(null); - }} - > - - { - invariant(rendererRef.current); - setContextMenuPosition(null); - }} - > - Create a node - - {componentEditorState.selectedNodeIds.size > 0 && ( - { - const oldNodes = [ - ...componentEditorState.selectedNodeIds, - ].map((nodeId) => { - const node = store.nodes.get(nodeId); - invariant(node); - return node; - }); - const oldConnections = [ - ...componentEditorState.selectedConnectionIds, - ].map((connectionId) => { - const connection = store.connections.get(connectionId); - invariant(connection); - return connection; - }); - const newComponent = CCComponentStore.create({ - name: "New Component", - }); - store.components.register(newComponent); - const oldToNewNodeIdMap = new Map(); - const newNodes = oldNodes.map((oldNode) => { - const newNode = CCNodeStore.create({ - parentComponentId: newComponent.id, - position: oldNode.position, - componentId: oldNode.componentId, - intrinsicVariablePinCount: - oldNode.intrinsicVariablePinCount, - }); - oldToNewNodeIdMap.set(oldNode.id, newNode.id); - return newNode; - }); - for (const node of newNodes) store.nodes.register(node); - const newConnections = oldConnections.flatMap( - (oldConnection) => { - const oldFromNodePin = nullthrows( - store.nodePins.get(oldConnection.from) - ); - const oldToNodePin = nullthrows( - store.nodePins.get(oldConnection.to) - ); - const newFromNodeId = nullthrows( - oldToNewNodeIdMap.get(oldFromNodePin.nodeId) - ); - const newToNodeId = nullthrows( - oldToNewNodeIdMap.get(oldToNodePin.nodeId) - ); - return CCConnectionStore.create({ - parentComponentId: newComponent.id, - from: store.nodePins.getByImplementationNodeIdAndPinId( - newFromNodeId, - oldFromNodePin.componentPinId - ).id, - to: store.nodePins.getByImplementationNodeIdAndPinId( - newToNodeId, - oldToNodePin.componentPinId - ).id, - bentPortion: oldConnection.bentPortion, - }); - } - ); - for (const connection of newConnections) - store.connections.register(connection); - store.connections.unregister([ - ...componentEditorState.selectedConnectionIds, - ]); - store.nodes.unregister([ - ...componentEditorState.selectedNodeIds, - ]); - setContextMenuPosition(null); - onEditComponent(newComponent.id); - }} - > - Create a new component... - - )} - {(componentEditorState.selectedNodeIds.size > 0 || - componentEditorState.selectedConnectionIds.size > 0) && ( - { - if (componentEditorState.selectedNodeIds.size > 0) - store.nodes.unregister([ - ...componentEditorState.selectedNodeIds, - ]); - if (componentEditorState.selectedConnectionIds.size > 0) - store.connections.unregister([ - ...componentEditorState.selectedConnectionIds, - ]); - componentEditorState.selectNode([], true); - componentEditorState.selectConnection([], false); - setContextMenuPosition(null); - }} - > - Delete - - )} - {(() => { - if (componentEditorState.selectedNodeIds.size !== 1) - return undefined; - const iteratorResult = componentEditorState.selectedNodeIds - .values() - .next(); - invariant(!iteratorResult.done); - const targetNode = store.nodes.get(iteratorResult.value); - invariant(targetNode); - const targetComponent = store.components.get( - targetNode.componentId - ); - invariant(targetComponent); - if (targetComponent.isIntrinsic) return undefined; - return ( - <> - - { - invariant(targetNode); - setContextMenuPosition(null); - onEditComponent(targetNode.componentId); - }} - > - Edit... - - - ); - })()} - - - )} + + {isComponentPropertyDialogOpen && ( { + componentEditorState.selectNode([], true); + }} + onPointerDown={(pointerDownEvent) => { + const { currentTarget } = pointerDownEvent; + const startUserTransformation = + componentEditorState.userPerspectiveTransformation; + const startInverseViewTransformation = + componentEditorState.getInverseViewTransformation(); + const startPoint = matrix.applyToPoint(startInverseViewTransformation, { + x: pointerDownEvent.nativeEvent.offsetX, + y: pointerDownEvent.nativeEvent.offsetY, + }); + const onPointerMove = (pointerMoveEvent: PointerEvent) => { + const endPoint = matrix.applyToPoint(startInverseViewTransformation, { + x: pointerMoveEvent.offsetX, + y: pointerMoveEvent.offsetY, + }); + componentEditorState.setUserPerspectiveTransformation( + matrix.compose( + startUserTransformation, + matrix.translate( + endPoint.x - startPoint.x, + endPoint.y - startPoint.y + ) + ) + ); + }; + currentTarget.addEventListener("pointermove", onPointerMove); + const onPointerUp = () => { + currentTarget.removeEventListener("pointermove", onPointerMove); + currentTarget.removeEventListener("pointerup", onPointerUp); + }; + currentTarget.addEventListener("pointerup", onPointerUp); + }} + onWheel={(wheelEvent) => { + const scale = Math.exp(-wheelEvent.deltaY / 256); + const center = matrix.applyToPoint( + componentEditorState.getInverseViewTransformation(), + { + x: wheelEvent.nativeEvent.offsetX, + y: wheelEvent.nativeEvent.offsetY, + } + ); + componentEditorState.setUserPerspectiveTransformation( + matrix.compose( + componentEditorState.userPerspectiveTransformation, + matrix.scale(scale, scale, center.x, center.y) + ) + ); + }} + fill={whiteColor} + /> + ); +} diff --git a/src/pages/edit/Editor/renderer/Connection.tsx b/src/pages/edit/Editor/renderer/Connection.tsx new file mode 100644 index 0000000..85eb569 --- /dev/null +++ b/src/pages/edit/Editor/renderer/Connection.tsx @@ -0,0 +1,76 @@ +import nullthrows from "nullthrows"; +import type { CCConnectionId } from "../../../../store/connection"; +import { useStore } from "../../../../store/react"; +import { useNode } from "../../../../store/react/selectors"; +import getCCComponentEditorRendererNodeGeometry from "./Node.geometry"; +import ensureStoreItem from "../../../../store/react/error"; + +export type CCComponentEditorRendererConnectionCoreProps = { + from: { x: number; y: number }; + to: { x: number; y: number }; +}; +export function CCComponentEditorRendererConnectionCore({ + from, + to, +}: CCComponentEditorRendererConnectionCoreProps) { + const straightGap = 10; + const direction = from.x < to.x ? 1 : -1; + + return ( + + ); +} + +export type CCComponentEditorRendererConnectionProps = { + connectionId: CCConnectionId; +}; +const CCComponentEditorRendererConnection = ensureStoreItem( + (props, store) => store.connections.get(props.connectionId), + ({ connectionId }: CCComponentEditorRendererConnectionProps) => { + const { store } = useStore(); + const connection = nullthrows(store.connections.get(connectionId)); + const fromNodePin = nullthrows(store.nodePins.get(connection.from)); + const toNodePin = nullthrows(store.nodePins.get(connection.to)); + const fromNode = useNode(fromNodePin.nodeId); + const toNode = useNode(toNodePin.nodeId); + const fromNodeGeometry = getCCComponentEditorRendererNodeGeometry( + store, + fromNode.id + ); + const toNodeGeometry = getCCComponentEditorRendererNodeGeometry( + store, + toNode.id + ); + const fromNodePinPosition = nullthrows( + fromNodeGeometry.nodePinPositionById.get(fromNodePin.id) + ); + const toNodePinPosition = nullthrows( + toNodeGeometry.nodePinPositionById.get(toNodePin.id) + ); + + return ( + + ); + } +); +export default CCComponentEditorRendererConnection; diff --git a/src/pages/edit/Editor/renderer/Node.geometry.ts b/src/pages/edit/Editor/renderer/Node.geometry.ts new file mode 100644 index 0000000..1e08687 --- /dev/null +++ b/src/pages/edit/Editor/renderer/Node.geometry.ts @@ -0,0 +1,62 @@ +import nullthrows from "nullthrows"; +import type CCStore from "../../../../store"; +import type { CCNodeId } from "../../../../store/node"; +import type { CCNodePinId } from "../../../../store/nodePin"; + +export default function getCCComponentEditorRendererNodeGeometry( + store: CCStore, + nodeId: CCNodeId +) { + const width = 100; + const gapY = 20; + const paddingY = 20; + + const node = nullthrows(store.nodes.get(nodeId)); + const nodePins = store.nodePins.getManyByNodeId(nodeId); + + const x = node.position.x - width / 2; + + let inputPinCount = 0; + let outputPinCount = 0; + const nodePinPositionById = new Map(); + for (const nodePin of nodePins) { + const position = { x: 0, y: 0 }; + const componentPin = nullthrows( + store.componentPins.get(nodePin.componentPinId) + ); + if (componentPin.type === "input") { + position.x = node.position.x - width / 2; + position.y = node.position.y + inputPinCount * gapY; + inputPinCount += 1; + } else { + position.x = node.position.x + width / 2; + position.y = node.position.y + outputPinCount * gapY; + outputPinCount += 1; + } + nodePinPositionById.set(nodePin.id, position); + } + + const height = + (Math.max(inputPinCount, outputPinCount) - 1) * gapY + paddingY * 2; + const y = node.position.y - height / 2; + + for (const nodePin of nodePins) { + const componentPin = nullthrows( + store.componentPins.get(nodePin.componentPinId) + ); + const position = nullthrows(nodePinPositionById.get(nodePin.id)); + if (componentPin.type === "input") { + position.y -= ((inputPinCount - 1) * gapY) / 2; + } else { + position.y -= ((outputPinCount - 1) * gapY) / 2; + } + } + + return { + x, + y, + width, + height, + nodePinPositionById, + }; +} diff --git a/src/pages/edit/Editor/renderer/Node.tsx b/src/pages/edit/Editor/renderer/Node.tsx new file mode 100644 index 0000000..115d5fd --- /dev/null +++ b/src/pages/edit/Editor/renderer/Node.tsx @@ -0,0 +1,114 @@ +import nullthrows from "nullthrows"; +import { useState } from "react"; +import * as matrix from "transformation-matrix"; +import type { CCNodeId } from "../../../../store/node"; +import { useNode } from "../../../../store/react/selectors"; +import { useStore } from "../../../../store/react"; +import { useComponentEditorStore } from "../store"; +import CCComponentEditorRendererNodePin from "./NodePin"; +import getCCComponentEditorRendererNodeGeometry from "./Node.geometry"; +import ensureStoreItem from "../../../../store/react/error"; +import { blackColor, primaryColor, whiteColor } from "../../../../common/theme"; + +export type CCComponentEditorRendererNodeProps = { + nodeId: CCNodeId; +}; +const CCComponentEditorRendererNode = ensureStoreItem( + (props, store) => store.nodes.get(props.nodeId), + ({ nodeId }: CCComponentEditorRendererNodeProps) => { + const { store } = useStore(); + const node = useNode(nodeId); + const component = nullthrows(store.components.get(node.componentId)); + const geometry = getCCComponentEditorRendererNodeGeometry(store, nodeId); + const componentEditorState = useComponentEditorStore()(); + const [dragging, setDragging] = useState(false); + const [dragStartPosition, setDragStartPosition] = useState({ x: 0, y: 0 }); + const [previousNodePosition, setPreviousNodePosition] = useState({ + x: 0, + y: 0, + }); + + const handleDragStart = (e: React.PointerEvent) => { + setDragStartPosition({ + x: e.nativeEvent.offsetX, + y: e.nativeEvent.offsetY, + }); + setPreviousNodePosition({ + x: node.position.x, + y: node.position.y, + }); + setDragging(true); + e.currentTarget.setPointerCapture(e.pointerId); + }; + + const handleDragging = (e: React.PointerEvent) => { + if (dragging) { + const { sx, sy } = matrix.decomposeTSR( + componentEditorState.getInverseViewTransformation() + ).scale; + const transformation = matrix.scale(sx, sy); + const diff = matrix.applyToPoint(transformation, { + x: e.nativeEvent.offsetX - dragStartPosition.x, + y: e.nativeEvent.offsetY - dragStartPosition.y, + }); + store.nodes.update(nodeId, { + position: { + x: previousNodePosition.x + diff.x, + y: previousNodePosition.y + diff.y, + }, + variablePins: node.variablePins, + }); + } + }; + + const handleDragEnd = (e: React.PointerEvent) => { + setDragging(false); + e.currentTarget.releasePointerCapture(e.pointerId); + }; + + return ( + <> + + {component.name} + + { + componentEditorState.selectNode([nodeId], true); + }} + onContextMenu={(e) => { + e.preventDefault(); + componentEditorState.selectNode([nodeId], true); + componentEditorState.openContextMenu(e); + }} + /> + {store.nodePins.getManyByNodeId(nodeId).map((nodePin) => { + const position = nullthrows( + geometry.nodePinPositionById.get(nodePin.id) + ); + return ( + + ); + })} + + ); + } +); +export default CCComponentEditorRendererNode; diff --git a/src/pages/edit/Editor/renderer/NodePin.tsx b/src/pages/edit/Editor/renderer/NodePin.tsx new file mode 100644 index 0000000..ec7a30d --- /dev/null +++ b/src/pages/edit/Editor/renderer/NodePin.tsx @@ -0,0 +1,187 @@ +import { useState, type PointerEvent, type ReactNode } from "react"; +import * as matrix from "transformation-matrix"; +import { KDTree } from "mnemonist"; +import nullthrows from "nullthrows"; +import type { Point } from "../../../../common/types"; +import type { CCNodePinId } from "../../../../store/nodePin"; +import { CCComponentEditorRendererConnectionCore } from "./Connection"; +import { useComponentEditorStore } from "../store"; +import { useStore } from "../../../../store/react"; +import getCCComponentEditorRendererNodeGeometry from "./Node.geometry"; +import { CCConnectionStore } from "../../../../store/connection"; +import type { SimulationValue } from "../store/slices/core"; + +const NODE_PIN_POSITION_SENSITIVITY = 10; + +export type CCComponentEditorRendererNodeProps = { + nodePinId: CCNodePinId; + position: Point; +}; +export default function CCComponentEditorRendererNodePin({ + nodePinId, + position, +}: CCComponentEditorRendererNodeProps) { + const { store } = useStore(); + const nodePin = nullthrows(store.nodePins.get(nodePinId)); + const node = nullthrows(store.nodes.get(nodePin.nodeId)); + const componentPin = nullthrows( + store.componentPins.get(nodePin.componentPinId) + ); + + const inverseViewTransformation = useComponentEditorStore()((s) => + s.getInverseViewTransformation() + ); + const [draggingState, setDraggingState] = useState<{ + cursorPosition: Point; + nodePinPositionKDTree: KDTree; + } | null>(null); + const onDrag = (e: PointerEvent) => { + let nodePinPositionKDTree = draggingState?.nodePinPositionKDTree; + if (!nodePinPositionKDTree) { + const nodes = store.nodes.getManyByParentComponentId( + node.parentComponentId + ); + nodePinPositionKDTree = KDTree.from( + nodes + .filter((yourNode) => yourNode.id !== node.id) + .flatMap((yourNode) => [ + ...getCCComponentEditorRendererNodeGeometry(store, yourNode.id) + .nodePinPositionById, + ]) + .flatMap(([yourNodePinId, yourNodePinPosition]) => { + const yourNodePin = nullthrows(store.nodePins.get(yourNodePinId)); + const yourComponentPin = nullthrows( + store.componentPins.get(yourNodePin.componentPinId) + ); + if (yourComponentPin.type === componentPin.type) return []; + return [ + [yourNodePinId, [yourNodePinPosition.x, yourNodePinPosition.y]], + ] as const; + }), + 2 + ); + } + setDraggingState({ + cursorPosition: matrix.applyToPoint(inverseViewTransformation, { + x: e.nativeEvent.offsetX, + y: e.nativeEvent.offsetY, + }), + nodePinPositionKDTree, + }); + }; + + let draggingView: ReactNode = null; + let nodePinIdToConnect: CCNodePinId | null = null; + if (draggingState) { + const nearestNodePinId = + draggingState.nodePinPositionKDTree.nearestNeighbor([ + draggingState.cursorPosition.x, + draggingState.cursorPosition.y, + ]); + const nearestNodePin = nullthrows(store.nodePins.get(nearestNodePinId)); + const nearestNodePinPosition = nullthrows( + getCCComponentEditorRendererNodeGeometry( + store, + nearestNodePin.nodeId + ).nodePinPositionById.get(nearestNodePinId) + ); + const distance = Math.hypot( + nearestNodePinPosition.x - draggingState.cursorPosition.x, + nearestNodePinPosition.y - draggingState.cursorPosition.y + ); + if (distance < NODE_PIN_POSITION_SENSITIVITY) { + nodePinIdToConnect = nearestNodePinId; + } + draggingView = ( + + ); + } + + const isSimulationMode = useComponentEditorStore()( + (s) => s.editorMode === "play" + ); + const hasNoConnection = + store.connections.getConnectionsByNodePinId(nodePinId).length === 0; + + const componentEditorStore = useComponentEditorStore()(); + const pinType = componentPin.type; + const simulationValueToString = (simulationValue: SimulationValue) => { + return simulationValue.reduce( + (acm, currentValue) => acm + (currentValue === true ? "1" : "0"), + "" + ); + }; + const implementedComponentPin = + store.componentPins.getByImplementation(nodePinId); + let nodePinValueInit = null; + if (isSimulationMode && hasNoConnection) { + if (pinType === "input") { + nodePinValueInit = componentEditorStore.getInputValue( + implementedComponentPin!.id + )!; + } else { + nodePinValueInit = componentEditorStore.getNodePinValue(nodePinId)!; + } + } + const nodePinValue = nodePinValueInit; + const updateInputValue = () => { + const updatedPinValue = [...nodePinValue!]; + updatedPinValue[0] = !updatedPinValue[0]; + componentEditorStore.setInputValue( + implementedComponentPin!.id, + updatedPinValue + ); + }; + + return ( + <> + {isSimulationMode && hasNoConnection && ( + + {simulationValueToString(nodePinValue!)} + + )} + { + e.currentTarget.setPointerCapture(e.pointerId); + onDrag(e); + }} + onPointerMove={draggingState ? onDrag : undefined} + onPointerUp={() => { + if (!nodePinIdToConnect) return; + const route = { + input: { from: nodePinIdToConnect, to: nodePin.id }, + output: { from: nodePin.id, to: nodePinIdToConnect }, + }[componentPin.type]; + store.connections.register( + CCConnectionStore.create({ + parentComponentId: node.parentComponentId, + ...route, + bentPortion: 0.5, + }) + ); + }} + onLostPointerCapture={() => { + setDraggingState(null); + }} + /> + {draggingView} + + ); +} diff --git a/src/pages/edit/Editor/renderer/base.ts b/src/pages/edit/Editor/renderer/base.ts deleted file mode 100644 index 90930ff..0000000 --- a/src/pages/edit/Editor/renderer/base.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable max-classes-per-file */ - -import type CCStore from "../../../../store"; -import type { ComponentEditorStore } from "../store"; - -export type CCComponentEditorRendererContext = { - store: CCStore; - componentEditorStore: ComponentEditorStore; - overlayArea: HTMLElement; -}; - -export interface CCComponentEditorRenderer { - destroy(): void; -} - -export default abstract class CCComponentEditorRendererBase - implements CCComponentEditorRenderer -{ - protected context: CCComponentEditorRendererContext; - - #childRenderers = new Set(); - - constructor(context: CCComponentEditorRendererContext) { - this.context = context; - } - - registerChildRenderer(renderer: CCComponentEditorRenderer) { - this.#childRenderers.add(renderer); - } - - unregisterChildRenderer(renderer: CCComponentEditorRenderer) { - this.#childRenderers.delete(renderer); - renderer.destroy(); - } - - destroy() { - for (const childRenderer of this.#childRenderers) { - childRenderer.destroy(); - } - } -} diff --git a/src/pages/edit/Editor/renderer/componentPin.ts b/src/pages/edit/Editor/renderer/componentPin.ts deleted file mode 100644 index e86cc81..0000000 --- a/src/pages/edit/Editor/renderer/componentPin.ts +++ /dev/null @@ -1,239 +0,0 @@ -import * as PIXI from "pixi.js"; -import invariant from "tiny-invariant"; -import type { EditorModePlay } from "../store"; -import type { CCNodeId } from "../../../../store/node"; -import type { CCComponentPinId } from "../../../../store/componentPin"; -import { - activeColor, - editorGridColor, - errorColor, - grayColor, - whiteColor, -} from "../../../../common/theme"; -import { CCComponentEditorRendererTextBox } from "./textBox"; -import type { CCComponentEditorRendererContext } from "./base"; -import CCComponentEditorRendererBase from "./base"; - -type CCComponentEditorRendererComponentPinProps = { - context: CCComponentEditorRendererContext; - pixiParentContainer: PIXI.Container; - nodeId: CCNodeId; // TODO: this might be unnecessary - pinId: CCComponentPinId; - position: PIXI.Point; -}; - -/** - * Class for rendering component pin - */ -export default class CCComponentEditorRendererComponentPin extends CCComponentEditorRendererBase { - readonly #componentPinId: CCComponentPinId; - - position: PIXI.Point; - - readonly #pixiParentContainer: PIXI.Container; - - readonly #pixiContainer: PIXI.Container; - - readonly #pixiGraphics: PIXI.Graphics; - - readonly #pixiLabelTextBox: CCComponentEditorRendererTextBox; - - readonly #pixiValueText: PIXI.Text; - - readonly #unsubscribeComponentEditorStore: () => void; - - #valueBoxWidth: number; - - private static readonly drawingConstants = { - marginToNode: 20, - marginToValueBox: 10, - fontSize: 16, - valueColor: whiteColor, - valueBoxWidthUnit: 40, - valueBoxHeight: 20, - valueBoxRadius: 1000, - } as const; - - /** - * Constructor of CCComponentEditorRendererComponentPin - * @param props - */ - constructor(props: CCComponentEditorRendererComponentPinProps) { - super(props.context); - this.#componentPinId = props.pinId; - this.position = props.position; - this.#pixiParentContainer = props.pixiParentContainer; - this.#pixiContainer = new PIXI.Container(); - this.#pixiParentContainer.addChild(this.#pixiContainer); - this.#pixiGraphics = new PIXI.Graphics(); - if ( - this.context.store.componentPins.get(this.#componentPinId)!.type === - "input" - ) { - // this.#pixiGraphics.interactive = true; - this.#pixiGraphics.eventMode = "dynamic"; - this.#pixiGraphics.cursor = "pointer"; - this.#pixiGraphics.on("pointerdown", this.onClick); - } - this.#pixiContainer.addChild(this.#pixiGraphics); - this.#pixiLabelTextBox = new CCComponentEditorRendererTextBox({ - context: this.context, - pixiParentContainer: this.#pixiContainer, - }); - this.#pixiLabelTextBox.onChange = (value) => { - this.context.store.componentPins.update(this.#componentPinId, { - name: value, - }); - }; - this.registerChildRenderer(this.#pixiLabelTextBox); - this.#pixiLabelTextBox.fontSize = - CCComponentEditorRendererComponentPin.drawingConstants.fontSize; - this.#pixiValueText = new PIXI.Text(); - this.#pixiValueText.style.fontSize = - CCComponentEditorRendererComponentPin.drawingConstants.fontSize; - this.#pixiValueText.style.fill = - CCComponentEditorRendererComponentPin.drawingConstants.valueColor; - this.#pixiValueText.anchor.set(0.5, 0.5); - if ( - this.context.store.componentPins.get(this.#componentPinId)!.type === - "input" - ) { - // this.#pixiValueText.interactive = true; - this.#pixiValueText.eventMode = "dynamic"; - this.#pixiValueText.cursor = "pointer"; - this.#pixiValueText.on("pointerdown", this.onClick); - } - this.#pixiContainer.addChild(this.#pixiValueText); - this.context.store.components.on("didUpdate", this.render); - this.#unsubscribeComponentEditorStore = - this.context.componentEditorStore.subscribe(this.render); - this.#valueBoxWidth = - CCComponentEditorRendererComponentPin.drawingConstants.valueBoxWidthUnit; - this.context.store.componentPins.on("didUpdate", (pin) => { - if (pin.id === this.#componentPinId) this.render(); - }); - this.render(); - } - - /** - * Event handler for clicking the pin - * @param e event - */ - onClick = (e: PIXI.FederatedPointerEvent) => { - const componentPin = this.context.store.componentPins.get( - this.#componentPinId - ); - invariant(componentPin); - invariant(componentPin.implementation); - const editorState = this.context.componentEditorStore.getState(); - const previousValue = editorState.getInputValue(this.#componentPinId); - invariant(previousValue); - const increaseValue = (value: boolean[]) => { - const newValue = [...value]; - for (let i = newValue.length - 1; i >= 0; i -= 1) { - newValue[i] = !newValue[i]; - if (newValue[i]) break; - } - return newValue; - }; - editorState.setInputValue( - this.#componentPinId, - increaseValue(previousValue) - ); - e.preventDefault(); - }; - - /** - * Render the pin - */ - render = () => { - const pin = this.context.store.componentPins.get(this.#componentPinId); - if (!pin) return; - - this.#pixiGraphics.clear(); - const editorState = this.context.componentEditorStore.getState(); - this.#pixiContainer.position = this.position; - this.#pixiLabelTextBox.value = pin.name; - const c = CCComponentEditorRendererComponentPin.drawingConstants; - if (editorState.editorMode === "edit") { - this.#pixiLabelTextBox.isEditable = true; - this.#pixiValueText.visible = false; - this.#valueBoxWidth = c.valueBoxWidthUnit; - this.#pixiGraphics.lineStyle(1, editorGridColor); - this.#pixiGraphics.beginFill(whiteColor); - - this.#pixiLabelTextBox.alignment = "center"; - this.#pixiLabelTextBox.position.set( - (c.marginToNode + c.valueBoxWidthUnit / 2) * - (pin.type === "input" ? -1 : 1), - 0 - ); - } else if (editorState.editorMode === "play") { - invariant((editorState.editorMode satisfies EditorModePlay) === "play"); - this.#pixiLabelTextBox.isEditable = false; - if (pin.type === "input") { - this.#valueBoxWidth = c.valueBoxWidthUnit; - const input = editorState.getInputValue(this.#componentPinId); - invariant(input); - this.#pixiValueText.text = input.map((v) => (v ? "1" : "0")).join(""); - this.#pixiGraphics.beginFill(activeColor); - } else { - const createValueText = (values: boolean[]) => { - let valueText = ""; - for (let i = 0; i < values.length; i += 1) { - valueText += values[i] ? "1" : "0"; - } - return valueText; - }; - const outputValue = editorState.getComponentPinValue( - this.#componentPinId - ); - if (outputValue) { - this.#pixiValueText.text = createValueText(outputValue); - this.#valueBoxWidth = - c.valueBoxWidthUnit + - ((outputValue.length - 1) * c.valueBoxWidthUnit) / 4; - this.#pixiGraphics.beginFill(grayColor.darken2); - } else { - this.#pixiValueText.text = ""; - this.#pixiGraphics.beginFill(errorColor); - } - } - this.#pixiLabelTextBox.alignment = - pin.type === "input" ? "right" : "left"; - this.#pixiLabelTextBox.position.set( - (c.marginToNode + this.#valueBoxWidth + c.marginToValueBox) * - (pin.type === "input" ? -1 : 1), - 0 - ); - this.#pixiValueText.visible = true; - this.#pixiValueText.position.set( - (c.marginToNode + this.#valueBoxWidth / 2) * - (pin.type === "input" ? -1 : 1), - 0 - ); - } - this.#pixiLabelTextBox.render(); - - this.#pixiGraphics.drawRoundedRect( - pin.type === "input" - ? -c.valueBoxWidthUnit - c.marginToNode - : c.marginToNode, - -c.valueBoxHeight / 2, - this.#valueBoxWidth, - c.valueBoxHeight, - c.valueBoxRadius - ); - this.#pixiGraphics.endFill(); - }; - - /** - * Destroy the pin - */ - override destroy() { - this.#pixiParentContainer.removeChild(this.#pixiContainer); - this.context.store.components.off("didUpdate", this.render); - this.#unsubscribeComponentEditorStore(); - super.destroy(); - } -} diff --git a/src/pages/edit/Editor/renderer/connection.ts b/src/pages/edit/Editor/renderer/connection.ts deleted file mode 100644 index bd33aad..0000000 --- a/src/pages/edit/Editor/renderer/connection.ts +++ /dev/null @@ -1,389 +0,0 @@ -import * as PIXI from "pixi.js"; -import nullthrows from "nullthrows"; -import type { CCConnectionId } from "../../../../store/connection"; -import type CCStore from "../../../../store"; -import CCComponentEditorRendererNode from "./node"; -import type { EditorMode } from "../store"; -import CCComponentEditorRendererBase, { - type CCComponentEditorRendererContext, -} from "./base"; - -export type CCConnectionEndpoint = { - nodeId: string; - pinId: string; -}; - -const lineWidth = 2; -const lineColor = 0x000000; - -/** - * Class for rendering connection - */ -export default class CCComponentEditorRendererConnection extends CCComponentEditorRendererBase { - #store: CCStore; - - #connectionId: CCConnectionId; - - #pixiParentContainer: PIXI.Container; - - #pixiGraphics: { - from: PIXI.Graphics; - middle: PIXI.Graphics; - to: PIXI.Graphics; - value: PIXI.Text; - }; - - #valueText = ""; - - #bentPortionCache: number; - - #temporaryBentPortion: number; - - #offset: number; - - #onDragStart: (e: PIXI.FederatedMouseEvent) => void; - - /** - * Constructor of CCComponentEditorRendererConnection - * @param store store - * @param connectionId id of connection - * @param pixiParentContainer parent container of pixi - * @param componentEditorStore store of component editor - * @param onDragStart function to be called when drag starts - * @param getPinValue function to get value of pin - * @returns instance of CCComponentEditorRendererConnection - */ - constructor( - store: CCStore, - connectionId: CCConnectionId, - pixiParentContainer: PIXI.Container, - context: CCComponentEditorRendererContext, - onDragStart: (e: PIXI.FederatedMouseEvent) => void - ) { - super(context); - this.#store = store; - this.#connectionId = connectionId; - this.#onDragStart = onDragStart; - this.#pixiGraphics = { - from: CCComponentEditorRendererConnection.#createGraphics(), - middle: CCComponentEditorRendererConnection.#createGraphics(), - to: CCComponentEditorRendererConnection.#createGraphics(), - value: new PIXI.Text(this.#valueText, { fontSize: 18 }), - }; - this.#pixiGraphics.value.visible = false; - this.#pixiParentContainer = pixiParentContainer; - this.#pixiParentContainer.addChild(this.#pixiGraphics.from); - this.#pixiParentContainer.addChild(this.#pixiGraphics.middle); - this.#pixiParentContainer.addChild(this.#pixiGraphics.to); - this.#pixiParentContainer.addChild(this.#pixiGraphics.value); - - this.#pixiGraphics.from.on("pointerdown", (e) => { - if (e.button === 2) { - this.onPointerDown(e); - } - }); - this.#pixiGraphics.middle.on("pointerdown", (e) => { - if (e.button === 2) { - this.onPointerDown(e); - } else if (e.button === 0) { - this.#onDragStart(e); - e.stopPropagation(); - } - }); - this.#pixiGraphics.to.on("pointerdown", (e) => { - if (e.button === 2) { - this.onPointerDown(e); - } - }); - const editValueText = (mode: EditorMode) => { - if (mode === "play") { - const connection = this.#store.connections.get(this.#connectionId)!; - const inputValue = this.context.componentEditorStore - .getState() - .getNodePinValue(connection.from)!; - const value = inputValue.map((v) => (v ? "1" : "0")).join(""); - this.#pixiGraphics.value.text = value || ""; - this.#pixiGraphics.value.visible = true; - } else { - this.#pixiGraphics.value.visible = false; - } - }; - this.#pixiGraphics.from.on("mouseover", () => { - editValueText(this.context.componentEditorStore.getState().editorMode); - }); - this.#pixiGraphics.to.on("mouseover", () => { - editValueText(this.context.componentEditorStore.getState().editorMode); - }); - this.#pixiGraphics.middle.on("mouseover", () => { - editValueText(this.context.componentEditorStore.getState().editorMode); - }); - this.#pixiGraphics.from.on("mouseout", () => { - this.#pixiGraphics.value.visible = false; - }); - this.#pixiGraphics.middle.on("mouseout", () => { - this.#pixiGraphics.value.visible = false; - }); - this.#pixiGraphics.to.on("mouseout", () => { - this.#pixiGraphics.value.visible = false; - }); - this.#bentPortionCache = this.#store.connections.get( - this.#connectionId - )!.bentPortion; - this.#temporaryBentPortion = this.#bentPortionCache; - this.#offset = 0; - this.#render(); - this.#store.nodes.on("didUpdate", this.#render); - } - - /** - * Event handler for pointer down - * @param e event - */ - onPointerDown(e: PIXI.FederatedEvent) { - if ( - !this.context.componentEditorStore - .getState() - .selectedConnectionIds.has(this.#connectionId) - ) { - this.context.componentEditorStore - .getState() - .selectConnection([this.#connectionId], false); - } - e.stopPropagation(); - } - - /** - * Event handler for drag end - */ - onDragEnd() { - const fromEndPoint = nullthrows( - this.#store.connections.get(this.#connectionId) - ).from; - const toEndPoint = nullthrows( - this.#store.connections.get(this.#connectionId) - ).to; - const fromPosition = CCComponentEditorRendererNode.getNodePinAbsolute( - this.#store, - fromEndPoint - ); - const toPosition = CCComponentEditorRendererNode.getNodePinAbsolute( - this.#store, - toEndPoint - ); - const diffX = toPosition.x - fromPosition.x; - this.#bentPortionCache = this.#temporaryBentPortion + this.#offset / diffX; - if (this.#bentPortionCache > 1) { - this.#bentPortionCache = 1; - } else if (this.#bentPortionCache < 0) { - this.#bentPortionCache = 0; - } - this.#temporaryBentPortion = this.#bentPortionCache; - this.#store.connections.get(this.#connectionId)!.bentPortion = - this.#bentPortionCache; - // e.stopPropagation(); - } - - /** - * Create new graphics - * @returns new graphics - */ - static #createGraphics() { - const graphics = new PIXI.Graphics(); - // graphics.interactive = true; - graphics.eventMode = "dynamic"; - graphics.cursor = "pointer"; - graphics.lineStyle(lineWidth, lineColor); - return graphics; - } - - /** - * Destroy the connection - */ - override destroy() { - this.#pixiParentContainer.removeChild(this.#pixiGraphics.from); - this.#pixiParentContainer.removeChild(this.#pixiGraphics.middle); - this.#pixiParentContainer.removeChild(this.#pixiGraphics.to); - - this.#pixiGraphics.from.destroy(); - this.#pixiGraphics.to.destroy(); - this.#pixiGraphics.middle.destroy(); - this.#store.nodes.off("didUpdate", this.#render); - } - - /** - * Update bent portion - * @param offset new offset - */ - updateBentPortion(offset: number) { - this.#offset = offset; - const fromEndPoint = nullthrows( - this.#store.connections.get(this.#connectionId)?.from - ); - const toEndPoint = nullthrows( - this.#store.connections.get(this.#connectionId)?.to - ); - const fromPosition = CCComponentEditorRendererNode.getNodePinAbsolute( - this.#store, - fromEndPoint - ); - const toPosition = CCComponentEditorRendererNode.getNodePinAbsolute( - this.#store, - toEndPoint - ); - const diffX = toPosition.x - fromPosition.x; - this.#bentPortionCache = this.#temporaryBentPortion + offset / diffX; - if (this.#bentPortionCache > 1) { - this.#bentPortionCache = 1; - } else if (this.#bentPortionCache < 0) { - this.#bentPortionCache = 0; - } - this.#render(); - } - - /** - * Render the connection - */ - #render = () => { - const connection = this.#store.connections.get(this.#connectionId); - if (!connection) return; - this.#pixiGraphics.from.clear(); - this.#pixiGraphics.middle.clear(); - this.#pixiGraphics.to.clear(); - this.#pixiGraphics.from.lineStyle(lineWidth, lineColor); - this.#pixiGraphics.middle.lineStyle(lineWidth, lineColor); - this.#pixiGraphics.to.lineStyle(lineWidth, lineColor); - const endPointGap = 9; - const fromEndPoint = nullthrows(connection.from); - const toEndPoint = nullthrows(connection.to); - const fromPosition = CCComponentEditorRendererNode.getNodePinAbsolute( - this.#store, - fromEndPoint - ); - const toPosition = CCComponentEditorRendererNode.getNodePinAbsolute( - this.#store, - toEndPoint - ); - const diffX = toPosition.x - fromPosition.x; - // const diffY = toPosition.y - fromPosition.y; - this.#pixiGraphics.from.beginFill(lineColor); - this.#pixiGraphics.from.moveTo( - fromPosition.x + endPointGap, - fromPosition.y - lineWidth / 2 - ); - this.#pixiGraphics.from.lineTo( - fromPosition.x + this.#bentPortionCache * diffX, - fromPosition.y - lineWidth / 2 - ); - this.#pixiGraphics.from.lineTo( - fromPosition.x + this.#bentPortionCache * diffX, - fromPosition.y + lineWidth / 2 - ); - this.#pixiGraphics.from.lineTo( - fromPosition.x + endPointGap, - fromPosition.y + lineWidth / 2 - ); - this.#pixiGraphics.from.endFill(); - - const fromHitArea = new PIXI.Polygon( - new PIXI.Point( - fromPosition.x + endPointGap, - fromPosition.y - 2 * lineWidth - ), - new PIXI.Point( - fromPosition.x + this.#bentPortionCache * diffX, - fromPosition.y - 2 * lineWidth - ), - new PIXI.Point( - fromPosition.x + this.#bentPortionCache * diffX, - fromPosition.y + 2 * lineWidth - ), - new PIXI.Point( - fromPosition.x + endPointGap, - fromPosition.y + 2 * lineWidth - ) - ); - this.#pixiGraphics.from.hitArea = fromHitArea; - - this.#pixiGraphics.value.x = - fromPosition.x + this.#bentPortionCache * diffX - lineWidth / 2; - - this.#pixiGraphics.value.y = - fromPosition.y < toPosition.y - ? fromPosition.y - 20 - : toPosition.y - 20 - lineWidth / 2; - - this.#pixiGraphics.middle.beginFill(lineColor); - this.#pixiGraphics.middle.moveTo( - fromPosition.x + this.#bentPortionCache * diffX - lineWidth / 2, - fromPosition.y + (fromPosition.y < toPosition.y ? -lineWidth : lineWidth) - ); - this.#pixiGraphics.middle.lineTo( - fromPosition.x + this.#bentPortionCache * diffX - lineWidth / 2, - toPosition.y + - (fromPosition.y < toPosition.y ? lineWidth / 2 : -lineWidth / 2) - ); - this.#pixiGraphics.middle.lineTo( - fromPosition.x + this.#bentPortionCache * diffX + lineWidth / 2, - toPosition.y + - (fromPosition.y < toPosition.y ? lineWidth / 2 : -lineWidth / 2) - ); - this.#pixiGraphics.middle.lineTo( - fromPosition.x + this.#bentPortionCache * diffX + lineWidth / 2, - fromPosition.y + (fromPosition.y < toPosition.y ? -lineWidth : lineWidth) - ); - this.#pixiGraphics.middle.endFill(); - - const middleHitArea = new PIXI.Polygon( - new PIXI.Point( - fromPosition.x + this.#bentPortionCache * diffX - 2 * lineWidth, - fromPosition.y - ), - new PIXI.Point( - fromPosition.x + this.#bentPortionCache * diffX + 2 * lineWidth, - fromPosition.y - ), - new PIXI.Point( - fromPosition.x + this.#bentPortionCache * diffX, - toPosition.y - ), - new PIXI.Point( - fromPosition.x + this.#bentPortionCache * diffX - 2 * lineWidth, - toPosition.y - ) - ); - this.#pixiGraphics.middle.hitArea = middleHitArea; - - this.#pixiGraphics.to.beginFill(lineColor); - this.#pixiGraphics.to.moveTo( - fromPosition.x + this.#bentPortionCache * diffX, - toPosition.y - lineWidth / 2 - ); - this.#pixiGraphics.to.lineTo( - toPosition.x - endPointGap, - toPosition.y - lineWidth / 2 - ); - this.#pixiGraphics.to.lineTo( - toPosition.x - endPointGap, - toPosition.y + lineWidth / 2 - ); - this.#pixiGraphics.to.lineTo( - fromPosition.x + this.#bentPortionCache * diffX, - toPosition.y + lineWidth / 2 - ); - this.#pixiGraphics.to.endFill(); - - const toHitArea = new PIXI.Polygon( - new PIXI.Point(toPosition.x - endPointGap, toPosition.y - 2 * lineWidth), - new PIXI.Point( - fromPosition.x + this.#bentPortionCache * diffX, - toPosition.y - 2 * lineWidth - ), - new PIXI.Point( - fromPosition.x + this.#bentPortionCache * diffX, - toPosition.y + 2 * lineWidth - ), - new PIXI.Point(toPosition.x - endPointGap, toPosition.y + 2 * lineWidth) - ); - this.#pixiGraphics.to.hitArea = toHitArea; - }; -} diff --git a/src/pages/edit/Editor/renderer/index.tsx b/src/pages/edit/Editor/renderer/index.tsx index 052596f..bb62e49 100644 --- a/src/pages/edit/Editor/renderer/index.tsx +++ b/src/pages/edit/Editor/renderer/index.tsx @@ -1,498 +1,67 @@ -import * as PIXI from "pixi.js"; -import invariant from "tiny-invariant"; -import { editorBackgroundColor } from "../../../../common/theme"; -import type { CCComponentId } from "../../../../store/component"; -import type { CCNode, CCNodeId } from "../../../../store/node"; -import CCComponentEditorRendererNode from "./node"; +import * as matrix from "transformation-matrix"; +import { parseDataTransferAsComponent } from "../../../../common/serialization"; import { - CCConnectionStore, - type CCConnection, - type CCConnectionId, -} from "../../../../store/connection"; -import CCComponentEditorRendererConnection from "./connection"; -import CCComponentEditorRendererRangeSelect from "./rangeSelect"; -import type { CCComponentPinId } from "../../../../store/componentPin"; -// import CCSimulator from "./simulator"; -import type { CCComponentEditorRendererContext } from "./base"; -import CCComponentEditorRendererBase from "./base"; -import type { CCNodePinId } from "../../../../store/nodePin"; - -type DragState = { - startPosition: PIXI.Point; - target: - | { type: "world"; initialCenter: PIXI.Point } - | { - type: "node"; - nodeId: CCNodeId; - initialPosition: Map; - } - | { - type: "pin"; - pinId: CCComponentPinId; - nodeId: CCNodeId; - initialPosition: PIXI.Point; - } - | { type: "rangeSelect"; initialPosition: PIXI.Point } - | { - type: "connection"; - connectionId: CCConnectionId; - initialPosition: PIXI.Point; - }; -}; - -export type CCComponentEditorRendererProps = { - context: CCComponentEditorRendererContext; - componentId: CCComponentId; - htmlContainer: HTMLDivElement; - onContextMenu: (position: PIXI.Point) => void; -}; - -/** - * Class for rendering component editor - */ -export default class CCComponentEditorRenderer extends CCComponentEditorRendererBase { - #unsubscribeComponentEditorStore: () => void; - - #componentId: CCComponentId; - - #htmlContainer: HTMLDivElement; - - #pixiApplication: PIXI.Application; - - #pixiWorld: PIXI.Container; - - #nodeRenderers = new Map(); - - #connectionRenderers = new Map< - CCConnectionId, - CCComponentEditorRendererConnection - >(); - - #rangeSelectRenderer: CCComponentEditorRendererRangeSelect; - - // #gridRenderer = null; - - #dragState: DragState | null = null; - - #resizeObserver: ResizeObserver; - - #creatingConnectionPixiGraphics: PIXI.Graphics; - - // #simulator: CCSimulator; - - /** - * Constructor of CCComponentEditorRenderer - * @param props - */ - constructor(props: CCComponentEditorRendererProps) { - super(props.context); - this.#componentId = props.componentId; - this.#htmlContainer = props.htmlContainer; - // this.#simulator = new CCSimulator({ - // store: this.context.store, - // componentEditorStore: this.context.componentEditorStore, - // componentId: this.#componentId, - // }); - invariant(this.context.store.components.get(props.componentId)); - - const rect = this.#htmlContainer.getBoundingClientRect(); - this.context.componentEditorStore - .getState() - .setCanvasSize(new PIXI.Point(rect.width, rect.height)); - this.#resizeObserver = new ResizeObserver(([entry]) => { - const contentRect = entry?.contentRect; - if (!contentRect) return; - this.context.componentEditorStore - .getState() - .setCanvasSize(new PIXI.Point(contentRect.width, contentRect.height)); - }); - this.#resizeObserver.observe(this.#htmlContainer); - - this.#pixiApplication = new PIXI.Application({ - resizeTo: this.#htmlContainer, - background: editorBackgroundColor, - resolution: window.devicePixelRatio, - autoDensity: true, - antialias: true, - }); - this.#htmlContainer.appendChild( - this.#pixiApplication.view as HTMLCanvasElement - ); - this.#pixiWorld = new PIXI.Container(); - this.#creatingConnectionPixiGraphics = new PIXI.Graphics(); - this.#pixiWorld.addChild(this.#creatingConnectionPixiGraphics); - this.#rangeSelectRenderer = new CCComponentEditorRendererRangeSelect({ - store: this.context.store, - componentEditorStore: this.context.componentEditorStore, - pixiParentContainer: this.#pixiWorld, - }); - this.#pixiApplication.stage.addChild(this.#pixiWorld); - // this.#pixiApplication.stage.interactive = true; - this.#pixiApplication.stage.eventMode = "dynamic"; - this.#pixiApplication.stage.hitArea = { contains: () => true }; // Capture events everywhere - this.#pixiApplication.stage.on("pointerdown", (e) => { - const { worldPerspective, toWorldPosition } = - this.context.componentEditorStore.getState(); - if (e.button === 2) { - this.#dragState = { - startPosition: e.global.clone(), - target: { - type: "world", - initialCenter: worldPerspective.center, - }, - }; - } else if (e.button === 0) { - const position = toWorldPosition(e.global.clone()); - this.context.componentEditorStore - .getState() - .setRangeSelect({ start: position, end: position }); - this.#dragState = { - startPosition: e.global.clone(), - target: { - type: "rangeSelect", - initialPosition: position, - }, - }; - this.#rangeSelectRenderer.render(); - } - this.context.componentEditorStore.getState().selectNode([], true); - }); - this.#pixiApplication.stage.on("pointermove", (e) => { - if (!this.#dragState) return; - const { worldPerspective, setWorldPerspective } = - this.context.componentEditorStore.getState(); - const dragOffset = e.global - .subtract(this.#dragState.startPosition) - .multiplyScalar(1 / worldPerspective.scale); - switch (this.#dragState.target.type) { - case "world": - setWorldPerspective({ - center: this.#dragState.target.initialCenter.subtract(dragOffset), - scale: worldPerspective.scale, - }); - return; - case "node": { - for (const nodeId of this.context.componentEditorStore.getState() - .selectedNodeIds) { - const initialPosition = this.#dragState.target.initialPosition.get( - nodeId as CCNodeId - )!; - this.context.store.nodes.update(nodeId as CCNodeId, { - position: initialPosition.add(dragOffset), - }); - } + useConnectionIds, + useNodeIds, +} from "../../../../store/react/selectors"; +import { useComponentEditorStore } from "../store"; +import CCComponentEditorRendererBackground from "./Background"; +import CCComponentEditorRendererConnection from "./Connection"; +import CCComponentEditorRendererNode from "./Node"; +import { useStore } from "../../../../store/react"; +import { CCNodeStore } from "../../../../store/node"; + +export default function CCComponentEditorRenderer() { + const componentEditorState = useComponentEditorStore()(); + const { store } = useStore(); + const viewBox = componentEditorState.getViewBox(); + const nodeIds = useNodeIds(componentEditorState.componentId); + const connectionIds = useConnectionIds(componentEditorState.componentId); + + return ( + { + e.preventDefault(); + }} + onDrop={(e) => { + const droppedComponentId = parseDataTransferAsComponent(e.dataTransfer); + if (!droppedComponentId || componentEditorState.editorMode === "play") return; - } - case "pin": { - this.#creatingConnectionPixiGraphics.clear(); - this.#creatingConnectionPixiGraphics.lineStyle(2, 0x696969); - const fromPosition = this.#dragState.target.initialPosition; - const toPosition = fromPosition.add(dragOffset); - this.#creatingConnectionPixiGraphics.moveTo( - fromPosition.x, - fromPosition.y - ); - const diffX = toPosition.x - fromPosition.x; - this.#creatingConnectionPixiGraphics.lineTo( - fromPosition.x + 0.5 * diffX, - fromPosition.y - ); - this.#creatingConnectionPixiGraphics.lineTo( - fromPosition.x + 0.5 * diffX, - toPosition.y - ); - this.#creatingConnectionPixiGraphics.lineTo( - toPosition.x, - toPosition.y - ); - return; - } - case "rangeSelect": { - const start = this.#dragState.target.initialPosition; - const end = this.#dragState.target.initialPosition.add(dragOffset); - this.context.componentEditorStore.getState().setRangeSelect({ - start, - end, - }); - this.#rangeSelectRenderer.render(); - - for (const nodeRenderer of this.#nodeRenderers.values()) { - nodeRenderer.judgeIsRangeSelected(start, end); - } - return; - } - case "connection": { - const fromPosition = this.#dragState.target.initialPosition; - const toPosition = fromPosition.add(dragOffset); - const offset = toPosition.x - fromPosition.x; - const connectionRenderer = this.#connectionRenderers.get( - this.#dragState.target.connectionId - ); - connectionRenderer?.updateBentPortion(offset); - return; - } - default: - throw new Error( - `Unexpected drag target: ${this.#dragState.target satisfies never}` - ); - } - }); - this.#pixiApplication.stage.on("pointerup", () => { - if (this.#dragState?.target.type === "connection") { - const connectionRenderer = this.#connectionRenderers.get( - this.#dragState.target.connectionId - )!; - connectionRenderer.onDragEnd(); - } - this.#dragState = null; - this.context.componentEditorStore.getState().setRangeSelect(null); - if (this.#creatingConnectionPixiGraphics != null) { - this.#creatingConnectionPixiGraphics.clear(); - } - this.#rangeSelectRenderer.render(); - }); - - this.#pixiApplication.stage.on("pointerleave", () => { - this.#dragState = null; - this.context.componentEditorStore.getState().setRangeSelect(null); - this.#rangeSelectRenderer.render(); - }); - - this.context.store.nodes - .getManyByParentComponentId(this.#componentId) - .forEach((node) => this.#addNodeRenderer(node.id)); - this.context.store.nodes.on("didRegister", this.#onNodeAdded); - this.context.store.nodes.on("didUnregister", this.#onNodeRemoved); - - this.context.store.connections - .getConnectionIdsByParentComponentId(this.#componentId) - .forEach((connectionId) => this.#addConnectionRenderer(connectionId)); - this.context.store.connections.on("didRegister", this.#onConnectionAdded); - this.context.store.connections.on( - "didUnregister", - this.#onConnectionRemoved - ); - - // Support zooming - this.#pixiApplication.stage.on("wheel", (e) => { - const { zoom, toWorldPosition } = - this.context.componentEditorStore.getState(); - zoom(toWorldPosition(e.global), 0.999 ** e.deltaY); - }); - - // Context menu - this.#pixiApplication.stage.on("rightclick", (e) => { - const componentEditorState = this.context.componentEditorStore.getState(); - if (componentEditorState.editorMode === "play") return; - props.onContextMenu(e.global.clone()); - e.preventDefault(); - }); - - this.#unsubscribeComponentEditorStore = - this.context.componentEditorStore.subscribe(this.#render); - this.#render(); - } - - /** - * Add node renderer - * @param nodeId id of node - */ - #addNodeRenderer(nodeId: CCNodeId) { - if (this.#nodeRenderers.has(nodeId)) return; - const onDragStart = (e: PIXI.FederatedMouseEvent) => { - const { selectedNodeIds } = this.context.componentEditorStore.getState(); - const initialPosition = new Map(); - for (const selectedNodeId of selectedNodeIds) { - const selectedNode = this.context.store.nodes.get(selectedNodeId)!; - initialPosition.set(selectedNodeId, selectedNode.position.clone()); - } - this.#dragState = { - startPosition: e.global.clone(), - target: { - type: "node", - nodeId, - initialPosition, - }, - }; - }; - const onDragStartPin = ( - e: PIXI.FederatedMouseEvent, - nodePinId: CCNodePinId - ) => { - // const node = this.context.store.nodes.get(nodeId)!; - const { toWorldPosition, editorMode } = - this.context.componentEditorStore.getState(); - // const componentEditorState = this.context.componentEditorStore.getState(); - if (editorMode === "play") return; - const lineWidth = 2; - const lineColor = 0x000000; - // this.#creatingConnectionPixiGraphics.clear(); - this.#pixiWorld.addChild(this.#creatingConnectionPixiGraphics); - this.#creatingConnectionPixiGraphics.lineStyle(lineWidth, lineColor); - const pinPosition = CCComponentEditorRendererNode.getNodePinAbsolute( - this.context.store, - nodePinId - ); - this.#creatingConnectionPixiGraphics.moveTo(pinPosition.x, pinPosition.y); - const { componentPinId } = this.context.store.nodePins.get(nodePinId)!; - this.#dragState = { - startPosition: e.global.clone(), - target: { - type: "pin", - pinId: componentPinId, - nodeId, - initialPosition: toWorldPosition(e.global.clone()), - }, - }; - }; - const onDragEndPin = ( - _: PIXI.FederatedMouseEvent, - nodePinId: CCNodePinId - ) => { - this.#creatingConnectionPixiGraphics?.clear(); - const { componentPinId } = this.context.store.nodePins.get(nodePinId)!; - const pinType = - this.context.store.componentPins.get(componentPinId)?.type; - if (this.#dragState?.target.type === "pin") { - const anotherPinId = this.#dragState.target.pinId; - const anotherPinType = - this.context.store.componentPins.get(anotherPinId)?.type; - const anotherNodeId = this.#dragState.target.nodeId; - const { id: anotherNodePinId } = - this.context.store.nodePins.getByImplementationNodeIdAndPinId( - anotherNodeId, - anotherPinId - ); - if (pinType === "input" && anotherPinType === "output") { - if (this.context.store.connections.hasNoConnectionOf(nodePinId)) { - const newConnection = CCConnectionStore.create({ - to: nodePinId, - from: anotherNodePinId, - parentComponentId: this.#componentId, - bentPortion: 0.5, - }); - this.context.store.connections.register(newConnection); - } - } else if (pinType === "output" && anotherPinType === "input") { - if ( - this.context.store.connections.hasNoConnectionOf(anotherNodePinId) - ) { - const newConnection = CCConnectionStore.create({ - from: nodePinId, - to: anotherNodePinId, - parentComponentId: this.#componentId, - bentPortion: 0.5, - }); - this.context.store.connections.register(newConnection); - } - } - } - this.#dragState = null; - }; - - const newNodeRenderer = new CCComponentEditorRendererNode({ - context: this.context, - nodeId, - pixiParentContainer: this.#pixiWorld, - onDragStart, - onDragStartPin, - onDragEndPin, - }); - this.#nodeRenderers.set(nodeId, newNodeRenderer); - } - - /** - * Add connection renderer - * @param connectionId id of connection - */ - #addConnectionRenderer(connectionId: CCConnectionId) { - const onDragStart = (e: PIXI.FederatedMouseEvent) => { - const { toWorldPosition } = this.context.componentEditorStore.getState(); - this.#dragState = { - startPosition: e.global.clone(), - target: { - type: "connection", - connectionId, - initialPosition: toWorldPosition(e.global.clone()), - }, - }; - }; - const newConnectionRenderer = new CCComponentEditorRendererConnection( - this.context.store, - connectionId, - this.#pixiWorld, - this.context, - onDragStart - ); - this.#connectionRenderers.set(connectionId, newConnectionRenderer); - } - - /** - * Event handler for adding connection - * @param connection connection - */ - #onConnectionAdded = (connection: CCConnection) => { - if (connection.parentComponentId !== this.#componentId) return; - this.#addConnectionRenderer(connection.id); - // this.#simulator.clear(); - }; - - /** - * Event handler for removing connection - * @param connection connection - */ - #onConnectionRemoved = (connection: CCConnection) => { - if (connection.parentComponentId !== this.#componentId) return; - this.#connectionRenderers.get(connection.id)?.destroy(); - this.#connectionRenderers.delete(connection.id); - // this.#simulator.clear(); - }; - - /** - * Event handler for adding node - * @param node node - */ - #onNodeAdded = (node: CCNode) => { - if (node.parentComponentId !== this.#componentId) return; - this.#addNodeRenderer(node.id); - // this.#simulator.clear(); - }; - - /** - * Event handler for removing node - * @param node node - */ - #onNodeRemoved = (node: CCNode) => { - if (node.parentComponentId !== this.#componentId) return; - this.#nodeRenderers.get(node.id)?.destroy(); - this.#nodeRenderers.delete(node.id); - // this.#simulator.clear(); - }; - - /** - * Render the component editor - */ - #render = () => { - const { worldPerspective, toCanvasPosition } = - this.context.componentEditorStore.getState(); - this.#pixiWorld.position = toCanvasPosition(new PIXI.Point(0, 0)); - this.#pixiWorld.scale = { - x: worldPerspective.scale, - y: worldPerspective.scale, - }; - }; - - /** - * Destroy the component editor - */ - // eslint-disable-next-line class-methods-use-this - override destroy() { - super.destroy(); - this.context.store.nodes.off("didRegister", this.#onNodeAdded); - this.context.store.nodes.off("didUnregister", this.#onNodeRemoved); - for (const renderer of this.#nodeRenderers.values()) renderer.destroy(); - this.#rangeSelectRenderer.destroy(); - this.#unsubscribeComponentEditorStore(); - this.#pixiApplication.destroy(true); - } + store.nodes.register( + CCNodeStore.create({ + componentId: droppedComponentId, + parentComponentId: componentEditorState.componentId, + position: matrix.applyToPoint( + componentEditorState.getInverseViewTransformation(), + { + x: e.nativeEvent.offsetX, + y: e.nativeEvent.offsetY, + } + ), + variablePins: [], // todo + }) + ); + }} + > + + {nodeIds.map((nodeId) => ( + + ))} + {connectionIds.map((connectionId) => ( + + ))} + + ); } diff --git a/src/pages/edit/Editor/renderer/node.ts b/src/pages/edit/Editor/renderer/node.ts deleted file mode 100644 index 9558cde..0000000 --- a/src/pages/edit/Editor/renderer/node.ts +++ /dev/null @@ -1,468 +0,0 @@ -// import type { Point } from "pixi.js"; -import * as PIXI from "pixi.js"; -import type { Point } from "pixi.js"; -import nullthrows from "nullthrows"; -import { blackColor, primaryColor, whiteColor } from "../../../../common/theme"; -import type { CCNodeId } from "../../../../store/node"; -import { type CCComponentPinId } from "../../../../store/componentPin"; -import type CCStore from "../../../../store"; -import CCComponentEditorRendererNodePin from "./nodePin"; -import CCComponentEditorRendererComponentPin from "./componentPin"; -import { rearrangeRangeSelect } from "./rangeSelect"; -import CCComponentEditorRendererBase, { - type CCComponentEditorRendererContext, -} from "./base"; -import type { CCNodePinId } from "../../../../store/nodePin"; - -export type CCComponentEditorRendererNodeProps = { - context: CCComponentEditorRendererContext; - nodeId: CCNodeId; - pixiParentContainer: PIXI.Container; - onDragStart(e: PIXI.FederatedMouseEvent): void; - onDragStartPin(e: PIXI.FederatedMouseEvent, nodePinId: CCNodePinId): void; - onDragEndPin(e: PIXI.FederatedMouseEvent, nodePinId: CCNodePinId): void; -}; - -type PixiTexts = { - componentName: PIXI.Text; - pinNames: Map; -}; - -/** - * Get size of node - * @param inputPinCount - * @param outputPinCount - * @returns size - */ -const getSize = (inputPinCount: number, outputPinCount: number) => - new PIXI.Point( - 200, - (100 / 3) * (Math.max(inputPinCount, outputPinCount) + 1) - ); - -/** - * Class for rendering node - */ -export default class CCComponentEditorRendererNode extends CCComponentEditorRendererBase { - #unsubscribeComponentEditorStore: () => void; - - #nodeId: CCNodeId; - - #pixiParentContainer: PIXI.Container; - - #pixiGraphics: PIXI.Graphics; - - static readonly #componentNameFontSize = 24; - - static readonly #edgeNameFontSize = 16; - - #pixiTexts: PixiTexts; - - #nodePinRenderers = new Map(); - - #componentPinRenderers = new Map< - CCComponentPinId, - CCComponentEditorRendererComponentPin - >(); - - #pixiWorld: PIXI.Container; - - /** - * Constructor of CCComponentEditorRendererNode - * @param props - */ - constructor(props: CCComponentEditorRendererNodeProps) { - super(props.context); - this.#nodeId = props.nodeId; - this.#pixiParentContainer = props.pixiParentContainer; - this.#pixiGraphics = new PIXI.Graphics(); - this.#pixiGraphics.eventMode = "dynamic"; - this.#pixiTexts = this.#createText(); - this.#pixiWorld = new PIXI.Container(); - this.#pixiWorld.sortableChildren = true; - this.#pixiParentContainer.addChild(this.#pixiWorld); - this.#pixiWorld.addChild(this.#pixiGraphics); - this.#pixiWorld.addChild(this.#pixiTexts.componentName); - - const node = this.context.store.nodes.get(this.#nodeId)!; - const nodePins = this.context.store.nodePins.getManyByNodeId(node.id); - for (const nodePin of nodePins) { - const pinRenderer = new CCComponentEditorRendererNodePin({ - context: this.context, - nodePinId: nodePin.id, - pixiParentContainer: this.#pixiWorld, - pixiText: this.#pixiTexts.pinNames.get(nodePin.componentPinId)!, - onDragStart: props.onDragStartPin, - onDragEnd: props.onDragEndPin, - }); - this.#nodePinRenderers.set(nodePin.id, pinRenderer); - } - - this.reconcileChildComponentPinRenderers(); - - this.#pixiGraphics.on("pointerdown", (e) => { - if ( - this.context.componentEditorStore - .getState() - .selectedNodeIds.has(this.#nodeId) - ) { - if (e.shiftKey) { - this.context.componentEditorStore - .getState() - .unselectNode([this.#nodeId]); - } - } else { - this.context.componentEditorStore - .getState() - .selectNode([this.#nodeId], !e.shiftKey); - } - props.onDragStart(e); - e.stopPropagation(); - }); - this.context.store.nodes.on("didUpdate", this.render); - this.context.store.components.on( - "didUpdate", - this.reconcileChildComponentPinRenderers - ); - this.context.store.connections.on( - "didRegister", - this.reconcileChildComponentPinRenderers - ); - this.context.store.connections.on( - "didUnregister", - this.reconcileChildComponentPinRenderers - ); - this.#unsubscribeComponentEditorStore = - this.context.componentEditorStore.subscribe(this.render); - this.render(); - } - - /** - * Event handler for pointer down - * @param event event - */ - onPointerDown(event: (e: PIXI.FederatedPointerEvent) => void) { - this.#pixiGraphics.on("pointerdown", event); - } - - /** - * Create text for name - * @returns text - */ - #createText(): PixiTexts { - const node = this.context.store.nodes.get(this.#nodeId)!; - const component = this.context.store.components.get(node.componentId)!; - const componentPins = this.context.store.componentPins.getManyByComponentId( - node.componentId - ); - - const componentName = new PIXI.Text(component.name, { - fontSize: CCComponentEditorRendererNode.#componentNameFontSize * 3, - }); - componentName.scale.x = 1 / 3; - componentName.scale.y = 1 / 3; - const map = new Map(); - for (const pin of componentPins) { - const pinText = new PIXI.Text(pin.name, { - fontSize: CCComponentEditorRendererNode.#edgeNameFontSize * 3, - }); - pinText.scale.x = 1 / 3; - pinText.scale.y = 1 / 3; - map.set(pin.id, pinText); - } - return { componentName, pinNames: map }; - } - - /** - * Render node - */ - render = () => { - const node = this.context.store.nodes.get(this.#nodeId); - if (!node) return; - const nodePins = this.context.store.nodePins.getManyByNodeId(this.#nodeId); - const inputNodePins = nodePins.filter( - (nodePin) => - this.context.store.componentPins.get(nodePin.componentPinId)!.type === - "input" - ); - const outputNodePins = nodePins.filter( - (nodePin) => - this.context.store.componentPins.get(nodePin.componentPinId)!.type === - "output" - ); - - const size = getSize(inputNodePins.length, outputNodePins.length); - const borderWidth = 3; - const outlineWidth = 1; - this.#pixiGraphics.clear(); - this.#pixiGraphics.beginFill(whiteColor); - this.#pixiGraphics.lineStyle({ - color: blackColor, - width: borderWidth, - alignment: 1, - }); - this.#pixiGraphics.drawRect(-size.x / 2, -size.y / 2, size.x, size.y); - this.#pixiGraphics.endFill(); - const gap = 6; - const edgeSize = 10; - inputNodePins.forEach((nodePin, index) => { - nullthrows(this.#nodePinRenderers.get(nodePin.id)).render( - index, - size, - inputNodePins.length - ); - }); - outputNodePins.forEach((nodePin, index) => { - nullthrows(this.#nodePinRenderers.get(nodePin.id)).render( - index, - size, - outputNodePins.length - ); - }); - this.#pixiTexts.componentName.anchor.set(0, 1); - this.#pixiTexts.componentName.x = -size.x / 2; - this.#pixiTexts.componentName.y = -size.y / 2 - gap; - - if ( - this.context.componentEditorStore - .getState() - .selectedNodeIds.has(this.#nodeId) - ) { - this.#pixiGraphics.lineStyle({ - color: primaryColor, - width: outlineWidth, - alignment: 1, - }); - const margin = 8; - this.#pixiGraphics.drawRect( - -size.x / 2 - borderWidth * 1.5 - edgeSize / 2, - -size.y / 2 - - CCComponentEditorRendererNode.#componentNameFontSize - - margin, - size.x + borderWidth * 3 + edgeSize, - size.y + - CCComponentEditorRendererNode.#componentNameFontSize + - margin + - borderWidth / 2 - - outlineWidth / 2 - ); - } - this.#pixiWorld.position = node.position; - }; - - /** - * Reconcile child component pin renderers - */ - reconcileChildComponentPinRenderers = () => { - const node = this.context.store.nodes.get(this.#nodeId); - if (!node) return; - - const parentComponent = this.context.store.components.get( - node.parentComponentId - )!; - const parentComponentPins = - this.context.store.componentPins.getManyByComponentId(parentComponent.id); - const nodePins = this.context.store.nodePins.getManyByNodeId(this.#nodeId); - - const existingComponentPinRenderers = new Map(this.#componentPinRenderers); - const newComponentPinRenderers = new Map< - CCComponentPinId, - CCComponentEditorRendererComponentPin - >(); - for (const nodePin of nodePins) { - if (this.context.store.connections.hasNoConnectionOf(nodePin.id)) { - const parentComponentPin = parentComponentPins.find( - (pin) => pin.implementation === nodePin.id - )!; - const existingComponentPinRenderer = existingComponentPinRenderers.get( - parentComponentPin.id - ); - if (existingComponentPinRenderer) { - newComponentPinRenderers.set( - parentComponentPin.id, - existingComponentPinRenderer - ); - existingComponentPinRenderers.delete(parentComponentPin.id); - } else { - const componentPinRenderer = - new CCComponentEditorRendererComponentPin({ - context: this.context, - pixiParentContainer: this.#pixiWorld, - nodeId: this.#nodeId, - pinId: parentComponentPin.id, - position: CCComponentEditorRendererNode.getPinOffset( - this.context.store, - nodePin.id - ), - }); - newComponentPinRenderers.set( - parentComponentPin.id, - componentPinRenderer - ); - } - } - } - for (const componentPinRenderer of existingComponentPinRenderers.values()) { - componentPinRenderer.destroy(); - } - this.#componentPinRenderers = newComponentPinRenderers; - }; - - /** - * Judge if the range is selected - * @param start_ start - * @param end_ end - */ - judgeIsRangeSelected(start_: PIXI.Point, end_: PIXI.Point) { - const { start, end } = rearrangeRangeSelect({ start: start_, end: end_ }); - const node = this.context.store.nodes.get(this.#nodeId)!; - const componentPins = this.context.store.componentPins.getManyByComponentId( - node.componentId - ); - const inputComponentPins = componentPins.filter( - (pin) => pin.type === "input" - ); - const outputComponentPins = componentPins.filter( - (pin) => pin.type === "output" - ); - const size = getSize(inputComponentPins.length, outputComponentPins.length); - const nodePosition = node.position; - const nodePositions = [ - new PIXI.Point(nodePosition.x - size.x / 2, nodePosition.y - size.y / 2), - new PIXI.Point(nodePosition.x - size.x / 2, nodePosition.y + size.y / 2), - new PIXI.Point(nodePosition.x + size.x / 2, nodePosition.y - size.y / 2), - new PIXI.Point(nodePosition.x + size.x / 2, nodePosition.y + size.y / 2), - ]; - if ( - Math.max(nodePositions[0]!.x, start.x) < - Math.min(nodePositions[3]!.x, end.x) && - Math.max(nodePositions[0]!.y, start.y) < - Math.min(nodePositions[3]!.y, end.y) - ) { - this.context.componentEditorStore.getState().selectNode([node.id], false); - } else { - this.context.componentEditorStore.getState().unselectNode([node.id]); - } - } - - /** - * Destroy node - */ - override destroy() { - super.destroy(); - this.#pixiGraphics.destroy(); - this.#pixiTexts.componentName.destroy(); - for (const text of this.#pixiTexts.pinNames) { - text[1].destroy(); - } - for (const pinRenderer of this.#nodePinRenderers.values()) { - pinRenderer.destroy(); - } - for (const componentPinRenderer of this.#componentPinRenderers.values()) { - componentPinRenderer.destroy(); - } - this.context.store.nodes.off("didUpdate", this.render); - this.context.store.components.off( - "didUpdate", - this.reconcileChildComponentPinRenderers - ); - this.context.store.connections.off( - "didRegister", - this.reconcileChildComponentPinRenderers - ); - this.context.store.connections.off( - "didUnregister", - this.reconcileChildComponentPinRenderers - ); - this.#unsubscribeComponentEditorStore(); - } - - /** - * Get offset of position of pin in node - * @param store store - * @param nodeId id of node - * @param componentPinId id of pin - * @returns offset - */ - static getPinOffset(store: CCStore, nodePinId: CCNodePinId): Point { - const nodePin = store.nodePins.get(nodePinId)!; - const node = store.nodes.get(nodePin.nodeId)!; - const component = store.components.get(node.componentId)!; - const componentPins = store.componentPins.getManyByComponentId( - component.id - ); - const inputComponentPinIds = componentPins - .filter((pin) => pin.type === "input") - .map((pin) => pin.id); - const inputComponentPinCount = inputComponentPinIds.length; - const outputComponentPinIds = componentPins - .filter((pin) => pin.type === "output") - .map((pin) => pin.id); - const outputPinCount = outputComponentPinIds.length; - const size = getSize(inputComponentPinCount, outputPinCount); - if (inputComponentPinIds.includes(nodePin.componentPinId)) { - const pinIndex = inputComponentPinIds.indexOf(nodePin.componentPinId); - return new PIXI.Point( - -size.x / 2, - -size.y / 2 + (size.y / (inputComponentPinCount + 1)) * (pinIndex + 1) - ); - } - if (outputComponentPinIds.includes(nodePin.componentPinId)) { - const pinIndex = outputComponentPinIds.indexOf(nodePin.componentPinId); - return new PIXI.Point( - size.x / 2, - -size.y / 2 + (size.y / (outputPinCount + 1)) * (pinIndex + 1) - ); - } - throw Error( - `pin: ${nodePin.componentPinId} not found in node: ${nodePin.nodeId}` - ); - } - - /** - * Get absolute position of NodePin - * @param store store - * @param nodeId id of node - * @param pinId id of pin - * @returns absolute position - */ - static getNodePinAbsolute(store: CCStore, nodePinId: CCNodePinId): Point { - const nodePin = nullthrows(store.nodePins.get(nodePinId)); - const node = nullthrows(store.nodes.get(nodePin.nodeId)); - const componentPin = nullthrows( - store.componentPins.get(nodePin.componentPinId) - ); - const componentPins = store.componentPins.getManyByComponentId( - node.componentId - ); - const inputComponentPinIds = componentPins - .filter((pin) => pin.type === "input") - .map((pin) => pin.id); - const inputComponentPinCount = inputComponentPinIds.length; - const outputComponentPinIds = componentPins - .filter((pin) => pin.type === "output") - .map((pin) => pin.id); - const outputPinCount = outputComponentPinIds.length; - const size = getSize(inputComponentPinCount, outputPinCount); - if (componentPin.type === "input") { - const pinIndex = inputComponentPinIds.indexOf(componentPin.id); - return new PIXI.Point( - node.position.x - size.x / 2, - node.position.y - - size.y / 2 + - (size.y / (inputComponentPinCount + 1)) * (pinIndex + 1) - ); - } - if (componentPin.type === "output") { - const pinIndex = outputComponentPinIds.indexOf(componentPin.id); - return new PIXI.Point( - node.position.x + size.x / 2, - node.position.y - - size.y / 2 + - (size.y / (outputPinCount + 1)) * (pinIndex + 1) - ); - } - - throw Error(`pin: ${componentPin.id} not found in node: ${node.id}`); - } -} diff --git a/src/pages/edit/Editor/renderer/nodePin.ts b/src/pages/edit/Editor/renderer/nodePin.ts deleted file mode 100644 index e2441e9..0000000 --- a/src/pages/edit/Editor/renderer/nodePin.ts +++ /dev/null @@ -1,143 +0,0 @@ -import * as PIXI from "pixi.js"; -import type { CCNodePinId } from "../../../../store/nodePin"; -import { blackColor, whiteColor, primaryColor } from "../../../../common/theme"; -import CCComponentEditorRendererBase, { - type CCComponentEditorRendererContext, -} from "./base"; - -export type CCComponentEditorRendererNodePinProps = { - context: CCComponentEditorRendererContext; - nodePinId: CCNodePinId; - pixiParentContainer: PIXI.Container; - pixiText: PIXI.Text; - onDragStart(e: PIXI.FederatedMouseEvent, nodePinId: CCNodePinId): void; - onDragEnd(e: PIXI.FederatedMouseEvent, nodePinId: CCNodePinId): void; -}; - -/** - * Class for rendering node pin - */ -export default class CCComponentEditorRendererNodePin extends CCComponentEditorRendererBase { - #nodePinId: CCNodePinId; - - #pixiParentContainer: PIXI.Container; - - #pixiWorld: PIXI.Container; - - #pixiGraphics: PIXI.Graphics; - - #pixiText: PIXI.Text; - - isSelected = false; - - /** - * Constructor of CCComponentEditorRendererNodePin - * @param props - */ - constructor({ - context, - nodePinId, - pixiParentContainer, - pixiText, - onDragStart, - onDragEnd, - }: CCComponentEditorRendererNodePinProps) { - super(context); - this.#nodePinId = nodePinId; - this.#pixiParentContainer = pixiParentContainer; - this.#pixiWorld = new PIXI.Container(); - this.#pixiParentContainer.addChild(this.#pixiWorld); - this.#pixiGraphics = new PIXI.Graphics(); - // this.#pixiGraphics.interactive = true; - this.#pixiGraphics.eventMode = "dynamic"; - this.#pixiGraphics.cursor = "pointer"; - this.#pixiWorld.addChild(this.#pixiGraphics); - this.#pixiText = pixiText; - this.#pixiWorld.addChild(this.#pixiText); - this.#pixiGraphics.on("pointerdown", (e) => { - onDragStart(e, nodePinId); - e.stopPropagation(); - }); - this.#pixiGraphics.on("pointerup", (e) => { - onDragEnd(e, nodePinId); - e.stopPropagation(); - }); - } - - /** - * Event handler for pointer down - * @param event - */ - onPointerDown(event: (e: PIXI.FederatedPointerEvent) => void) { - this.#pixiGraphics.on("pointerdown", event); - } - - /** - * Render the node pin - * @param index index of the pin in node - * @param size size of the node - * @param pinsLength length of pins in the node - */ - render(index: number, size: PIXI.Point, pinsLength: number) { - const nodePin = this.context.store.nodePins.get(this.#nodePinId)!; - const componentPin = this.context.store.componentPins.get( - nodePin.componentPinId - )!; - const gap = 6; - const edgeSize = 10; - const borderWidth = 3; - const edgeGap = size.y / (pinsLength + 1); - const sign = componentPin.type === "input" ? 1 : -1; - const position = { - x: -(sign * size.x) / 2 - edgeSize / 2 - (sign * borderWidth) / 2, - y: -size.y / 2 + edgeGap * (index + 1) - edgeSize / 2, - }; - this.#pixiGraphics.clear(); - this.#pixiGraphics.beginFill(whiteColor); - this.#pixiGraphics.lineStyle({ - color: blackColor, - width: borderWidth, - alignment: 1, - }); - this.#pixiGraphics.drawRoundedRect(0, 0, edgeSize, edgeSize, 2); - this.#pixiGraphics.endFill(); - if (this.#pixiText) { - if (componentPin.type === "input") { - this.#pixiText.x = edgeSize + gap; - this.#pixiText.y = 0; - this.#pixiText.anchor.set(0, 0.25); - } else if (componentPin.type === "output") { - this.#pixiText.x = -gap; - this.#pixiText.y = 0; - this.#pixiText.anchor.set(1, 0.25); - } - } - - if (this.isSelected) { - this.#pixiGraphics.lineStyle({ - color: primaryColor, - width: 1, - alignment: 1, - }); - this.#pixiGraphics.drawRect(-2, -2, edgeSize + 4, edgeSize + 4); - } - this.#pixiWorld.position = position; - - const hitAreaGap = 4; - const hitArea = new PIXI.Polygon( - new PIXI.Point(-hitAreaGap, -hitAreaGap), - new PIXI.Point(-hitAreaGap, edgeSize + hitAreaGap), - new PIXI.Point(edgeSize + hitAreaGap, edgeSize + hitAreaGap), - new PIXI.Point(edgeSize + hitAreaGap, -hitAreaGap) - ); - this.#pixiGraphics.hitArea = hitArea; - this.#pixiWorld.zIndex = 100; - } - - /** - * Destroy the node pin - */ - override destroy() { - this.#pixiParentContainer.removeChild(this.#pixiWorld); - } -} diff --git a/src/pages/edit/Editor/renderer/rangeSelect.ts b/src/pages/edit/Editor/renderer/rangeSelect.ts deleted file mode 100644 index 092ee7b..0000000 --- a/src/pages/edit/Editor/renderer/rangeSelect.ts +++ /dev/null @@ -1,92 +0,0 @@ -import invariant from "tiny-invariant"; -import * as PIXI from "pixi.js"; -import type CCStore from "../../../../store"; -import type { ComponentEditorStore } from "../store"; -import { primaryColor } from "../../../../common/theme"; - -type CCComponentEditorRendererRangeSelectProps = { - store: CCStore; - componentEditorStore: ComponentEditorStore; - pixiParentContainer: PIXI.Container; -}; - -/** - * Rearrange range select - * @param param0 start and end points - * @returns rearranged start and end points - */ -export const rearrangeRangeSelect = ({ - start, - end, -}: { - start: PIXI.Point; - end: PIXI.Point; -}) => { - return { - start: new PIXI.Point(Math.min(start.x, end.x), Math.min(start.y, end.y)), - end: new PIXI.Point(Math.max(start.x, end.x), Math.max(start.y, end.y)), - }; -}; - -/** - * Class for rendering range select - */ -export default class CCComponentEditorRendererRangeSelect { - #store: CCStore; - - #pixiGraphics: PIXI.Graphics; - - #pixiParentContainer: PIXI.Container; - - #componentEditorStore: ComponentEditorStore; - - #pixiWorld: PIXI.Container; - - /** - * Constructor of CCComponentEditorRendererRangeSelect - * @param props - */ - constructor(props: CCComponentEditorRendererRangeSelectProps) { - this.#store = props.store; - this.#componentEditorStore = props.componentEditorStore; - this.#pixiParentContainer = props.pixiParentContainer; - this.#pixiWorld = new PIXI.Container(); - this.#pixiParentContainer.addChild(this.#pixiWorld); - this.#pixiGraphics = new PIXI.Graphics(); - this.#pixiWorld.addChild(this.#pixiGraphics); - - // TODO: implement - invariant(this.#store); - invariant(this.#componentEditorStore); - } - - /** - * Render range select - */ - render() { - const { rangeSelect } = this.#componentEditorStore.getState(); - this.#pixiGraphics.clear(); - if (!rangeSelect) { - return; - } - const { start, end } = rearrangeRangeSelect(rangeSelect); - this.#pixiGraphics.lineStyle({ - color: primaryColor, - width: 1, - alignment: 1, - }); - this.#pixiGraphics.drawRect( - start.x, - start.y, - end.x - start.x, - end.y - start.y - ); - } - - /** - * Destroy range select - */ - destroy() { - this.#pixiGraphics.destroy(); - } -} diff --git a/src/pages/edit/Editor/renderer/simulator.ts b/src/pages/edit/Editor/renderer/simulator.ts deleted file mode 100644 index 47404c5..0000000 --- a/src/pages/edit/Editor/renderer/simulator.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type CCStore from "../../../../store"; -import type { CCComponentId } from "../../../../store/component"; -import CCEvaluation from "../../../../store/evaluation"; -import type { CCComponentPinId } from "../../../../store/componentPin"; -import type { ComponentEditorStore } from "../store"; - -type CCSimulatorProps = { - store: CCStore; - componentEditorStore: ComponentEditorStore; - componentId: CCComponentId; -}; - -/** - * Simulator of component - */ -export default class CCSimulator { - readonly #store: CCStore; - - readonly #componentId: CCComponentId; - - #evaluation: CCEvaluation; - - /** - * Constructor of CCSimulator - * @param props - */ - constructor(props: CCSimulatorProps) { - this.#store = props.store; - this.#componentId = props.componentId; - this.#evaluation = new CCEvaluation(this.#store); - } - - /** - * Simulation - * @param input map of input pins and their values - * @param timeStep time step - * @returns map of output pins and their values - */ - simulation = (input: Map, timeStep: number) => { - const outputs = this.#evaluation.evaluate( - this.#componentId, - input, - timeStep - ); - return outputs; - }; - - /** - * Clear evaluation - */ - clear() { - this.#evaluation.clear(); - } -} diff --git a/src/pages/edit/Editor/renderer/textBox.ts b/src/pages/edit/Editor/renderer/textBox.ts deleted file mode 100644 index d1f2c4c..0000000 --- a/src/pages/edit/Editor/renderer/textBox.ts +++ /dev/null @@ -1,144 +0,0 @@ -import * as PIXI from "pixi.js"; -import CCComponentEditorRendererBase, { - type CCComponentEditorRendererContext, -} from "./base"; - -export type CCComponentEditorRendererTextBoxProps = { - context: CCComponentEditorRendererContext; - pixiParentContainer: PIXI.Container; -}; - -/** - * Class for rendering text box - */ -export class CCComponentEditorRendererTextBox extends CCComponentEditorRendererBase { - #unsubscribeComponentEditorStore: () => void; - - readonly #pixiParentContainer: PIXI.Container; - - readonly #pixiText: PIXI.Text; - - #isInEditMode = false; - - #htmlInput: HTMLInputElement | null = null; - - value = ""; - - position: PIXI.Point; - - fontSize = 16; - - alignment: "left" | "center" | "right" = "left"; - - isEditable = false; - - onChange?: (value: string) => void; - - /** - * Constructor of CCComponentEditorRendererTextBox - * @param props - */ - constructor(props: CCComponentEditorRendererTextBoxProps) { - super(props.context); - this.#unsubscribeComponentEditorStore = - this.context.componentEditorStore.subscribe(() => this.render()); - this.#pixiParentContainer = props.pixiParentContainer; - this.#pixiText = new PIXI.Text(); - this.#pixiText.on("pointerdown", () => { - if (!this.isEditable) return; - this.#isInEditMode = true; - this.render(); - }); - this.#pixiParentContainer.addChild(this.#pixiText); - this.position = new PIXI.Point(0, 0); - this.render(); - } - - /** - * Render text box - */ - render() { - this.#pixiText.position = this.position; - this.#pixiText.text = this.value; - if (this.alignment === "left") { - this.#pixiText.anchor.set(0, 0.5); - } else if (this.alignment === "center") { - this.#pixiText.anchor.set(0.5, 0.5); - } else if (this.alignment === "right") { - this.#pixiText.anchor.set(1, 0.5); - } - this.#pixiText.style.fontSize = this.fontSize; - this.#pixiText.visible = !this.#isInEditMode; - if (this.isEditable) { - // this.#pixiText.interactive = true; - this.#pixiText.eventMode = "dynamic"; - } else { - // this.#pixiText.interactive = false; - this.#pixiText.eventMode = "none"; - } - if (this.#isInEditMode) { - const htmlInput = this.activateHtmlInput(); - htmlInput.style.textAlign = this.alignment; - const t = this.#pixiText.worldTransform; - htmlInput.style.transform = `matrix(${t.a}, ${t.b}, ${t.c}, ${t.d}, ${t.tx}, ${t.ty})`; - } else { - this.deactivateHtmlInput(); - } - } - - /** - * Activate HTML input element and return it - * @returns HTML input element - */ - activateHtmlInput() { - if (this.#htmlInput) return this.#htmlInput; - this.#htmlInput = window.document.createElement("input"); - this.#htmlInput.value = this.value; - this.#htmlInput.style.position = "absolute"; - this.#htmlInput.style.padding = "0"; - this.#htmlInput.style.border = "none"; - this.#htmlInput.style.outline = "none"; - this.#htmlInput.style.background = "none"; - this.#htmlInput.style.pointerEvents = "auto"; - this.#htmlInput.style.fontSize = `${this.fontSize}px`; - this.#htmlInput.style.translate = [ - { - left: "0", - center: "-50%", - right: "-100%", - }[this.alignment], - "-50%", - ].join(" "); - this.#htmlInput.addEventListener("blur", () => { - this.#isInEditMode = false; - if (this.#htmlInput && this.onChange) - this.onChange(this.#htmlInput.value); - this.render(); - }); - this.context.overlayArea.appendChild(this.#htmlInput); - // Focus after other event handlers have finished processing. - setTimeout(() => { - this.#htmlInput?.focus(); - this.#htmlInput?.select(); - }, 10); - return this.#htmlInput; - } - - /** - * Deactivate HTML input element - */ - deactivateHtmlInput() { - if (!this.#htmlInput) return; - this.context.overlayArea.removeChild(this.#htmlInput); - this.#htmlInput = null; - } - - /** - * Destroy the text box - */ - override destroy() { - super.destroy(); - this.deactivateHtmlInput(); - this.#unsubscribeComponentEditorStore(); - } -} diff --git a/src/pages/edit/Editor/store/index.tsx b/src/pages/edit/Editor/store/index.tsx index bf1c363..5628ffa 100644 --- a/src/pages/edit/Editor/store/index.tsx +++ b/src/pages/edit/Editor/store/index.tsx @@ -1,202 +1,28 @@ import { createContext, useContext, useState } from "react"; import invariant from "tiny-invariant"; import { create } from "zustand"; -import PIXI from "pixi.js"; -import type { CCNodeId } from "../../../../store/node"; -import type { CCConnectionId } from "../../../../store/connection"; -import { - type WorldPerspectiveStoreMixin, - worldPerspectiveStoreMixin, -} from "./worldPerspective"; -import type { CCComponentPinId } from "../../../../store/componentPin"; import { useStore } from "../../../../store/react"; import type CCStore from "../../../../store"; -import type { CCNodePinId } from "../../../../store/nodePin"; import type { CCComponentId } from "../../../../store/component"; -import { simulateComponent } from "../../../../store/componentEvaluator"; - -export type EditorMode = EditorModeEdit | EditorModePlay; -export type EditorModeEdit = "edit"; -export type EditorModePlay = "play"; - -export type RangeSelect = { start: PIXI.Point; end: PIXI.Point } | null; - -export type InputValueKey = CCComponentPinId; - -type State = { - editorMode: EditorMode; - timeStep: number; - selectedNodeIds: Set; - rangeSelect: RangeSelect; - setRangeSelect(rangeSelect: RangeSelect): void; - selectedConnectionIds: Set; - inputValues: Map; - getInputValue(componentPinId: CCComponentPinId): SimulationValue; - setInputValue(componentPinId: CCComponentPinId, value: SimulationValue): void; - setEditorMode(mode: EditorMode): void; - resetTimeStep(): void; - incrementTimeStep(): void; - selectNode(ids: CCNodeId[], exclusive: boolean): void; - unselectNode(ids: CCNodeId[]): void; - selectConnection(ids: CCConnectionId[], exclusive: boolean): void; - getNodePinValue(nodePinId: CCNodePinId): SimulationValue | undefined; - getComponentPinValue( - componentPinId: CCComponentPinId - ): SimulationValue | undefined; -} & WorldPerspectiveStoreMixin; - -export type SimulationValue = boolean[]; -export type SimulationFrame = { - componentId: CCComponentId; - nodes: Map< - CCNodeId, - { - pins: Map; - /** null if intrinsic */ - child: SimulationFrame | null; - } - >; -}; +import { createComponentEditorStoreCoreSlice } from "./slices/core"; +import type { ComponentEditorStoreValue } from "./types"; +import createComponentEditorStorePerspectiveSlice from "./slices/perspective"; +import createComponentEditorStoreContextMenuSlice from "./slices/contextMenu"; function createEditorStore(componentId: CCComponentId, store: CCStore) { - let simulationCacheKey = ""; - /** index = timeStep */ - let simulationCachedFrames: SimulationFrame[] = []; - - const editorStore = create((set, get) => ({ - editorMode: "edit", - timeStep: 0, - selectedNodeIds: new Set(), - rangeSelect: null, - selectedConnectionIds: new Set(), - /** @private */ - inputValues: new Map(), - getInputValue(componentPinId: CCComponentPinId) { - const value = this.inputValues.get(componentPinId); - if (!value) { - const multiplexability = - store.componentPins.getComponentPinMultiplexability(componentPinId); - if (multiplexability.isMultiplexable) { - const newValue = [false]; - return newValue; - } - const newValue = new Array(multiplexability.multiplicity).fill(false); - return newValue; - } - return value; - }, - setInputValue(componentPinId: CCComponentPinId, value: SimulationValue) { - set((state) => { - return { - ...state, - inputValues: new Map(state.inputValues).set(componentPinId, value), - }; - }); - }, - setRangeSelect(rangeSelect: RangeSelect) { - set((state) => ({ ...state, rangeSelect })); - }, - setEditorMode(mode: EditorMode) { - set((state) => ({ ...state, editorMode: mode })); - }, - resetTimeStep() { - set((state) => ({ ...state, timeStep: 0 })); - }, - incrementTimeStep() { - set((state) => ({ ...state, timeStep: state.timeStep + 1 })); - }, - selectNode(ids: CCNodeId[], exclusive: boolean) { - set((state) => ({ - ...state, - selectedNodeIds: new Set( - exclusive ? ids : [...state.selectedNodeIds, ...ids] - ), - selectedConnectionIds: new Set(), - })); - }, - unselectNode(ids: CCNodeId[]) { - set((state) => ({ - ...state, - selectedNodeIds: new Set( - [...state.selectedNodeIds].filter((nodeId) => !ids.includes(nodeId)) - ), - selectedConnectionIds: new Set(), - })); - }, - selectConnection(ids: CCConnectionId[], exclusive: boolean) { - set((state) => ({ - ...state, - selectedConnectionIds: new Set( - exclusive ? ids : [...state.selectedConnectionIds, ...ids] - ), - selectedNodeIds: new Set(), - })); - }, - ...worldPerspectiveStoreMixin(set, get), - getNodePinValue(nodePinId: CCNodePinId): SimulationValue | undefined { - const { nodeId } = store.nodePins.get(nodePinId)!; - const editorState = editorStore.getState(); - return simulationCachedFrames[editorState.timeStep]!.nodes.get( - nodeId - )!.pins.get(nodePinId); - }, - getComponentPinValue( - componentPinId: CCComponentPinId - ): SimulationValue | undefined { - const componentPin = store.componentPins.get(componentPinId)!; - invariant(componentPin.implementation); - const nodePinId = componentPin.implementation; - return this.getNodePinValue(nodePinId); - }, + const props = { store, componentId }; + const coreSlice = createComponentEditorStoreCoreSlice(props); + const perspectiveSlice = createComponentEditorStorePerspectiveSlice(props); + const contextMenuSlice = createComponentEditorStoreContextMenuSlice(props); + const editorStore = create((set, get) => ({ + componentId, + ...coreSlice.define(set, get), + ...perspectiveSlice.define(set, get), + ...contextMenuSlice.define(set, get), })); - - const executeSimulation = () => { - if (editorStore.getState().editorMode !== "play") return; - - const newSimulationCacheKey = - store.nodes - .toArray() - .map((node) => node.id) - .join() + - store.connections - .toArray() - .map((connection) => connection.id) - .join() + - [...editorStore.getState().inputValues.entries()] - .map(([key, value]) => key + value.join()) - .join(); - if (newSimulationCacheKey !== simulationCacheKey) { - simulationCacheKey = newSimulationCacheKey; - simulationCachedFrames = []; - } - - const editorState = editorStore.getState(); - let isUpdated = false; - for ( - let timeStep = simulationCachedFrames.length; - timeStep <= editorState.timeStep; - timeStep += 1 - ) { - const previousFrame = simulationCachedFrames[timeStep - 1] ?? null; - const inputValues = new Map(); - const pins = store.componentPins.getManyByComponentId(componentId); - for (const pin of pins) { - inputValues.set(pin.id, editorState.getInputValue(pin.id)); - } - simulationCachedFrames.push( - simulateComponent(store, componentId, inputValues, previousFrame)! - ); - isUpdated = true; - } - if (isUpdated) editorStore.setState((s) => ({ ...s })); - }; - store.nodes.on("didRegister", executeSimulation); - store.nodes.on("didUpdate", executeSimulation); - store.nodes.on("didUnregister", executeSimulation); - store.connections.on("didRegister", executeSimulation); - store.connections.on("didUnregister", executeSimulation); - editorStore.subscribe(executeSimulation); - + coreSlice.postCreate?.(editorStore); + perspectiveSlice.postCreate?.(editorStore); + contextMenuSlice.postCreate?.(editorStore); return editorStore; } diff --git a/src/pages/edit/Editor/store/slices/contextMenu/index.tsx b/src/pages/edit/Editor/store/slices/contextMenu/index.tsx new file mode 100644 index 0000000..0e685b5 --- /dev/null +++ b/src/pages/edit/Editor/store/slices/contextMenu/index.tsx @@ -0,0 +1,25 @@ +import { type ComponentEditorSliceCreator } from "../../types"; +import type { ContextMenuStoreSlice } from "./types"; + +const createComponentEditorStoreContextMenuSlice: ComponentEditorSliceCreator< + ContextMenuStoreSlice +> = () => ({ + define: (set) => ({ + contextMenuState: null, + openContextMenu: (e) => { + set((state) => ({ + ...state, + contextMenuState: { + position: { + x: e.nativeEvent.offsetX, + y: e.nativeEvent.offsetY, + }, + }, + })); + }, + closeContextMenu: () => + set((state) => ({ ...state, contextMenuState: null })), + }), +}); + +export default createComponentEditorStoreContextMenuSlice; diff --git a/src/pages/edit/Editor/store/slices/contextMenu/types.ts b/src/pages/edit/Editor/store/slices/contextMenu/types.ts new file mode 100644 index 0000000..537c035 --- /dev/null +++ b/src/pages/edit/Editor/store/slices/contextMenu/types.ts @@ -0,0 +1,12 @@ +import type { MouseEvent } from "react"; +import type { Point } from "../../../../../../common/types"; + +export type ContextMenuState = { + position: Point; +}; + +export type ContextMenuStoreSlice = { + contextMenuState: ContextMenuState | null; + openContextMenu: (e: MouseEvent) => void; + closeContextMenu: () => void; +}; diff --git a/src/pages/edit/Editor/store/slices/core/index.ts b/src/pages/edit/Editor/store/slices/core/index.ts new file mode 100644 index 0000000..e71a954 --- /dev/null +++ b/src/pages/edit/Editor/store/slices/core/index.ts @@ -0,0 +1,184 @@ +import invariant from "tiny-invariant"; +import type { CCComponentId } from "../../../../../../store/component"; +import type { CCComponentPinId } from "../../../../../../store/componentPin"; +import type { CCNodeId } from "../../../../../../store/node"; +import type { CCNodePinId } from "../../../../../../store/nodePin"; +import type { CCConnectionId } from "../../../../../../store/connection"; +import type { ComponentEditorSliceCreator } from "../../types"; +import type { EditorStoreCoreSlice } from "./types"; +import simulateComponent from "../../../../../../store/componentEvaluator"; + +export type SimulationValue = boolean[]; +export type SimulationFrame = { + componentId: CCComponentId; + nodes: Map< + CCNodeId, + { + pins: Map; + /** null if intrinsic */ + child: SimulationFrame | null; + } + >; +}; + +export const createComponentEditorStoreCoreSlice: ComponentEditorSliceCreator< + EditorStoreCoreSlice +> = ({ store, componentId }) => { + let simulationCacheKey = ""; + /** index = timeStep */ + let simulationCachedFrames: SimulationFrame[] = []; + + return { + define: (set, get) => { + return { + editorMode: "edit", + timeStep: 0, + selectedNodeIds: new Set(), + rangeSelect: null, + selectedConnectionIds: new Set(), + /** @private */ + inputValues: new Map(), + getInputValue(componentPinId: CCComponentPinId) { + const value = get().inputValues.get(componentPinId); + if (!value) { + const multiplexability = + store.componentPins.getComponentPinMultiplexability( + componentPinId + ); + if (multiplexability === "undecidable") { + throw new Error("Cannot determine multiplexability"); + } + if (multiplexability.isMultiplexable) { + const newValue = [false]; + return newValue; + } + const newValue = new Array(multiplexability.multiplicity).fill( + false + ); + return newValue; + } + return value; + }, + setInputValue( + componentPinId: CCComponentPinId, + value: SimulationValue + ) { + set((state) => { + return { + ...state, + inputValues: new Map(state.inputValues).set( + componentPinId, + value + ), + }; + }); + }, + setRangeSelect(rangeSelect) { + set((state) => ({ ...state, rangeSelect })); + }, + setEditorMode(mode) { + set((state) => ({ ...state, editorMode: mode })); + }, + resetTimeStep() { + set((state) => ({ ...state, timeStep: 0 })); + }, + incrementTimeStep() { + set((state) => ({ ...state, timeStep: state.timeStep + 1 })); + }, + selectNode(ids: CCNodeId[], exclusive: boolean) { + set((state) => ({ + ...state, + selectedNodeIds: new Set( + exclusive ? ids : [...state.selectedNodeIds, ...ids] + ), + selectedConnectionIds: new Set(), + })); + }, + unselectNode(ids: CCNodeId[]) { + set((state) => ({ + ...state, + selectedNodeIds: new Set( + [...state.selectedNodeIds].filter( + (nodeId) => !ids.includes(nodeId) + ) + ), + selectedConnectionIds: new Set(), + })); + }, + selectConnection(ids: CCConnectionId[], exclusive: boolean) { + set((state) => ({ + ...state, + selectedConnectionIds: new Set( + exclusive ? ids : [...state.selectedConnectionIds, ...ids] + ), + selectedNodeIds: new Set(), + })); + }, + getNodePinValue(nodePinId: CCNodePinId): SimulationValue | undefined { + const { nodeId } = store.nodePins.get(nodePinId)!; + return simulationCachedFrames[get().timeStep]!.nodes.get( + nodeId + )!.pins.get(nodePinId); + }, + getComponentPinValue( + componentPinId: CCComponentPinId + ): SimulationValue | undefined { + const componentPin = store.componentPins.get(componentPinId)!; + invariant(componentPin.implementation); + const nodePinId = componentPin.implementation; + return this.getNodePinValue(nodePinId); + }, + }; + }, + postCreate: (editorStore) => { + const executeSimulation = () => { + if (editorStore.getState().editorMode !== "play") return; + + const newSimulationCacheKey = + store.nodes + .getMany() + .map((node) => node.id) + .join() + + store.connections + .getMany() + .map((connection) => connection.id) + .join() + + [...editorStore.getState().inputValues.entries()] + .map(([key, value]) => key + value.join()) + .join(); + if (newSimulationCacheKey !== simulationCacheKey) { + simulationCacheKey = newSimulationCacheKey; + simulationCachedFrames = []; + } + + const editorState = editorStore.getState(); + let isUpdated = false; + for ( + let timeStep = simulationCachedFrames.length; + timeStep <= editorState.timeStep; + timeStep += 1 + ) { + const previousFrame = simulationCachedFrames[timeStep - 1] ?? null; + const inputValues = new Map(); + const pins = store.componentPins.getManyByComponentId(componentId); + for (const pin of pins) { + if (pin.type === "input") { + inputValues.set(pin.id, editorState.getInputValue(pin.id)); + } + } + simulationCachedFrames.push( + simulateComponent(store, componentId, inputValues, previousFrame)! + ); + isUpdated = true; + } + if (isUpdated) editorStore.setState((s) => ({ ...s })); + }; + store.nodes.on("didRegister", executeSimulation); + store.nodes.on("didUpdate", executeSimulation); + store.nodes.on("didUnregister", executeSimulation); + store.connections.on("didRegister", executeSimulation); + store.connections.on("didUnregister", executeSimulation); + editorStore.subscribe(executeSimulation); + }, + }; +}; diff --git a/src/pages/edit/Editor/store/slices/core/types.ts b/src/pages/edit/Editor/store/slices/core/types.ts new file mode 100644 index 0000000..e9c1238 --- /dev/null +++ b/src/pages/edit/Editor/store/slices/core/types.ts @@ -0,0 +1,36 @@ +import type { CCComponentPinId } from "../../../../../../store/componentPin"; +import type { CCNodeId } from "../../../../../../store/node"; +import type { CCConnectionId } from "../../../../../../store/connection"; +import type { SimulationValue } from "."; +import type { CCNodePinId } from "../../../../../../store/nodePin"; +import type { Point } from "../../../../../../common/types"; + +export type EditorMode = EditorModeEdit | EditorModePlay; +export type EditorModeEdit = "edit"; +export type EditorModePlay = "play"; + +export type RangeSelect = { start: Point; end: Point } | null; + +export type InputValueKey = CCComponentPinId; + +export type EditorStoreCoreSlice = { + editorMode: EditorMode; + timeStep: number; + selectedNodeIds: Set; + rangeSelect: RangeSelect; + setRangeSelect(rangeSelect: RangeSelect): void; + selectedConnectionIds: Set; + inputValues: Map; + getInputValue(componentPinId: CCComponentPinId): SimulationValue; + setInputValue(componentPinId: CCComponentPinId, value: SimulationValue): void; + setEditorMode(mode: EditorMode): void; + resetTimeStep(): void; + incrementTimeStep(): void; + selectNode(ids: CCNodeId[], exclusive: boolean): void; + unselectNode(ids: CCNodeId[]): void; + selectConnection(ids: CCConnectionId[], exclusive: boolean): void; + getNodePinValue(nodePinId: CCNodePinId): SimulationValue | undefined; + getComponentPinValue( + componentPinId: CCComponentPinId + ): SimulationValue | undefined; +}; diff --git a/src/pages/edit/Editor/store/slices/perspective/index.tsx b/src/pages/edit/Editor/store/slices/perspective/index.tsx new file mode 100644 index 0000000..ffb5a98 --- /dev/null +++ b/src/pages/edit/Editor/store/slices/perspective/index.tsx @@ -0,0 +1,69 @@ +import * as matrix from "transformation-matrix"; +import { type ComponentEditorSliceCreator } from "../../types"; +import type { PerspectiveStoreSlice } from "./types"; + +const createComponentEditorStorePerspectiveSlice: ComponentEditorSliceCreator< + PerspectiveStoreSlice +> = () => { + let resizeObserver: ResizeObserver | null; + let resizeObserverObservedElement: SVGSVGElement | null; + const registerRendererElement = (element: SVGSVGElement | null) => { + if (!resizeObserver) return; + if (resizeObserverObservedElement) + resizeObserver.unobserve(resizeObserverObservedElement); + if (element) resizeObserver.observe(element); + resizeObserverObservedElement = element; + }; + return { + define: (set, get) => ({ + rendererSize: { width: 0, height: 0 }, + userPerspectiveTransformation: matrix.identity(), + registerRendererElement, + setUserPerspectiveTransformation: (transformation) => { + set((state) => ({ + ...state, + userPerspectiveTransformation: transformation, + })); + }, + getViewTransformation: () => { + return matrix.compose( + matrix.translate( + get().rendererSize.width / 2, + get().rendererSize.height / 2 + ), + get().userPerspectiveTransformation + ); + }, + getInverseViewTransformation: () => + matrix.inverse(get().getViewTransformation()), + getViewBox: () => { + const inverseViewTransformation = get().getInverseViewTransformation(); + const viewBoxTopLeft = matrix.applyToPoint(inverseViewTransformation, { + x: 0, + y: 0, + }); + const viewBoxBottomRight = matrix.applyToPoint( + inverseViewTransformation, + { + x: get().rendererSize.width, + y: get().rendererSize.height, + } + ); + return { + x: viewBoxTopLeft.x, + y: viewBoxTopLeft.y, + width: viewBoxBottomRight.x - viewBoxTopLeft.x, + height: viewBoxBottomRight.y - viewBoxTopLeft.y, + }; + }, + }), + postCreate(editorStore) { + resizeObserver = new ResizeObserver((entries) => { + if (!entries[0]) return; + editorStore.setState({ rendererSize: entries[0].contentRect }); + }); + }, + }; +}; + +export default createComponentEditorStorePerspectiveSlice; diff --git a/src/pages/edit/Editor/store/slices/perspective/types.ts b/src/pages/edit/Editor/store/slices/perspective/types.ts new file mode 100644 index 0000000..e29b4de --- /dev/null +++ b/src/pages/edit/Editor/store/slices/perspective/types.ts @@ -0,0 +1,11 @@ +import type * as matrix from "transformation-matrix"; + +export type PerspectiveStoreSlice = { + rendererSize: { width: number; height: number }; + userPerspectiveTransformation: matrix.Matrix; + registerRendererElement: (element: SVGSVGElement | null) => void; + setUserPerspectiveTransformation: (transformation: matrix.Matrix) => void; + getViewTransformation(): matrix.Matrix; + getInverseViewTransformation(): matrix.Matrix; + getViewBox(): { x: number; y: number; width: number; height: number }; +}; diff --git a/src/pages/edit/Editor/store/types.ts b/src/pages/edit/Editor/store/types.ts new file mode 100644 index 0000000..d8be9ec --- /dev/null +++ b/src/pages/edit/Editor/store/types.ts @@ -0,0 +1,23 @@ +import type { StoreApi } from "zustand"; +import type CCStore from "../../../../store"; +import type { CCComponentId } from "../../../../store/component"; +import type { EditorStoreCoreSlice } from "./slices/core/types"; +import type { ContextMenuStoreSlice } from "./slices/contextMenu/types"; +import type { PerspectiveStoreSlice } from "./slices/perspective/types"; + +export type ComponentEditorStoreValue = { + readonly componentId: CCComponentId; +} & EditorStoreCoreSlice & + PerspectiveStoreSlice & + ContextMenuStoreSlice; + +export type ComponentEditorSliceCreator = (props: { + store: CCStore; + componentId: CCComponentId; +}) => { + define: ( + set: (reducer: (state: T) => T) => void, + get: () => ComponentEditorStoreValue + ) => T; + postCreate?: (editorStore: StoreApi) => void; +}; diff --git a/src/pages/edit/Editor/store/worldPerspective.ts b/src/pages/edit/Editor/store/worldPerspective.ts deleted file mode 100644 index 161e36b..0000000 --- a/src/pages/edit/Editor/store/worldPerspective.ts +++ /dev/null @@ -1,57 +0,0 @@ -import * as PIXI from "pixi.js"; -import type { Perspective } from "../../../../common/perspective"; - -export type WorldPerspectiveStoreMixin = { - canvasSize: PIXI.Point; - worldPerspective: Perspective; - setCanvasSize(size: PIXI.Point): void; - setWorldPerspective(perspective: Perspective): void; - toWorldPosition(canvasPosition: PIXI.Point): PIXI.Point; - toCanvasPosition(worldPosition: PIXI.Point): PIXI.Point; - zoom(zoomCenter: PIXI.Point, factor: number): void; -}; - -export function worldPerspectiveStoreMixin( - set: ( - reducer: (state: WorldPerspectiveStoreMixin) => WorldPerspectiveStoreMixin - ) => void, - get: () => WorldPerspectiveStoreMixin -): WorldPerspectiveStoreMixin { - return { - canvasSize: new PIXI.Point(0, 0), - worldPerspective: { - center: new PIXI.Point(0, 0), - scale: 1, - }, - setCanvasSize(canvasSize) { - set((state) => ({ ...state, canvasSize })); - }, - setWorldPerspective(worldPerspective) { - set((state) => ({ ...state, worldPerspective })); - }, - toWorldPosition(canvasPosition) { - return canvasPosition - .subtract(get().canvasSize.multiplyScalar(0.5)) - .multiplyScalar(1 / get().worldPerspective.scale) - .add(get().worldPerspective.center); - }, - toCanvasPosition(worldPosition) { - return worldPosition - .subtract(get().worldPerspective.center) - .multiplyScalar(get().worldPerspective.scale) - .add(get().canvasSize.multiplyScalar(0.5)); - }, - zoom(zoomCenter, factor) { - set((state) => ({ - ...state, - worldPerspective: { - center: state.worldPerspective.center - .subtract(zoomCenter) - .multiplyScalar(1 / factor) - .add(zoomCenter), - scale: state.worldPerspective.scale * factor, - }, - })); - }, - }; -} diff --git a/src/pages/edit/SidePanel.tsx b/src/pages/edit/SidePanel.tsx index e6d137d..af60463 100644 --- a/src/pages/edit/SidePanel.tsx +++ b/src/pages/edit/SidePanel.tsx @@ -1,10 +1,9 @@ import { Box, InputAdornment, TextField } from "@mui/material"; import invariant from "tiny-invariant"; import nullthrows from "nullthrows"; -import { Color } from "pixi.js"; import { useState } from "react"; import { Search } from "@mui/icons-material"; -import useAllComponents from "../../store/react/selectors"; +import { useComponents } from "../../store/react/selectors"; import { useStore } from "../../store/react"; import { isIncluding, type CCComponentId } from "../../store/component"; import { ccPinTypes } from "../../store/componentPin"; @@ -33,8 +32,8 @@ function ComponentRenderer({ componentId }: { componentId: CCComponentId }) { gridTemplateColumns: "1fr 1fr", alignItems: "center", marginTop: "4px", - border: `2px solid ${new Color(blackColor).toHex()}`, - background: new Color(whiteColor).toHex(), + border: `2px solid ${blackColor}`, + background: whiteColor, }} > {ccPinTypes.map((type) => ( @@ -75,9 +74,9 @@ function ComponentRenderer({ componentId }: { componentId: CCComponentId }) { style={{ width: "10px", height: "10px", - border: `2px solid ${new Color(blackColor).toHex()}`, + border: `2px solid ${blackColor}`, borderRadius: "4px", - background: new Color(whiteColor).toHex(), + background: whiteColor, }} />
{pin.name}
@@ -93,7 +92,7 @@ function ComponentRenderer({ componentId }: { componentId: CCComponentId }) { export default function SidePanel(sidePanelProps: SidePanelProps) { const { editedComponentId } = sidePanelProps; const { store } = useStore(); - const components = useAllComponents(); + const components = useComponents(); const [searchText, setSearchText] = useState(""); return ( diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index db41cf1..e967ee8 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -6,7 +6,7 @@ import { Add as AddIcon, } from "@mui/icons-material"; import { CCComponentStore, type CCComponentId } from "../../store/component"; -import useAllComponents from "../../store/react/selectors"; +import { useComponents } from "../../store/react/selectors"; import { useStore } from "../../store/react"; import { type CCStorePropsFromJson } from "../../store"; import { ComponentPropertyDialog } from "../../components/ComponentPropertyDialog"; @@ -17,7 +17,7 @@ export type HomePageProps = { export default function HomePage({ onComponentSelected }: HomePageProps) { const { store, resetStore } = useStore(); - const components = useAllComponents().filter( + const components = useComponents().filter( (component) => !component.isIntrinsic ); const downloadStore = () => { diff --git a/src/store/component.ts b/src/store/component.ts index ab74b40..df1a78e 100644 --- a/src/store/component.ts +++ b/src/store/component.ts @@ -80,14 +80,6 @@ export class CCComponentStore extends EventEmitter { return this.#components.get(id); } - /** - * Get all of components - * @returns map of id and component (read only) - */ - getAll(): ReadonlyMap { - return this.#components; - } - /** * Update the name of component * @param id id of component @@ -119,7 +111,7 @@ export class CCComponentStore extends EventEmitter { * Get array of components * @returns array of components */ - toArray(): CCComponent[] { + getMany(): CCComponent[] { return [...this.#components.values()]; } } @@ -219,7 +211,7 @@ function validateComponent(store: CCStore, componentId: CCComponentId) { } export function validateAllComponents(store: CCStore) { - for (const component of store.components.toArray()) { + for (const component of store.components.getMany()) { validateComponent(store, component.id); } } diff --git a/src/store/componentEvaluator.ts b/src/store/componentEvaluator.ts index a62ed91..ab5141b 100644 --- a/src/store/componentEvaluator.ts +++ b/src/store/componentEvaluator.ts @@ -8,669 +8,7 @@ import type { CCComponentPinId } from "./componentPin"; import type { SimulationFrame, SimulationValue, -} from "../pages/edit/Editor/store"; - -type ComponentEvaluationResult = { - readonly output: Map; - readonly outputNodePinValues?: Map; -}; - -/** - * Class for component evaluator - */ -export default class CCComponentEvaluator { - #flipFlopValue: Map = new Map(); - - #store: CCStore; - - #childrenEvaluator: Map = new Map(); - - /** - * Constructor of CCComponentEvaluator - * @param store store - */ - constructor(store: CCStore) { - this.#store = store; - } - - /** - * Evaluate intrinsic component - * @param componentId id of component - * @param input map of input pins and their values - * @param timeStep time step - * @param nodeId id of node - * @returns map of output pins and their values - */ - evaluateIntrinsic( - nodeId: CCNodeId, - input: Map, - timeStep: number - ): Map | null { - const node = this.#store.nodes.get(nodeId)!; - const { componentId } = node; - const pinIds = - this.#store.componentPins.getPinIdsByComponentId(componentId); - const nodePins = this.#store.nodePins.getManyByNodeId(nodeId); - const store = this.#store; - const inputNodePins = nodePins.filter((nodePin: CCNodePin) => { - const componentPin = store.componentPins.get(nodePin.componentPinId)!; - return componentPin.type === "input"; - }); - const outputNodePins = nodePins.filter((nodePin: CCNodePin) => { - const componentPin = store.componentPins.get(nodePin.componentPinId)!; - return componentPin.type === "output"; - }); - switch (componentId) { - case intrinsics.notIntrinsicComponent.id: { - invariant(pinIds.length === 2); - const inputPinId = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.notIntrinsicComponentInputPin.id - )!.id; - const inputValue = input.get(inputPinId); - const outputPinId = outputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.notIntrinsicComponentOutputPin.id - )!.id; - const outputValue = []; - for (const value of inputValue!) { - outputValue.push(!value); - } - const outputMap = new Map(); - outputMap.set(outputPinId, outputValue); - return outputMap; - } - case intrinsics.andIntrinsicComponent.id: { - invariant(pinIds.length === 3); - const inputPinId0 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.andIntrinsicComponentInputPinA.id - )!.id; - const inputPinId1 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.andIntrinsicComponentInputPinB.id - )!.id; - const inputValue0 = input.get(inputPinId0); - const inputValue1 = input.get(inputPinId1); - const outputPinId = outputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.andIntrinsicComponentOutputPin.id - )!.id; - const outputValue = []; - if (inputValue0!.length !== inputValue1!.length) { - return null; - } - for (let i = 0; i < inputValue0!.length; i += 1) { - outputValue.push(inputValue0![i]! && inputValue1![i]!); - } - const outputMap = new Map(); - outputMap.set(outputPinId, outputValue); - return outputMap; - } - case intrinsics.orIntrinsicComponent.id: { - invariant(pinIds.length === 3); - const inputPinId0 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.orIntrinsicComponentInputPinA.id - )!.id; - const inputPinId1 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.orIntrinsicComponentInputPinB.id - )!.id; - const inputValue0 = input.get(inputPinId0); - const inputValue1 = input.get(inputPinId1); - const outputPinId = outputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.orIntrinsicComponentOutputPin.id - )!.id; - const outputValue = []; - if (inputValue0!.length !== inputValue1!.length) { - return null; - } - for (let i = 0; i < inputValue0!.length; i += 1) { - outputValue.push(inputValue0![i]! || inputValue1![i]!); - } - const outputMap = new Map(); - outputMap.set(outputPinId, outputValue); - return outputMap; - } - case intrinsics.xorIntrinsicComponent.id: { - invariant(pinIds.length === 3); - const inputPinId0 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.xorIntrinsicComponentInputPinA.id - )!.id; - const inputPinId1 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.xorIntrinsicComponentInputPinB.id - )!.id; - const inputValue0 = input.get(inputPinId0); - const inputValue1 = input.get(inputPinId1); - const outputPinId = outputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.xorIntrinsicComponentOutputPin.id - )!.id; - const outputValue = []; - if (inputValue0!.length !== inputValue1!.length) { - return null; - } - for (let i = 0; i < inputValue0!.length; i += 1) { - outputValue.push(inputValue0![i]! !== inputValue1![i]!); - } - const outputMap = new Map(); - outputMap.set(outputPinId, outputValue); - return outputMap; - } - case intrinsics.inputIntrinsicComponent.id: { - invariant(pinIds.length === 2); - const inputPinId = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.inputIntrinsicComponentInputPin.id - )!.id; - const inputValue = input.get(inputPinId); - const outputPinId = outputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.inputIntrinsicComponentOutputPin.id - )!.id; - const outputValue = []; - for (const value of inputValue!) { - outputValue.push(!value); - } - const outputMap = new Map(); - outputMap.set(outputPinId, outputValue); - return outputMap; - } - case intrinsics.fourBitsIntrinsicComponent.id: { - invariant(pinIds.length === 5); - const inputPinId0 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.fourBitsIntrinsicComponentInputPin0.id - )!.id; - const inputPinId1 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.fourBitsIntrinsicComponentInputPin1.id - )!.id; - const inputPinId2 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.fourBitsIntrinsicComponentInputPin2.id - )!.id; - const inputPinId3 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.fourBitsIntrinsicComponentInputPin3.id - )!.id; - const inputValue0 = input.get(inputPinId0); - const inputValue1 = input.get(inputPinId1); - const inputValue2 = input.get(inputPinId2); - const inputValue3 = input.get(inputPinId3); - const outputPinId = outputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.fourBitsIntrinsicComponentOutputPin.id - )!.id; - if ( - inputValue0!.length === 1 && - inputValue1!.length === 1 && - inputValue2!.length === 1 && - inputValue3!.length === 1 - ) { - const outputValue = []; - outputValue.push(inputValue3![0]!); - outputValue.push(inputValue2![0]!); - outputValue.push(inputValue1![0]!); - outputValue.push(inputValue0![0]!); - const outputMap = new Map(); - outputMap.set(outputPinId, outputValue); - return outputMap; - } - return null; - } - case intrinsics.distributeFourBitsIntrinsicComponent.id: { - invariant(pinIds.length === 5); - const inputPinId = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.distributeFourBitsIntrinsicComponentInputPin.id - )!.id; - const inputValue = input.get(inputPinId); - const outputPinId0 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.distributeFourBitsIntrinsicComponentOutputPin0.id - )!.id; - const outputPinId1 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.distributeFourBitsIntrinsicComponentOutputPin1.id - )!.id; - const outputPinId2 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.distributeFourBitsIntrinsicComponentOutputPin2.id - )!.id; - const outputPinId3 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.distributeFourBitsIntrinsicComponentOutputPin3.id - )!.id; - if (inputValue!.length === 4) { - const outputMap = new Map(); - outputMap.set(outputPinId0, [inputValue![3]!]); - outputMap.set(outputPinId1, [inputValue![2]!]); - outputMap.set(outputPinId2, [inputValue![1]!]); - outputMap.set(outputPinId3, [inputValue![0]!]); - return outputMap; - } - return null; - } - case intrinsics.flipFlopIntrinsicComponent.id: { - invariant(pinIds.length === 2); - const inputPinId = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.flipFlopIntrinsicComponentInputPin.id - )!.id; - if (timeStep === 0) { - const multiplicity = - this.#store.nodePins.getNodePinMultiplexability(inputPinId); - if (multiplicity.isMultiplexable) { - this.#flipFlopValue.set(nodeId!, [false]); - } else { - this.#flipFlopValue.set( - nodeId!, - Array.from({ length: multiplicity.multiplicity }, () => false) - ); - } - } - const inputValue = input.get(inputPinId); - - const outputPinId = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.flipFlopIntrinsicComponentOutputPin.id - )!.id; - - const outputValue = this.#flipFlopValue.get(nodeId!); - - if (inputValue) { - this.#flipFlopValue.set(nodeId!, inputValue!); - } - - const outputMap = new Map(); - if (timeStep > 0 && outputValue) { - outputMap.set(outputPinId, outputValue); - } else if (inputValue) { - const initialValue = []; - for (let i = 0; i < inputValue!.length; i += 1) { - initialValue.push(false); - } - outputMap.set(outputPinId, initialValue); - } else { - // TODO: decide length of value (temporary: 1 bit) - outputMap.set(outputPinId, [false]); - } - return outputMap; - } - // case "Sample": - // return true; - default: - throw new Error(`invalid component (${componentId})`); - } - } - - /** - * Check if nodes and connections of the component of `componentId` are forming a cycle - * @param componentId id of component - * @returns if nodes and connections of the component of `componentId` are forming a cycle, `true` returns (otherwise `false`) - */ - isCyclic(componentId: CCComponentId): boolean { - const seen = new Set(); - const finished = new Set(); - const nodes = this.#store.nodes.getManyByParentComponentId(componentId); - const dfs = (nodeId: CCNodeId) => { - const node = this.#store.nodes.get(nodeId)!; - const pins = this.#store.nodePins.getManyByNodeId(node.id); - const connections = this.#store.connections.getConnectionsByNodePinId( - pins.find((pin: CCNodePin) => { - const componentPin = this.#store.componentPins.get( - pin.componentPinId - )!; - return componentPin.type === "output"; - })!.id - ); - seen.add(nodeId); - for (const connection of connections!) { - const connectedNodePin = this.#store.nodePins.get(connection.to)!; - const connectedNodeId = connectedNodePin.nodeId; - if (!finished.has(connectedNodePin.nodeId)) { - if (seen.has(connectedNodeId)) return true; - if (dfs(connectedNodeId)) return true; - } - } - finished.add(nodeId); - return false; - }; - return dfs(nodes[0]!.id); - } - - /** - * Evaluate component - * @param componentId id of component - * @param input map of input pins and their values - * @param timeStep time step - * @param _nodeId id of node - * @returns result of evaluation - */ - evaluateNode( - nodeId: CCNodeId, - input: Map, - timeStep: number - ): Map | null { - const node = this.#store.nodes.get(nodeId)!; - const component = this.#store.components.get(node.componentId); - if (!component) throw new Error(`Component ${component} is not defined.`); - if (component.isIntrinsic) { - const output = this.evaluateIntrinsic(nodeId, input, timeStep); - if (!output) { - return null; - } - return output; - } - if (this.isCyclic(component.id)) { - return null; - } - const nodePins = this.#store.nodePins.getManyByNodeId(nodeId); - const children = this.#store.nodes.getManyByParentComponentId(component.id); - const foundInputNumber = new Map(); - const inputNumber = new Map(); - const inputValues = new Map(); - for (const child of children) { - foundInputNumber.set(child.id, 0); - const innerPins = this.#store.nodePins.getManyByNodeId(child.id); - let inputPinNumber = 0; - for (const innerPin of innerPins) { - const componentPin = this.#store.componentPins.get( - innerPin.componentPinId - )!; - if (componentPin.type === "input") { - inputPinNumber += 1; - } - } - inputNumber.set(child.id, inputPinNumber); - } - for (const nodePin of nodePins) { - const componentPin = this.#store.componentPins.get( - nodePin.componentPinId - )!; - if (componentPin.type === "input" && componentPin.implementation) { - const connectedNodePin = this.#store.nodePins.get( - componentPin.implementation - )!; - inputValues.set(connectedNodePin.id, input.get(nodePin.id)!); - foundInputNumber.set( - connectedNodePin.nodeId, - foundInputNumber.get(connectedNodePin.nodeId)! + 1 - ); - } - } - const unevaluatedNodes = new Set(); - for (const child of children) { - unevaluatedNodes.add(child.id); - } - - const output = new Map(); - const visitedFlipFlops = new Set(); - - while (unevaluatedNodes.size > 0) { - const currentNodeId = [...unevaluatedNodes][0]!; - unevaluatedNodes.delete(currentNodeId); - const currentNode = this.#store.nodes.get(currentNodeId)!; - const currentComponentId = currentNode.componentId; - - if ( - inputNumber.get(currentNodeId)! === foundInputNumber.get(currentNodeId)! - ) { - if (!this.#childrenEvaluator.has(currentNodeId)) { - this.#childrenEvaluator.set( - currentNodeId, - new CCComponentEvaluator(this.#store) - ); - } - const evaluator = this.#childrenEvaluator.get(currentNodeId)!; - const result = evaluator.evaluateNode( - currentNodeId, - inputValues, - timeStep - ); - if (!result) { - return null; - } - for (const [outputPinId, outputValue] of result) { - if (!visitedFlipFlops.has(currentNodeId)) { - const connections = - this.#store.connections.getConnectionsByNodePinId(outputPinId)!; - if (connections.length !== 0) { - for (const connection of connections) { - const connectedNodePin = this.#store.nodePins.get( - connection.to - )!; - inputValues.set(connectedNodePin.id, outputValue); - foundInputNumber.set( - connectedNodePin.nodeId, - foundInputNumber.get(connectedNodePin.nodeId)! + 1 - ); - } - } else { - const parentNodePin = nodePins.find((nodePin) => { - const componentPin = this.#store.componentPins.get( - nodePin.componentPinId - )!; - return ( - componentPin.type === "output" && - componentPin.implementation === outputPinId - ); - })!; - output.set(parentNodePin.id, outputValue); - } - if ( - currentComponentId === intrinsics.flipFlopIntrinsicComponent.id - ) { - visitedFlipFlops.add(currentNodeId); - } - } - } - } else if ( - currentComponentId === intrinsics.flipFlopIntrinsicComponent.id && - !visitedFlipFlops.has(currentNodeId) - ) { - if (!this.#childrenEvaluator.has(currentNodeId)) { - this.#childrenEvaluator.set( - currentNodeId, - new CCComponentEvaluator(this.#store) - ); - } - const evaluator = this.#childrenEvaluator.get(currentNodeId)!; - const result = evaluator.evaluateNode( - currentNodeId, - inputValues, - timeStep - ); - if (!result) { - return null; - } - for (const [outputPinId, outputValue] of result) { - if (!visitedFlipFlops.has(currentNodeId)) { - const connections = - this.#store.connections.getConnectionsByNodePinId(outputPinId)!; - if (connections.length !== 0) { - for (const connection of connections) { - const connectedNodePin = this.#store.nodePins.get( - connection.to - )!; - inputValues.set(connectedNodePin.id, outputValue); - foundInputNumber.set( - connectedNodePin.nodeId, - foundInputNumber.get(connectedNodePin.nodeId)! + 1 - ); - } - } else { - const parentNodePin = nodePins.find((nodePin) => { - const componentPin = this.#store.componentPins.get( - nodePin.componentPinId - )!; - return ( - componentPin.type === "output" && - componentPin.implementation === outputPinId - ); - })!; - output.set(parentNodePin.id, outputValue); - } - if ( - currentComponentId === intrinsics.flipFlopIntrinsicComponent.id - ) { - visitedFlipFlops.add(currentNodeId); - } - } - } - visitedFlipFlops.add(currentNodeId); - unevaluatedNodes.add(currentNodeId); - } else { - unevaluatedNodes.add(currentNodeId); - } - } - return output; - } - - evaluateComponent( - componentId: CCComponentId, - input: Map, - timeStep: number - ): ComponentEvaluationResult | null { - const component = this.#store.components.get(componentId); - if (!component) throw new Error(`Component ${component} is not defined.`); - if (this.isCyclic(component.id)) { - return null; - } - const componentPins = - this.#store.componentPins.getManyByComponentId(componentId); - const children = this.#store.nodes.getManyByParentComponentId(component.id); - const foundInputNumber = new Map(); - const inputNumber = new Map(); - const inputValues = new Map(); - for (const child of children) { - foundInputNumber.set(child.id, 0); - const innerPins = this.#store.nodePins.getManyByNodeId(child.id); - let inputPinNumber = 0; - for (const innerPin of innerPins) { - const componentPin = this.#store.componentPins.get( - innerPin.componentPinId - )!; - if (componentPin.type === "input") { - inputPinNumber += 1; - } - } - inputNumber.set(child.id, inputPinNumber); - } - for (const componentPin of componentPins) { - if (componentPin.type === "input" && componentPin.implementation) { - const connectedNodePin = this.#store.nodePins.get( - componentPin.implementation - )!; - inputValues.set(connectedNodePin.id, input.get(componentPin.id)!); - foundInputNumber.set( - connectedNodePin.nodeId, - foundInputNumber.get(connectedNodePin.nodeId)! + 1 - ); - } - } - const unevaluatedNodes = new Set(); - for (const child of children) { - unevaluatedNodes.add(child.id); - } - - const output = new Map(); - const outputNodePinValues = new Map(); - const visitedFlipFlops = new Set(); - - while (unevaluatedNodes.size > 0) { - const currentNodeId = [...unevaluatedNodes][0]!; - unevaluatedNodes.delete(currentNodeId); - const currentNode = this.#store.nodes.get(currentNodeId)!; - const currentComponentId = currentNode.componentId; - - if ( - inputNumber.get(currentNodeId)! === foundInputNumber.get(currentNodeId)! - ) { - if (!this.#childrenEvaluator.has(currentNodeId)) { - this.#childrenEvaluator.set( - currentNodeId, - new CCComponentEvaluator(this.#store) - ); - } - const evaluator = this.#childrenEvaluator.get(currentNodeId)!; - const result = evaluator.evaluateNode( - currentNodeId, - inputValues, - timeStep - ); - if (!result) { - return null; - } - for (const [outputPinId, outputValue] of result) { - outputNodePinValues.set(outputPinId, outputValue); - if (!visitedFlipFlops.has(currentNodeId)) { - const connections = - this.#store.connections.getConnectionsByNodePinId(outputPinId)!; - if (connections.length !== 0) { - for (const connection of connections) { - const connectedNodePin = this.#store.nodePins.get( - connection.to - )!; - inputValues.set(connectedNodePin.id, outputValue); - foundInputNumber.set( - connectedNodePin.nodeId, - foundInputNumber.get(connectedNodePin.nodeId)! + 1 - ); - } - } else { - const parentComponentPin = componentPins.find((componentPin) => { - return ( - componentPin.type === "output" && - componentPin.implementation === outputPinId - ); - })!; - output.set(parentComponentPin.id, outputValue); - } - if ( - currentComponentId === intrinsics.flipFlopIntrinsicComponent.id - ) { - visitedFlipFlops.add(currentNodeId); - } - } - } - } else { - unevaluatedNodes.add(currentNodeId); - } - } - return { output, outputNodePinValues }; - } -} +} from "../pages/edit/Editor/store/slices/core"; function simulateIntrinsic( store: CCStore, @@ -820,90 +158,33 @@ function simulateIntrinsic( return outputValues; } case intrinsics.fourBitsIntrinsicComponent.id: { - invariant(pinIds.length === 5); - const inputPinId0 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.fourBitsIntrinsicComponentInputPin0.id - )!.id; - const inputPinId1 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.fourBitsIntrinsicComponentInputPin1.id - )!.id; - const inputPinId2 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.fourBitsIntrinsicComponentInputPin2.id - )!.id; - const inputPinId3 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.fourBitsIntrinsicComponentInputPin3.id - )!.id; - const inputValue0 = inputValues.get(inputPinId0); - const inputValue1 = inputValues.get(inputPinId1); - const inputValue2 = inputValues.get(inputPinId2); - const inputValue3 = inputValues.get(inputPinId3); + invariant(node.variablePins); + const outputValue = node.variablePins.flatMap( + (variablePin) => inputValues.get(variablePin)! + ); + const outputMap = new Map(); const outputPinId = outputNodePins.find( (nodePin: CCNodePin) => nodePin.componentPinId === intrinsics.fourBitsIntrinsicComponentOutputPin.id )!.id; - if ( - inputValue0!.length === 1 && - inputValue1!.length === 1 && - inputValue2!.length === 1 && - inputValue3!.length === 1 - ) { - const outputValue = []; - outputValue.push(inputValue3![0]!); - outputValue.push(inputValue2![0]!); - outputValue.push(inputValue1![0]!); - outputValue.push(inputValue0![0]!); - const outputValues = new Map(); - outputValues.set(outputPinId, outputValue); - return outputValues; - } - return null; + outputMap.set(outputPinId, outputValue); + return outputMap; } case intrinsics.distributeFourBitsIntrinsicComponent.id: { - invariant(pinIds.length === 5); const inputPinId = inputNodePins.find( (nodePin: CCNodePin) => nodePin.componentPinId === intrinsics.distributeFourBitsIntrinsicComponentInputPin.id )!.id; - const inputValue = inputValues.get(inputPinId); - const outputPinId0 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.distributeFourBitsIntrinsicComponentOutputPin0.id - )!.id; - const outputPinId1 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.distributeFourBitsIntrinsicComponentOutputPin1.id - )!.id; - const outputPinId2 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.distributeFourBitsIntrinsicComponentOutputPin2.id - )!.id; - const outputPinId3 = inputNodePins.find( - (nodePin: CCNodePin) => - nodePin.componentPinId === - intrinsics.distributeFourBitsIntrinsicComponentOutputPin3.id - )!.id; - if (inputValue!.length === 4) { - const outputValues = new Map(); - outputValues.set(outputPinId0, [inputValue![3]!]); - outputValues.set(outputPinId1, [inputValue![2]!]); - outputValues.set(outputPinId2, [inputValue![1]!]); - outputValues.set(outputPinId3, [inputValue![0]!]); - return outputValues; - } - return null; + const inputs = inputValues.get(inputPinId)!; + const outputMap = new Map(); + invariant(node.variablePins); + invariant(node.variablePins.length === inputs.length); + for (let i = 0; i < node.variablePins.length; i += 1) { + outputMap.set(node.variablePins[i]!, [inputs[i]!]); + } + return outputMap; } case intrinsics.flipFlopIntrinsicComponent.id: { invariant(pinIds.length === 2); @@ -964,7 +245,6 @@ function simulateNode( inputValues, previousFrame ); - console.log(outputValues); if (!outputValues) { return null; } @@ -1139,14 +419,12 @@ function simulateNode( return { outputValues, pins, child }; } -export function simulateComponent( +export default function simulateComponent( store: CCStore, componentId: CCComponentId, inputValues: Map, previousFrame: SimulationFrame | null ): SimulationFrame | null { - // return null; - console.log(previousFrame); const component = store.components.get(componentId); if (!component) throw new Error(`Component ${component} is not defined.`); const childMap = new Map< @@ -1214,10 +492,17 @@ export function simulateComponent( } return previousFrame.nodes.get(currentNodeId)!.child; })(); + const currentNodePinInputValues = new Map(); + for (const nodePin of store.nodePins.getManyByNodeId(currentNodeId)) { + currentNodePinInputValues.set( + nodePin.id, + nodePinInputValues.get(nodePin.id)! + ); + } const result = simulateNode( store, currentNodeId, - nodePinInputValues, + currentNodePinInputValues, frame ); if (!result) { diff --git a/src/store/componentPin.ts b/src/store/componentPin.ts index 3112132..21d78d5 100644 --- a/src/store/componentPin.ts +++ b/src/store/componentPin.ts @@ -12,10 +12,6 @@ export type CCComponentPin = { readonly componentId: CCComponentId; readonly type: CCPinType; readonly implementation: CCPinImplementation; - /** @deprecated should only be defined for intrinsic components */ - multiplexable: boolean; - /** @deprecated should only be defined for intrinsic components */ - bits: number; name: string; }; @@ -42,6 +38,10 @@ export type CCPinMultiplexability = | { isMultiplexable: true } | { isMultiplexable: false; multiplicity: number }; +export type CCComponentPinMultiplexability = + | CCPinMultiplexability + | "undecidable"; + export type CCComponentPinStoreEvents = { didRegister(pin: CCComponentPin): void; willUnregister(pin: CCComponentPin): void; @@ -132,8 +132,6 @@ export class CCComponentPinStore extends EventEmitter componentId: targetNode.parentComponentId, name: targetComponentPin.name, implementation: targetNodePin.id, - multiplexable: false, // dummy - bits: 1, // dummy }); } @@ -222,7 +220,7 @@ export class CCComponentPinStore extends EventEmitter */ getComponentPinMultiplexability( pinId: CCComponentPinId - ): CCPinMultiplexability { + ): CCComponentPinMultiplexability { const pin = this.#pins.get(pinId); invariant(pin); switch (pin.id) { @@ -243,22 +241,18 @@ export class CCComponentPinStore extends EventEmitter case intrinsic.flipFlopIntrinsicComponentOutputPin.id: { return { isMultiplexable: true }; } - case intrinsic.fourBitsIntrinsicComponentInputPin0.id: - case intrinsic.fourBitsIntrinsicComponentInputPin1.id: - case intrinsic.fourBitsIntrinsicComponentInputPin2.id: - case intrinsic.fourBitsIntrinsicComponentInputPin3.id: { + case intrinsic.fourBitsIntrinsicComponentInputPin.id: { return { isMultiplexable: false, multiplicity: 1 }; } case intrinsic.fourBitsIntrinsicComponentOutputPin.id: { - return { isMultiplexable: false, multiplicity: 4 }; + return "undecidable"; + // return { isMultiplexable: false, multiplicity: 4 }; } case intrinsic.distributeFourBitsIntrinsicComponentInputPin.id: { - return { isMultiplexable: false, multiplicity: 4 }; + return "undecidable"; + // return { isMultiplexable: false, multiplicity: 4 }; } - case intrinsic.distributeFourBitsIntrinsicComponentOutputPin0.id: - case intrinsic.distributeFourBitsIntrinsicComponentOutputPin1.id: - case intrinsic.distributeFourBitsIntrinsicComponentOutputPin2.id: - case intrinsic.distributeFourBitsIntrinsicComponentOutputPin3.id: { + case intrinsic.distributeFourBitsIntrinsicComponentOutputPin.id: { return { isMultiplexable: false, multiplicity: 1 }; } default: { @@ -288,7 +282,7 @@ export class CCComponentPinStore extends EventEmitter * Get array of pins * @returns array of pins */ - toArray(): CCComponentPin[] { + getMany(): CCComponentPin[] { return [...this.#pins.values()]; } diff --git a/src/store/connection.ts b/src/store/connection.ts index 11caf2f..1edcd84 100644 --- a/src/store/connection.ts +++ b/src/store/connection.ts @@ -163,7 +163,7 @@ export class CCConnectionStore extends EventEmitter { * Get array of connections * @returns array of connections */ - toArray(): CCConnection[] { + getMany(): CCConnection[] { return [...this.#connections.values()]; } } diff --git a/src/store/evaluation.ts b/src/store/evaluation.ts deleted file mode 100644 index 1b0f3ce..0000000 --- a/src/store/evaluation.ts +++ /dev/null @@ -1,116 +0,0 @@ -import invariant from "tiny-invariant"; -import type CCStore from "."; -import type { CCComponentId } from "./component"; -import type { CCComponentPinId } from "./componentPin"; -import CCComponentEvaluator from "./componentEvaluator"; - -export type CCEvaluationId = string; - -// type EvaluationCache = { -// output: Map; -// }; - -/** - * Class for evaluation - */ -export default class CCEvaluation { - // #cache: Map; - - // static readonly #cacheSize = 5; - - #store: CCStore; - - #componentEvaluator: CCComponentEvaluator | null; - - /** - * Constructor of CCEvaluation - * @param store store - */ - constructor(store: CCStore) { - // this.#cache = new Map(); - // this.#previousValueOfOutputNodePins = null; - this.#store = store; - this.#componentEvaluator = null; - } - - /** - * Clear the cache and the previous value of output of node pins, and reset the component evaluator - */ - clear() { - // this.#cache.clear(); - // this.#previousValueOfOutputNodePins?.clear(); - this.#componentEvaluator = null; - } - - /** - * Create id of evaluation for cache - * @param componentId id of component - * @param input map of input pins and their values - * @param timeStep time step - * @returns id of evaluation - */ - static createId( - componentId: CCComponentId, - input: Map, - timeStep: number - ): CCEvaluationId { - let id = ""; - id += componentId as string; - id += "_"; - for (const [key, values] of input) { - id += key; - id += "_"; - for (const value of values) { - if (value) { - id += "1"; - } else { - id += "0"; - } - id += "_"; - } - } - id += timeStep; - return id; - } - - /** - * Get the calculated value of a pin - * @param nodeId id of node - * @param pinId id of pin - * @returns calculated value of pin - */ - // getCalculatedPinValue(nodePinId: CCNodePinId): boolean[] | null { - // if (this.#previousValueOfOutputNodePins) { - // return this.#previousValueOfOutputNodePins.get(nodePinId)!; - // } - // return null; - // } - - /** - * Evaluate the component - * @param componentId id of component - * @param input map of input pins and their values - * @param timeStep time step - * @returns map of output pins and their values - */ - evaluate( - componentId: CCComponentId, - input: Map, - timeStep: number - ): Map | null { - if (!this.#componentEvaluator) { - this.#componentEvaluator = new CCComponentEvaluator(this.#store); - } - const { output, outputNodePinValues } = - this.#componentEvaluator.evaluateComponent(componentId, input, timeStep)!; - invariant(outputNodePinValues); - // if (this.#cache.size >= CCEvaluation.#cacheSize) { - // this.#cache.delete([...this.#cache.keys()][0]!); - // } - // this.#cache.set(id, { - // output, - // outputNodePinValues, - // }); - return output; - } -} diff --git a/src/store/index.ts b/src/store/index.ts index c046424..c25ca3c 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -68,11 +68,11 @@ export default class CCStore { */ toJSON() { return JSON.stringify({ - components: this.components.toArray(), - nodes: this.nodes.toArray(), - componentPins: this.componentPins.toArray(), - nodePins: this.nodePins.toArray(), - connections: this.connections.toArray(), + components: this.components.getMany(), + nodes: this.nodes.getMany(), + componentPins: this.componentPins.getMany(), + nodePins: this.nodePins.getMany(), + connections: this.connections.getMany(), }); } } diff --git a/src/store/intrinsics.ts b/src/store/intrinsics.ts index b71b767..e677d0a 100644 --- a/src/store/intrinsics.ts +++ b/src/store/intrinsics.ts @@ -15,8 +15,6 @@ export const andIntrinsicComponentInputPinA: CCComponentPin = { type: "input", name: "A", implementation: null, - multiplexable: true, - bits: 1, }; export const andIntrinsicComponentInputPinB: CCComponentPin = { @@ -25,8 +23,6 @@ export const andIntrinsicComponentInputPinB: CCComponentPin = { type: "input", name: "B", implementation: null, - multiplexable: true, - bits: 1, }; export const andIntrinsicComponentOutputPin: CCComponentPin = { @@ -35,8 +31,6 @@ export const andIntrinsicComponentOutputPin: CCComponentPin = { type: "output", name: "Out", implementation: null, - multiplexable: true, - bits: 1, }; export const orIntrinsicComponent: CCComponent = { @@ -51,8 +45,6 @@ export const orIntrinsicComponentInputPinA: CCComponentPin = { type: "input", name: "A", implementation: null, - multiplexable: true, - bits: 1, }; export const orIntrinsicComponentInputPinB: CCComponentPin = { @@ -61,8 +53,6 @@ export const orIntrinsicComponentInputPinB: CCComponentPin = { type: "input", name: "B", implementation: null, - multiplexable: true, - bits: 1, }; export const orIntrinsicComponentOutputPin: CCComponentPin = { @@ -71,8 +61,6 @@ export const orIntrinsicComponentOutputPin: CCComponentPin = { type: "output", name: "Out", implementation: null, - multiplexable: true, - bits: 1, }; export const notIntrinsicComponent: CCComponent = { @@ -87,8 +75,6 @@ export const notIntrinsicComponentInputPin: CCComponentPin = { type: "input", name: "In", implementation: null, - multiplexable: true, - bits: 1, }; export const notIntrinsicComponentOutputPin: CCComponentPin = { @@ -97,8 +83,6 @@ export const notIntrinsicComponentOutputPin: CCComponentPin = { type: "output", name: "Out", implementation: null, - multiplexable: true, - bits: 1, }; export const xorIntrinsicComponent: CCComponent = { @@ -113,8 +97,6 @@ export const xorIntrinsicComponentInputPinA: CCComponentPin = { type: "input", name: "A", implementation: null, - multiplexable: true, - bits: 1, }; export const xorIntrinsicComponentInputPinB: CCComponentPin = { @@ -123,8 +105,6 @@ export const xorIntrinsicComponentInputPinB: CCComponentPin = { type: "input", name: "B", implementation: null, - multiplexable: true, - bits: 1, }; export const xorIntrinsicComponentOutputPin: CCComponentPin = { @@ -133,8 +113,6 @@ export const xorIntrinsicComponentOutputPin: CCComponentPin = { type: "output", name: "Out", implementation: null, - multiplexable: true, - bits: 1, }; export const inputIntrinsicComponent: CCComponent = { @@ -149,8 +127,6 @@ export const inputIntrinsicComponentInputPin: CCComponentPin = { type: "input", name: "In", implementation: null, - multiplexable: true, - bits: 1, }; export const inputIntrinsicComponentOutputPin: CCComponentPin = { @@ -159,8 +135,6 @@ export const inputIntrinsicComponentOutputPin: CCComponentPin = { type: "output", name: "Out", implementation: null, - multiplexable: true, - bits: 1, }; export const fourBitsIntrinsicComponent: CCComponent = { @@ -169,44 +143,12 @@ export const fourBitsIntrinsicComponent: CCComponent = { name: "FourBits", }; -export const fourBitsIntrinsicComponentInputPin0: CCComponentPin = { +export const fourBitsIntrinsicComponentInputPin: CCComponentPin = { id: "ffffffff-0006-4000-8000-000000000001" as CCComponentPinId, componentId: fourBitsIntrinsicComponent.id, type: "input", name: "bit0", implementation: null, - multiplexable: false, - bits: 1, -}; - -export const fourBitsIntrinsicComponentInputPin1: CCComponentPin = { - id: "ffffffff-0006-4000-8000-000000000002" as CCComponentPinId, - componentId: fourBitsIntrinsicComponent.id, - type: "input", - name: "bit1", - implementation: null, - multiplexable: false, - bits: 1, -}; - -export const fourBitsIntrinsicComponentInputPin2: CCComponentPin = { - id: "ffffffff-0006-4000-8000-000000000003" as CCComponentPinId, - componentId: fourBitsIntrinsicComponent.id, - type: "input", - name: "bit2", - implementation: null, - multiplexable: false, - bits: 1, -}; - -export const fourBitsIntrinsicComponentInputPin3: CCComponentPin = { - id: "ffffffff-0006-4000-8000-000000000004" as CCComponentPinId, - componentId: fourBitsIntrinsicComponent.id, - type: "input", - name: "bit3", - implementation: null, - multiplexable: false, - bits: 1, }; export const fourBitsIntrinsicComponentOutputPin: CCComponentPin = { @@ -215,8 +157,6 @@ export const fourBitsIntrinsicComponentOutputPin: CCComponentPin = { type: "output", name: "Out", implementation: null, - multiplexable: false, - bits: 4, }; export const distributeFourBitsIntrinsicComponent: CCComponent = { @@ -231,48 +171,14 @@ export const distributeFourBitsIntrinsicComponentInputPin: CCComponentPin = { type: "input", name: "input", implementation: null, - multiplexable: false, - bits: 4, }; -export const distributeFourBitsIntrinsicComponentOutputPin0: CCComponentPin = { +export const distributeFourBitsIntrinsicComponentOutputPin: CCComponentPin = { id: "ffffffff-0007-4000-8000-000000000002" as CCComponentPinId, componentId: distributeFourBitsIntrinsicComponent.id, type: "output", name: "bit0", implementation: null, - multiplexable: false, - bits: 1, -}; - -export const distributeFourBitsIntrinsicComponentOutputPin1: CCComponentPin = { - id: "ffffffff-0007-4000-8000-000000000003" as CCComponentPinId, - componentId: distributeFourBitsIntrinsicComponent.id, - type: "output", - name: "bit1", - implementation: null, - multiplexable: false, - bits: 1, -}; - -export const distributeFourBitsIntrinsicComponentOutputPin2: CCComponentPin = { - id: "ffffffff-0007-4000-8000-000000000004" as CCComponentPinId, - componentId: distributeFourBitsIntrinsicComponent.id, - type: "output", - name: "bit2", - implementation: null, - multiplexable: false, - bits: 1, -}; - -export const distributeFourBitsIntrinsicComponentOutputPin3: CCComponentPin = { - id: "ffffffff-0007-4000-8000-000000000005" as CCComponentPinId, - componentId: distributeFourBitsIntrinsicComponent.id, - type: "output", - name: "bit3", - implementation: null, - multiplexable: false, - bits: 1, }; export const flipFlopIntrinsicComponent: CCComponent = { @@ -287,8 +193,6 @@ export const flipFlopIntrinsicComponentInputPin: CCComponentPin = { type: "input", name: "In", implementation: null, - multiplexable: true, - bits: 1, }; export const flipFlopIntrinsicComponentOutputPin: CCComponentPin = { @@ -297,8 +201,6 @@ export const flipFlopIntrinsicComponentOutputPin: CCComponentPin = { type: "output", name: "Out", implementation: null, - multiplexable: true, - bits: 1, }; /** @@ -327,17 +229,11 @@ export function registerIntrinsics(store: CCStore) { store.componentPins.register(inputIntrinsicComponentInputPin); store.componentPins.register(inputIntrinsicComponentOutputPin); store.components.register(fourBitsIntrinsicComponent); - store.componentPins.register(fourBitsIntrinsicComponentInputPin0); - store.componentPins.register(fourBitsIntrinsicComponentInputPin1); - store.componentPins.register(fourBitsIntrinsicComponentInputPin2); - store.componentPins.register(fourBitsIntrinsicComponentInputPin3); + store.componentPins.register(fourBitsIntrinsicComponentInputPin); store.componentPins.register(fourBitsIntrinsicComponentOutputPin); store.components.register(distributeFourBitsIntrinsicComponent); store.componentPins.register(distributeFourBitsIntrinsicComponentInputPin); - store.componentPins.register(distributeFourBitsIntrinsicComponentOutputPin0); - store.componentPins.register(distributeFourBitsIntrinsicComponentOutputPin1); - store.componentPins.register(distributeFourBitsIntrinsicComponentOutputPin2); - store.componentPins.register(distributeFourBitsIntrinsicComponentOutputPin3); + store.componentPins.register(distributeFourBitsIntrinsicComponentOutputPin); store.components.register(flipFlopIntrinsicComponent); store.componentPins.register(flipFlopIntrinsicComponentInputPin); store.componentPins.register(flipFlopIntrinsicComponentOutputPin); diff --git a/src/store/node.ts b/src/store/node.ts index bea7852..82667a6 100644 --- a/src/store/node.ts +++ b/src/store/node.ts @@ -1,10 +1,11 @@ import type { Opaque } from "type-fest"; import EventEmitter from "eventemitter3"; import invariant from "tiny-invariant"; -import * as PIXI from "pixi.js"; import nullthrows from "nullthrows"; import type CCStore from "."; import type { CCComponentId } from "./component"; +import type { CCNodePinId } from "./nodePin"; +import type { Point } from "../common/types"; export type CCNodeId = Opaque; @@ -12,9 +13,8 @@ export type CCNode = { readonly id: CCNodeId; readonly parentComponentId: CCComponentId; readonly componentId: CCComponentId; - position: PIXI.Point; - /** The dynamic pin count exclusive to certain intrinsic components */ - intrinsicVariablePinCount: number | null; + position: Point; + variablePins: CCNodePinId[] | null; }; export type CCNodeStoreEvents = { @@ -44,7 +44,7 @@ export class CCNodeStore extends EventEmitter { import(nodes: CCNode[]): void { for (const node of nodes) { - node.position = new PIXI.Point(node.position.x, node.position.y); + node.position = { x: node.position.x, y: node.position.y }; this.register(node); } } @@ -118,7 +118,7 @@ export class CCNodeStore extends EventEmitter { * @param id id of node * @param value new position */ - update(id: CCNodeId, value: Pick): void { + update(id: CCNodeId, value: Pick): void { const node = this.#nodes.get(id); invariant(node); this.#nodes.set(id, { ...node, ...value }); @@ -146,7 +146,18 @@ export class CCNodeStore extends EventEmitter { * Get array of nodes * @returns array of nodes */ - toArray(): CCNode[] { + getMany(): CCNode[] { return [...this.#nodes.values()]; } + + incrementVariablePin(nodeId: CCNodeId, nodePinId: CCNodePinId) { + const node = this.#nodes.get(nodeId)!; + node.variablePins!.push(nodePinId); + } + + decrementVariablePin(nodeId: CCNodeId): CCNodePinId { + const node = this.#nodes.get(nodeId)!; + invariant(node.variablePins!.length > 0); + return node.variablePins!.pop()!; + } } diff --git a/src/store/nodePin.ts b/src/store/nodePin.ts index 24aa585..235239f 100644 --- a/src/store/nodePin.ts +++ b/src/store/nodePin.ts @@ -5,6 +5,7 @@ import invariant from "tiny-invariant"; import type { CCNodeId } from "./node"; import type { CCComponentPinId, CCPinMultiplexability } from "./componentPin"; import type CCStore from "."; +import * as intrinsic from "./intrinsics"; export type CCNodePinId = Opaque; @@ -163,6 +164,17 @@ export class CCNodePinStore extends EventEmitter { const nodePins = this.getManyByNodeId(node.id); const givenPinMultiplexability = this.#store.componentPins.getComponentPinMultiplexability(pinId); + if (givenPinMultiplexability === "undecidable") { + if (pinId === intrinsic.fourBitsIntrinsicComponentOutputPin.id) { + return { isMultiplexable: false, multiplicity: nodePins.length - 1 }; + } + if ( + pinId === intrinsic.distributeFourBitsIntrinsicComponentInputPin.id + ) { + return { isMultiplexable: false, multiplicity: nodePins.length - 1 }; + } + throw new Error("unreachable"); + } if (!givenPinMultiplexability.isMultiplexable) { return givenPinMultiplexability; } @@ -172,6 +184,9 @@ export class CCNodePinStore extends EventEmitter { this.#store.componentPins.getComponentPinMultiplexability( nodePin.componentPinId ); + if (pinMultiplexability === "undecidable") { + throw new Error("unreachable"); + } if (pinMultiplexability.isMultiplexable) { const connections = this.#store.connections.getConnectionsByNodePinId(nodePinId)!; @@ -219,7 +234,25 @@ export class CCNodePinStore extends EventEmitter { * Get array of pins * @returns array of pins */ - toArray(): CCNodePin[] { + getMany(): CCNodePin[] { return [...this.#nodePins.values()]; } + + incrementVariablePin(nodeId: CCNodeId, componentPinId: CCComponentPinId) { + const node = this.#store.nodes.get(nodeId)!; + invariant(node.variablePins); + const nodePin = CCNodePinStore.create({ + nodeId, + componentPinId, + }); + this.register(nodePin); + this.#store.nodes.incrementVariablePin(nodeId, nodePin.id); + } + + decrementVariablePin(nodeId: CCNodeId) { + const node = this.#store.nodes.get(nodeId)!; + invariant(node.variablePins); + const deletedPinId = this.#store.nodes.decrementVariablePin(nodeId); + this.unregister(deletedPinId); + } } diff --git a/src/store/react/error.tsx b/src/store/react/error.tsx new file mode 100644 index 0000000..cbae792 --- /dev/null +++ b/src/store/react/error.tsx @@ -0,0 +1,14 @@ +import { createElement, type ComponentType } from "react"; +import type CCStore from ".."; +import { useStore } from "."; + +export default function ensureStoreItem

>( + check: (props: P, store: CCStore) => unknown, + component: ComponentType

+): ComponentType

{ + return function EnsureStoreItem(props: P) { + const { store } = useStore(); + if (!check(props, store)) return null; + return createElement(component, props); + }; +} diff --git a/src/store/react/index.tsx b/src/store/react/index.tsx index c1277af..bda90aa 100644 --- a/src/store/react/index.tsx +++ b/src/store/react/index.tsx @@ -7,10 +7,69 @@ import { useState, } from "react"; import invariant from "tiny-invariant"; +import nullthrows from "nullthrows"; import CCStore, { type CCStorePropsFromJson } from ".."; +import { CCComponentStore } from "../component"; +import { CCNodeStore } from "../node"; +import { + andIntrinsicComponent, + andIntrinsicComponentOutputPin, + notIntrinsicComponent, + notIntrinsicComponentInputPin, +} from "../intrinsics"; +import { CCConnectionStore } from "../connection"; function useContextValue() { - const [store, setStore] = useState(() => new CCStore()); + const [store, setStore] = useState(() => { + const tempStore = new CCStore(); + + const rootComponent = CCComponentStore.create({ + name: "Root", + }); + tempStore.components.register(rootComponent); + + const sampleNode1 = CCNodeStore.create({ + parentComponentId: rootComponent.id, + componentId: andIntrinsicComponent.id, + position: { x: -100, y: 0 }, + variablePins: null, + }); + tempStore.nodes.register(sampleNode1); + + const sampleNode2 = CCNodeStore.create({ + parentComponentId: rootComponent.id, + componentId: notIntrinsicComponent.id, + position: { x: 100, y: 0 }, + variablePins: null, + }); + tempStore.nodes.register(sampleNode2); + + const fromNodePin = nullthrows( + tempStore.nodePins + .getManyByNodeId(sampleNode1.id) + .find( + (nodePin) => + nodePin.componentPinId === andIntrinsicComponentOutputPin.id + ) + ); + const toNodePin = nullthrows( + tempStore.nodePins + .getManyByNodeId(sampleNode2.id) + .find( + (nodePin) => + nodePin.componentPinId === notIntrinsicComponentInputPin.id + ) + ); + const sampleConnection = CCConnectionStore.create({ + parentComponentId: rootComponent.id, + from: fromNodePin.id, + to: toNodePin.id, + bentPortion: 0.5, + }); + tempStore.connections.register(sampleConnection); + + return tempStore; + }); // For debugging useEffect(() => { diff --git a/src/store/react/selectors.ts b/src/store/react/selectors.ts index b1bd6cf..f597a83 100644 --- a/src/store/react/selectors.ts +++ b/src/store/react/selectors.ts @@ -1,21 +1,166 @@ -import { useCallback, useSyncExternalStore } from "react"; +import { useCallback, useMemo, useSyncExternalStore } from "react"; +import memoizeOne from "memoize-one"; +import nullthrows from "nullthrows"; import { useStore } from "."; +import type { CCComponentId } from "../component"; +import type { CCNode, CCNodeId } from "../node"; +import type { CCComponentPin } from "../componentPin"; +import type { CCNodePin } from "../nodePin"; -export default function useAllComponents() { +export function useComponents() { const { store } = useStore(); - const getSnapshot = useCallback(() => store.components.getAll(), [store]); + const getSnapshot = useMemo( + () => memoizeOne(() => store.components.getMany()), + [store] + ); const subscribe = useCallback( (onStoreChange: () => void) => { - store.components.on("didRegister", onStoreChange); - store.components.on("didUpdate", onStoreChange); - store.components.on("didUnregister", onStoreChange); + const handler = () => { + getSnapshot.clear(); + onStoreChange(); + }; + store.components.on("didRegister", handler); + store.components.on("didUpdate", handler); + store.components.on("didUnregister", handler); return () => { - store.components.off("didRegister", onStoreChange); - store.components.on("didUpdate", onStoreChange); - store.components.off("didUnregister", onStoreChange); + store.components.off("didRegister", handler); + store.components.off("didUpdate", handler); + store.components.off("didUnregister", handler); }; }, - [store] + [getSnapshot, store.components] + ); + return useSyncExternalStore(subscribe, getSnapshot); +} + +export function useNodeIds(parentComponentId: CCComponentId) { + const { store } = useStore(); + const getSnapshot = useMemo( + () => + memoizeOne(() => + store.nodes + .getMany() + .filter((node) => node.parentComponentId === parentComponentId) + .map((node) => node.id) + ), + [store, parentComponentId] + ); + const subscribe = useCallback( + (onStoreChange: () => void) => { + const handler = () => { + getSnapshot.clear(); + onStoreChange(); + }; + store.nodes.on("didRegister", handler); + store.nodes.on("didUnregister", handler); + return () => { + store.nodes.off("didRegister", handler); + store.nodes.off("didUnregister", handler); + }; + }, + [getSnapshot, store.nodes] + ); + return useSyncExternalStore(subscribe, getSnapshot); +} + +export function useConnectionIds(parentComponentId: CCComponentId) { + const { store } = useStore(); + const getSnapshot = useMemo( + () => + memoizeOne(() => + store.connections + .getMany() + .filter( + (connection) => connection.parentComponentId === parentComponentId + ) + .map((connection) => connection.id) + ), + [store, parentComponentId] + ); + const subscribe = useCallback( + (onStoreChange: () => void) => { + const handler = () => { + getSnapshot.clear(); + onStoreChange(); + }; + store.connections.on("didRegister", handler); + store.connections.on("didUnregister", handler); + return () => { + store.connections.off("didRegister", handler); + store.connections.off("didUnregister", handler); + }; + }, + [getSnapshot, store.connections] + ); + return useSyncExternalStore(subscribe, getSnapshot); +} + +export function useNode(nodeId: CCNodeId) { + const { store } = useStore(); + const getSnapshot = useCallback( + () => nullthrows(store.nodes.get(nodeId)), + [store, nodeId] + ); + const subscribe = useCallback( + (onStoreChange: () => void) => { + const handler = (node: CCNode) => { + if (node.id === nodeId) onStoreChange(); + }; + store.nodes.on("didUpdate", handler); + return () => { + store.nodes.off("didUpdate", handler); + }; + }, + [store, nodeId] + ); + return useSyncExternalStore(subscribe, getSnapshot); +} + +export function useComponentPins(componentId: CCComponentId) { + const { store } = useStore(); + const getSnapshot = useMemo( + () => + memoizeOne(() => store.componentPins.getManyByComponentId(componentId)), + [store, componentId] + ); + const subscribe = useCallback( + (onStoreChange: () => void) => { + const handler = (componentPin: CCComponentPin) => { + if (componentPin.componentId === componentId) onStoreChange(); + }; + store.componentPins.on("didRegister", handler); + store.componentPins.on("didUpdate", handler); + store.componentPins.on("didUnregister", handler); + return () => { + store.componentPins.off("didRegister", handler); + store.componentPins.off("didUpdate", handler); + store.componentPins.off("didUnregister", handler); + }; + }, + [store, componentId] + ); + return useSyncExternalStore(subscribe, getSnapshot); +} + +export function useNodePins(nodeId: CCNodeId) { + const { store } = useStore(); + const getSnapshot = useMemo( + () => memoizeOne(() => store.nodePins.getManyByNodeId(nodeId)), + [store, nodeId] + ); + const subscribe = useCallback( + (onStoreChange: () => void) => { + const handler = (nodePin: CCNodePin) => { + if (nodePin.nodeId === nodeId) onStoreChange(); + }; + store.nodePins.on("didRegister", handler); + store.nodePins.on("didUnregister", handler); + return () => { + store.nodePins.off("didRegister", handler); + store.nodePins.off("didUnregister", handler); + }; + }, + [store, nodeId] ); - return [...useSyncExternalStore(subscribe, getSnapshot).values()]; + return useSyncExternalStore(subscribe, getSnapshot); }