diff --git a/package-lock.json b/package-lock.json index fee99f43..d190801c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,13 +61,14 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", - "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/highlight": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -75,9 +76,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.7.tgz", - "integrity": "sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", "dev": true, "license": "MIT", "engines": { @@ -85,22 +86,22 @@ } }, "node_modules/@babel/core": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.7.tgz", - "integrity": "sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==", + "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.25.7", - "@babel/generator": "^7.25.7", - "@babel/helper-compilation-targets": "^7.25.7", - "@babel/helper-module-transforms": "^7.25.7", - "@babel/helpers": "^7.25.7", - "@babel/parser": "^7.25.7", - "@babel/template": "^7.25.7", - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7", + "@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", @@ -139,13 +140,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", - "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.7", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -155,14 +157,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", - "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", + "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.25.7", - "@babel/helper-validator-option": "^7.25.7", + "@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.1" @@ -182,30 +184,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", - "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", - "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", + "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-module-imports": "^7.25.7", - "@babel/helper-simple-access": "^7.25.7", - "@babel/helper-validator-identifier": "^7.25.7", - "@babel/traverse": "^7.25.7" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -215,33 +216,19 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz", - "integrity": "sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==", + "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, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", - "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.7", - "@babel/types": "^7.25.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", - "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", + "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==", "dev": true, "license": "MIT", "engines": { @@ -249,9 +236,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", - "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, "license": "MIT", "engines": { @@ -259,9 +246,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", - "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", + "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": { @@ -269,121 +256,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", - "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", + "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.25.7", - "@babel/types": "^7.25.7" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", - "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/parser": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", - "integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.7" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -392,6 +285,38 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.7.tgz", @@ -437,32 +362,32 @@ } }, "node_modules/@babel/template": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", - "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.7", - "@babel/parser": "^7.25.7", - "@babel/types": "^7.25.7" + "@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.25.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", - "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.7", - "@babel/generator": "^7.25.7", - "@babel/parser": "^7.25.7", - "@babel/template": "^7.25.7", - "@babel/types": "^7.25.7", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -481,15 +406,14 @@ } }, "node_modules/@babel/types": { - "version": "7.25.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", - "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.7", - "@babel/helper-validator-identifier": "^7.25.7", - "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" @@ -1080,6 +1004,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", @@ -2694,10 +2635,23 @@ "postcss": "^8.2.15" } }, + "node_modules/@tanstack/history": { + "version": "1.90.0", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.90.0.tgz", + "integrity": "sha512-riNhDGm+fAwxgZRJ0J/36IZis1UDHsDCNIxfEodbw6BgTWJr0ah+G20V4HT91uBXiCqYFvX3somlfTLhS5yHDA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/query-core": { - "version": "5.59.16", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.16.tgz", - "integrity": "sha512-crHn+G3ltqb5JG0oUv6q+PMz1m1YkjpASrXTU+sYWW9pLk0t2GybUHNRqYPZWhxgjPaVGC4yp92gSFEJgYEsPw==", + "version": "5.62.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.8.tgz", + "integrity": "sha512-4fV31vDsUyvNGrKIOUNPrZztoyL187bThnoQOvAXEVlZbSiuPONpfx53634MKKdvsDir5NyOGm80ShFaoHS/mw==", "license": "MIT", "funding": { "type": "github", @@ -2705,9 +2659,9 @@ } }, "node_modules/@tanstack/query-devtools": { - "version": "5.58.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.58.0.tgz", - "integrity": "sha512-iFdQEFXaYYxqgrv63ots+65FGI+tNp5ZS5PdMU1DWisxk3fez5HG3FyVlbUva+RdYS5hSLbxZ9aw3yEs97GNTw==", + "version": "5.61.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.61.4.tgz", + "integrity": "sha512-21Tw+u8E3IJJj4A/Bct4H0uBaDTEu7zBrR79FeSyY+mS2gx5/m316oDtJiKkILc819VSTYt+sFzODoJNcpPqZQ==", "dev": true, "license": "MIT", "funding": { @@ -2716,12 +2670,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.59.16", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.16.tgz", - "integrity": "sha512-MuyWheG47h6ERd4PKQ6V8gDyBu3ThNG22e1fRVwvq6ap3EqsFhyuxCAwhNP/03m/mLg+DAb0upgbPaX6VB+CkQ==", + "version": "5.62.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.8.tgz", + "integrity": "sha512-8TUstKxF/fysHonZsWg/hnlDVgasTdHx6Q+f1/s/oPKJBJbKUWPZEHwLTMOZgrZuroLMiqYKJ9w69Abm8mWP0Q==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.59.16" + "@tanstack/query-core": "5.62.8" }, "funding": { "type": "github", @@ -2732,50 +2686,215 @@ } }, "node_modules/@tanstack/react-query-devtools": { - "version": "5.59.6", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.59.6.tgz", - "integrity": "sha512-N6C2hCGF9aAKx+ESWjKmf5/ZkGttPCwVtCfBK9mxGOOo3iTC96jKzrZrqxk1BThXyTqJ6au8biBO4q9h2P4M2Q==", + "version": "5.62.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.62.8.tgz", + "integrity": "sha512-SwjXjQTRONd9WPeKVQQ9framG7YNqPV8PS+EGNVNXAyz2XThulMRCvZnh2+3DggnjcYM7YcpnuoZ4RH7q13p0g==", "dev": true, "license": "MIT", "dependencies": { - "@tanstack/query-devtools": "5.58.0" + "@tanstack/query-devtools": "5.61.4" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.59.6", + "@tanstack/react-query": "^5.62.8", "react": "^18 || ^19" } }, - "node_modules/@tanstack/react-virtual": { - "version": "3.10.8", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.8.tgz", - "integrity": "sha512-VbzbVGSsZlQktyLrP5nxE+vE1ZR+U0NFAWPbJLoG2+DKPwd2D7dVICTVIIaYlJqX1ZCEnYDbaOpmMwbsyhBoIA==", + "node_modules/@tanstack/react-router": { + "version": "1.91.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.91.3.tgz", + "integrity": "sha512-T6k50ApwcWKYjJB4VSK2WhXu/p40luynNJg5QC3oIqk24p0tLlgXIblXoTJzy7lVvDmQ4lwHCP9dBTvLy5NhVA==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.10.8" + "@tanstack/history": "1.90.0", + "@tanstack/react-store": "^0.6.1", + "jsesc": "^3.0.2", + "tiny-invariant": "^1.3.3", + "tiny-warning": "^1.0.3" + }, + "engines": { + "node": ">=12" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@tanstack/router-generator": "^1.87.7", + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "@tanstack/router-generator": { + "optional": true + } + } + }, + "node_modules/@tanstack/react-store": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.6.1.tgz", + "integrity": "sha512-6gOopOpPp1cAXkEyTEv6tMbAywwFunvIdCKN/SpEiButUayjXU+Q5Sp5Y3hREN3VMR4OA5+RI5SPhhJoqP9e4w==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.6.0", + "use-sync-external-store": "^1.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.11.2.tgz", + "integrity": "sha512-OuFzMXPF4+xZgx8UzJha0AieuMihhhaWG0tCqpp6tDzlFwOmNBPYMuLOtMJ1Tr4pXLHmgjcWhG6RlknY2oNTdQ==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.11.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/router-devtools": { + "version": "1.91.3", + "resolved": "https://registry.npmjs.org/@tanstack/router-devtools/-/router-devtools-1.91.3.tgz", + "integrity": "sha512-b/WOhWEC7+Znh0+OrSGxl7RMCwHT5vX6fygDN1wh1yiUOjs32EJquu4c9deWzRDlF3jV6ji7XYWs1XgKXcsrww==", + "dev": true, + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-router": "^1.91.3", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@tanstack/router-generator": { + "version": "1.87.7", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.87.7.tgz", + "integrity": "sha512-w9Px1C6DM0YNVXvu1VjUuZ5el0ykOeofEmEZBW83VUTzvCXFpcjPCHncU9FO9uXup8NFIxNfGz+xpwf93GoFnQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@tanstack/virtual-file-routes": "^1.87.6", + "prettier": "^3.4.2", + "tsx": "^4.19.2", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/router-plugin": { + "version": "1.91.1", + "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.91.1.tgz", + "integrity": "sha512-+htKBNRKwdZjpgT0ee32oBb7gpH3o0cJUKvx74oTfZ9N5oth255pns1ka4Sa6lhC/gyvC3NLgk/lMqD7eVJejA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.26.4", + "@babel/types": "^7.26.3", + "@tanstack/router-generator": "^1.87.7", + "@tanstack/virtual-file-routes": "^1.87.6", + "@types/babel__core": "^7.20.5", + "@types/babel__generator": "^7.6.8", + "@types/babel__template": "^7.4.4", + "@types/babel__traverse": "^7.20.6", + "babel-dead-code-elimination": "^1.0.8", + "chokidar": "^3.6.0", + "unplugin": "^1.16.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@rsbuild/core": ">=1.0.2", + "vite": ">=5.0.0 || >=6.0.0", + "webpack": ">=5.92.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@tanstack/store": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.6.0.tgz", + "integrity": "sha512-+m2OBglsjXcLmmKOX6/9v8BDOCtyxhMmZLsRUDswOOSdIIR9mvv6i0XNKsmTh3AlYU8c1mRcodC8/Vyf+69VlQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/virtual-core": { - "version": "3.10.8", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.8.tgz", - "integrity": "sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA==", + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.2.tgz", + "integrity": "sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/virtual-file-routes": { + "version": "1.87.6", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.87.6.tgz", + "integrity": "sha512-PTpeM8SHL7AJM0pJOacFvHribbUODS51qe9NsMqku4mogh6BWObY1EeVmeGnp9o3VngAEsf+rJMs2zqIVz3WFA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tauri-apps/api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.2.tgz", @@ -3556,9 +3675,9 @@ "license": "MIT" }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", "bin": { @@ -4022,6 +4141,19 @@ "node": ">= 0.4" } }, + "node_modules/babel-dead-code-elimination": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.8.tgz", + "integrity": "sha512-og6HQERk0Cmm+nTT4Od2wbPtgABXFMPaHACjbKLulZIFMkYyXZLkUGuAxdgpMJBrxyt/XFpSz++lNzjbcMnPkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + } + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -7277,6 +7409,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -7420,6 +7565,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -7648,6 +7803,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/history": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", + "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.7.6" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -8634,7 +8798,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -11481,10 +11644,10 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", - "dev": true, + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "devOptional": true, "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -12488,6 +12651,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/responselike": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", @@ -13924,6 +14097,12 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, "node_modules/to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", @@ -13931,16 +14110,6 @@ "dev": true, "license": "MIT" }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14081,6 +14250,457 @@ "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", + "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -14366,6 +14986,20 @@ "node": ">= 4.0.0" } }, + "node_modules/unplugin": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.0.tgz", + "integrity": "sha512-5liCNPuJW8dqh3+DM6uNM2EI3MLLpCKp/KY+9pB5M2S2SR2qvvDHhKgBOaTWEbZTAws3CXfB0rKTIolWKL05VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/unzip-response": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", @@ -14530,6 +15164,15 @@ "node": ">=0.10.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", @@ -14774,6 +15417,13 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, "node_modules/whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", @@ -15236,6 +15886,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -15307,8 +15967,9 @@ "@lezer/lr": "^1.3.3", "@react-hook/size": "^2.1.2", "@tailwindcss/container-queries": "^0.1.1", - "@tanstack/react-query": "^5.59.16", - "@tanstack/react-virtual": "^3.10.8", + "@tanstack/react-query": "^5.62.8", + "@tanstack/react-router": "^1.91.3", + "@tanstack/react-virtual": "^3.11.2", "@tauri-apps/api": "^2.0.1", "@tauri-apps/plugin-clipboard-manager": "^2.0.0", "@tauri-apps/plugin-dialog": "^2.0.0", @@ -15327,6 +15988,7 @@ "format-graphql": "^1.5.0", "framer-motion": "^11.5.4", "fuzzbunny": "^1.0.1", + "history": "^5.3.0", "jotai": "^2.9.3", "lucide-react": "^0.439.0", "mime": "^4.0.4", @@ -15350,7 +16012,9 @@ "devDependencies": { "@lezer/generator": "^1.7.1", "@tailwindcss/nesting": "^0.0.0-insiders.565cd3e", - "@tanstack/react-query-devtools": "^5.55.4", + "@tanstack/react-query-devtools": "^5.62.8", + "@tanstack/router-devtools": "^1.91.3", + "@tanstack/router-plugin": "^1.91.1", "@types/node": "^22.5.4", "@types/papaparse": "^5.3.14", "@types/parse-color": "^1.0.3", diff --git a/src-web/components/App.tsx b/src-web/components/App.tsx deleted file mode 100644 index 154029b6..00000000 --- a/src-web/components/App.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import { MotionConfig } from 'framer-motion'; -import React, { Suspense } from 'react'; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import { HelmetProvider } from 'react-helmet-async'; -import { AppRouter } from './AppRouter'; - -const queryClient = new QueryClient({ - queryCache: new QueryCache({ - onError: (err, query) => { - console.log('Query client error', { err, query }); - }, - }), - defaultOptions: { - queries: { - retry: false, - networkMode: 'always', - refetchOnWindowFocus: true, - refetchOnReconnect: false, - refetchOnMount: false, // Don't refetch when a hook mounts - }, - }, -}); - -const ENABLE_REACT_QUERY_DEVTOOLS = false; - -export function App() { - return ( - - {ENABLE_REACT_QUERY_DEVTOOLS && } - - - - - - - - - - - ); -} diff --git a/src-web/components/AppRouter.tsx b/src-web/components/AppRouter.tsx deleted file mode 100644 index 12963a2d..00000000 --- a/src-web/components/AppRouter.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { lazy } from 'react'; -import { createBrowserRouter, Navigate, RouterProvider, useParams } from 'react-router-dom'; -import { paths, useAppRoutes } from '../hooks/useAppRoutes'; -import { DefaultLayout } from './DefaultLayout'; -import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace'; -import RouteError from './RouteError'; - -const LazyWorkspace = lazy(() => import('./Workspace')); -const LazySettings = lazy(() => import('./Settings/Settings')); - -const router = createBrowserRouter([ - { - path: '/', - errorElement: , - element: , - children: [ - { - path: '/', - element: , - }, - { - path: paths.workspaces(), - element: , - }, - { - path: paths.workspace({ - workspaceId: ':workspaceId', - environmentId: null, - cookieJarId: null, - }), - element: , - }, - { - path: paths.request({ - workspaceId: ':workspaceId', - requestId: ':requestId', - environmentId: null, - cookieJarId: null, - }), - element: , - }, - { - path: '/workspaces/:workspaceId/environments/:environmentId/requests/:requestId', - element: , - }, - { - path: paths - .workspaceSettings({ - workspaceId: ':workspaceId', - }) - .replace(/\?.*/, ''), - element: , - }, - ], - }, -]); - -export function AppRouter() { - return ; -} - -function RedirectLegacyEnvironmentURLs() { - const routes = useAppRoutes(); - const { - requestId, - environmentId: rawEnvironmentId, - workspaceId, - } = useParams<{ - requestId?: string; - workspaceId?: string; - environmentId?: string; - }>(); - const environmentId = (rawEnvironmentId === '__default__' ? undefined : rawEnvironmentId) ?? null; - - let to; - if (workspaceId != null && requestId != null) { - to = routes.paths.request({ workspaceId, environmentId, requestId, cookieJarId: null }); - } else if (workspaceId != null) { - to = routes.paths.workspace({ workspaceId, environmentId, cookieJarId: null }); - } else { - to = routes.paths.workspaces(); - } - - return ; -} diff --git a/src-web/components/CommandPalette.tsx b/src-web/components/CommandPalette.tsx index 3079c769..336524cf 100644 --- a/src-web/components/CommandPalette.tsx +++ b/src-web/components/CommandPalette.tsx @@ -1,12 +1,10 @@ -import type { KeyboardEvent, ReactNode } from 'react'; -import type { HotkeyAction } from '../hooks/useHotKey'; import classNames from 'classnames'; import { fuzzyFilter } from 'fuzzbunny'; +import type { KeyboardEvent, ReactNode } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useActiveCookieJar } from '../hooks/useActiveCookieJar'; import { useActiveEnvironment } from '../hooks/useActiveEnvironment'; import { useActiveRequest } from '../hooks/useActiveRequest'; -import { useAppRoutes } from '../hooks/useAppRoutes'; import { useCreateEnvironment } from '../hooks/useCreateEnvironment'; import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest'; import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest'; @@ -14,6 +12,7 @@ import { useCreateWorkspace } from '../hooks/useCreateWorkspace'; import { useDebouncedState } from '../hooks/useDebouncedState'; import { useDeleteRequest } from '../hooks/useDeleteRequest'; import { useEnvironments } from '../hooks/useEnvironments'; +import type { HotkeyAction } from '../hooks/useHotKey'; import { useHotKey } from '../hooks/useHotKey'; import { useHttpRequestActions } from '../hooks/useHttpRequestActions'; import { useOpenSettings } from '../hooks/useOpenSettings'; @@ -28,6 +27,8 @@ import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest'; import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useWorkspaces } from '../hooks/useWorkspaces'; import { fallbackRequestName } from '../lib/fallbackRequestName'; +import { router } from '../main'; +import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId'; import { CookieDialog } from './CookieDialog'; import { Button } from './core/Button'; import { Heading } from './core/Heading'; @@ -58,7 +59,6 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { const [selectedItemKey, setSelectedItemKey] = useState(null); const [activeEnvironment, setActiveEnvironmentId] = useActiveEnvironment(); const httpRequestActions = useHttpRequestActions(); - const routes = useAppRoutes(); const workspaces = useWorkspaces(); const environments = useEnvironments(); const recentEnvironments = useRecentEnvironments(); @@ -268,11 +268,13 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { ), onSelect: () => { - return routes.navigate('request', { - workspaceId: r.workspaceId, - requestId: r.id, - environmentId: activeEnvironment?.id ?? null, - cookieJarId: activeCookieJar?.id ?? null, + router.navigate({ + to: Route.fullPath, + params: { + workspaceId: r.workspaceId, + requestId: r.id, + }, + search: (prev) => ({ ...prev }), }); }, }); @@ -313,9 +315,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { }, [ workspaceCommands, sortedRequests, - routes, activeEnvironment?.id, - activeCookieJar?.id, sortedEnvironments, setActiveEnvironmentId, sortedWorkspaces, diff --git a/src-web/components/DefaultLayout.tsx b/src-web/components/DefaultLayout.tsx deleted file mode 100644 index e14d6797..00000000 --- a/src-web/components/DefaultLayout.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import classNames from 'classnames'; -import { Outlet } from 'react-router-dom'; -import { useOsInfo } from '../hooks/useOsInfo'; -import { DialogProvider, Dialogs } from './DialogContext'; -import { ToastProvider, Toasts } from './ToastContext'; -import { GlobalHooks } from './GlobalHooks'; - -export function DefaultLayout() { - const osInfo = useOsInfo(); - return ( - - - <> - {/* Must be inside all the providers, so they have access to them */} - - - > - - - - - - - ); -} diff --git a/src-web/components/GlobalHooks.tsx b/src-web/components/GlobalHooks.tsx index 73a535db..8fbd29f7 100644 --- a/src-web/components/GlobalHooks.tsx +++ b/src-web/components/GlobalHooks.tsx @@ -1,8 +1,13 @@ import { emit } from '@tauri-apps/api/event'; import type { PromptTextRequest, PromptTextResponse } from '@yaakapp-internal/plugin'; import { useEnsureActiveCookieJar } from '../hooks/useActiveCookieJar'; +import { useActiveRequest } from '../hooks/useActiveRequest'; +import {useSubscribeActiveRequestId} from "../hooks/useActiveRequestId"; +import {useSubscribeActiveWorkspaceId} from "../hooks/useActiveWorkspace"; import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast'; -import {useGenerateThemeCss} from "../hooks/useGenerateThemeCss"; +import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest'; +import { useDuplicateHttpRequest } from '../hooks/useDuplicateHttpRequest'; +import { useGenerateThemeCss } from '../hooks/useGenerateThemeCss'; import { useHotKey } from '../hooks/useHotKey'; import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent'; import { useNotificationToast } from '../hooks/useNotificationToast'; @@ -11,10 +16,11 @@ import { useRecentCookieJars } from '../hooks/useRecentCookieJars'; import { useRecentEnvironments } from '../hooks/useRecentEnvironments'; import { useRecentRequests } from '../hooks/useRecentRequests'; import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces'; -import {useSyncFontSizeSetting} from "../hooks/useSyncFontSizeSetting"; -import {useSyncModelStores} from "../hooks/useSyncModelStores"; +import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting'; +import { useSyncModelStores } from '../hooks/useSyncModelStores'; import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels'; -import {useSyncZoomSetting} from "../hooks/useSyncZoomSetting"; +import {useSyncWorkspaceRequestTitle} from "../hooks/useSyncWorkspaceRequestTitle"; +import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting'; import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette'; export function GlobalHooks() { @@ -22,6 +28,9 @@ export function GlobalHooks() { useSyncZoomSetting(); useSyncFontSizeSetting(); useGenerateThemeCss(); + useSyncWorkspaceRequestTitle(); + useSubscribeActiveWorkspaceId(); + useSubscribeActiveRequestId(); // Include here so they always update, even if no component references them useRecentWorkspaces(); @@ -35,6 +44,23 @@ export function GlobalHooks() { useActiveWorkspaceChangedToast(); useEnsureActiveCookieJar(); + const activeRequest = useActiveRequest(); + const duplicateHttpRequest = useDuplicateHttpRequest({ + id: activeRequest?.id ?? null, + navigateAfter: true, + }); + const duplicateGrpcRequest = useDuplicateGrpcRequest({ + id: activeRequest?.id ?? null, + navigateAfter: true, + }); + useHotKey('http_request.duplicate', async () => { + if (activeRequest?.model === 'http_request') { + await duplicateHttpRequest.mutateAsync(); + } else { + await duplicateGrpcRequest.mutateAsync(); + } + }); + const toggleCommandPalette = useToggleCommandPalette(); useHotKey('command_palette.toggle', toggleCommandPalette); diff --git a/src-web/components/MoveToWorkspaceDialog.tsx b/src-web/components/MoveToWorkspaceDialog.tsx index 72696869..d36a3599 100644 --- a/src-web/components/MoveToWorkspaceDialog.tsx +++ b/src-web/components/MoveToWorkspaceDialog.tsx @@ -1,10 +1,11 @@ import type { GrpcRequest, HttpRequest } from '@yaakapp-internal/models'; import React, { useState } from 'react'; -import { useAppRoutes } from '../hooks/useAppRoutes'; import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest'; import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest'; import { useWorkspaces } from '../hooks/useWorkspaces'; import { fallbackRequestName } from '../lib/fallbackRequestName'; +import { router } from '../main'; +import { Route } from '../routes/workspaces/$workspaceId/index'; import { Button } from './core/Button'; import { InlineCode } from './core/InlineCode'; import { Select } from './core/Select'; @@ -22,7 +23,6 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr const updateHttpRequest = useUpdateAnyHttpRequest(); const updateGrpcRequest = useUpdateAnyGrpcRequest(); const toast = useToast(); - const routes = useAppRoutes(); const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(activeWorkspaceId); return ( @@ -71,10 +71,9 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr className="mr-auto min-w-[5rem]" onClick={() => { toast.hide('workspace-moved'); - routes.navigate('workspace', { - workspaceId: selectedWorkspaceId, - cookieJarId: null, - environmentId: null, + router.navigate({ + to: Route.fullPath, + params: { workspaceId: selectedWorkspaceId }, }); }} > diff --git a/src-web/components/RecentRequestsDropdown.tsx b/src-web/components/RecentRequestsDropdown.tsx index 8250751c..85f14248 100644 --- a/src-web/components/RecentRequestsDropdown.tsx +++ b/src-web/components/RecentRequestsDropdown.tsx @@ -1,15 +1,14 @@ import classNames from 'classnames'; import { useMemo, useRef } from 'react'; import { useKeyPressEvent } from 'react-use'; -import { useActiveCookieJar } from '../hooks/useActiveCookieJar'; -import { useActiveEnvironment } from '../hooks/useActiveEnvironment'; import { useActiveRequest } from '../hooks/useActiveRequest'; import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; -import { useAppRoutes } from '../hooks/useAppRoutes'; import { useHotKey } from '../hooks/useHotKey'; import { useRecentRequests } from '../hooks/useRecentRequests'; import { useRequests } from '../hooks/useRequests'; import { fallbackRequestName } from '../lib/fallbackRequestName'; +import { router } from '../main'; +import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId'; import type { ButtonProps } from './core/Button'; import { Button } from './core/Button'; import type { DropdownItem, DropdownRef } from './core/Dropdown'; @@ -20,9 +19,6 @@ export function RecentRequestsDropdown({ className }: Pick(null); const activeRequest = useActiveRequest(); const activeWorkspace = useActiveWorkspace(); - const [activeEnvironment] = useActiveEnvironment(); - const [activeCookieJar] = useActiveCookieJar(); - const routes = useAppRoutes(); const allRecentRequestIds = useRecentRequests(); const recentRequestIds = useMemo(() => allRecentRequestIds.slice(1), [allRecentRequestIds]); const requests = useRequests(); @@ -57,11 +53,13 @@ export function RecentRequestsDropdown({ className }: Pick, leftSlot: , onSelect: () => { - routes.navigate('request', { - requestId: request.id, - workspaceId: activeWorkspace.id, - environmentId: activeEnvironment?.id ?? null, - cookieJarId: activeCookieJar?.id ?? null, + router.navigate({ + to: Route.fullPath, + params: { + requestId: request.id, + workspaceId: activeWorkspace.id, + }, + search: (prev) => ({ ...prev }), }); }, }); @@ -79,7 +77,7 @@ export function RecentRequestsDropdown({ className }: Pick diff --git a/src-web/components/RedirectToLatestWorkspace.tsx b/src-web/components/RedirectToLatestWorkspace.tsx index 5c12ced7..1f5a8f1d 100644 --- a/src-web/components/RedirectToLatestWorkspace.tsx +++ b/src-web/components/RedirectToLatestWorkspace.tsx @@ -1,15 +1,14 @@ import { useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useAppRoutes } from '../hooks/useAppRoutes'; import { getRecentCookieJars } from '../hooks/useRecentCookieJars'; import { getRecentEnvironments } from '../hooks/useRecentEnvironments'; import { getRecentRequests } from '../hooks/useRecentRequests'; import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces'; import { useWorkspaces } from '../hooks/useWorkspaces'; +import { router } from '../main'; +import { Route as WorkspaceRoute } from '../routes/workspaces/$workspaceId'; +import { Route as RequestRoute } from '../routes/workspaces/$workspaceId/requests/$requestId'; export function RedirectToLatestWorkspace() { - const navigate = useNavigate(); - const routes = useAppRoutes(); const workspaces = useWorkspaces(); const recentWorkspaces = useRecentWorkspaces(); @@ -26,12 +25,20 @@ export function RedirectToLatestWorkspace() { const requestId = (await getRecentRequests(workspaceId))[0] ?? null; if (workspaceId != null && requestId != null) { - navigate(routes.paths.request({ workspaceId, environmentId, requestId, cookieJarId })); + await router.navigate({ + to: RequestRoute.fullPath, + params: { workspaceId, requestId }, + search: { cookieJarId, environmentId }, + }); } else { - navigate(routes.paths.workspace({ workspaceId, environmentId, cookieJarId })); + await router.navigate({ + to: WorkspaceRoute.fullPath, + params: { workspaceId }, + search: { cookieJarId, environmentId }, + }); } })(); - }, [navigate, recentWorkspaces, routes.paths, workspaces, workspaces.length]); + }, [recentWorkspaces, workspaces, workspaces.length]); return <>>; } diff --git a/src-web/components/RequestContextMenu.tsx b/src-web/components/RequestContextMenu.tsx new file mode 100644 index 00000000..0cfd3348 --- /dev/null +++ b/src-web/components/RequestContextMenu.tsx @@ -0,0 +1,162 @@ +import React, { useMemo } from 'react'; +import { useCreateDropdownItems } from '../hooks/useCreateDropdownItems'; +import { useDeleteFolder } from '../hooks/useDeleteFolder'; +import { useDeleteRequest } from '../hooks/useDeleteRequest'; +import { useDuplicateFolder } from '../hooks/useDuplicateFolder'; +import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest'; +import { useDuplicateHttpRequest } from '../hooks/useDuplicateHttpRequest'; +import { useHttpRequestActions } from '../hooks/useHttpRequestActions'; +import { useMoveToWorkspace } from '../hooks/useMoveToWorkspace'; +import { useRenameRequest } from '../hooks/useRenameRequest'; +import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest'; +import { useSendManyRequests } from '../hooks/useSendManyRequests'; +import { useWorkspaces } from '../hooks/useWorkspaces'; +import { getHttpRequest } from '../lib/store'; +import type { DropdownItem } from './core/Dropdown'; +import { ContextMenu } from './core/Dropdown'; +import { Icon } from './core/Icon'; +import { useDialog } from './DialogContext'; +import { FolderSettingsDialog } from './FolderSettingsDialog'; +import type { SidebarTreeNode } from './Sidebar'; + +interface Props { + child: SidebarTreeNode; + show: { x: number; y: number } | null; + close: () => void; +} + +export function RequestContextMenu({ child, show, close }: Props) { + const sendManyRequests = useSendManyRequests(); + const duplicateFolder = useDuplicateFolder(child.item.id); + const deleteFolder = useDeleteFolder(child.item.id); + const httpRequestActions = useHttpRequestActions(); + const sendRequest = useSendAnyHttpRequest(); + const workspaces = useWorkspaces(); + const dialog = useDialog(); + const deleteRequest = useDeleteRequest(child.item.id); + const renameRequest = useRenameRequest(child.item.id); + const duplicateHttpRequest = useDuplicateHttpRequest({ id: child.item.id, navigateAfter: true }); + const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: child.item.id, navigateAfter: true }); + const moveToWorkspace = useMoveToWorkspace(child.item.id); + const createDropdownItems = useCreateDropdownItems({ + folderId: child.item.model === 'folder' ? child.item.id : null, + }); + + const items = useMemo(() => { + if (child.item.model === 'folder') { + return [ + { + key: 'send-all', + label: 'Send All', + leftSlot: , + onSelect: () => sendManyRequests.mutate(child.children.map((c) => c.item.id)), + }, + { + key: 'folder-settings', + label: 'Settings', + leftSlot: , + onSelect: () => + dialog.show({ + id: 'folder-settings', + title: 'Folder Settings', + size: 'md', + render: () => , + }), + }, + { + key: 'duplicateFolder', + label: 'Duplicate', + leftSlot: , + onSelect: () => duplicateFolder.mutate(), + }, + { + key: 'delete-folder', + label: 'Delete', + variant: 'danger', + leftSlot: , + onSelect: () => deleteFolder.mutate(), + }, + { type: 'separator' }, + ...createDropdownItems, + ]; + } else { + const requestItems: DropdownItem[] = + child.item.model === 'http_request' + ? [ + { + key: 'send-request', + label: 'Send', + hotKeyAction: 'http_request.send', + hotKeyLabelOnly: true, // Already bound in URL bar + leftSlot: , + onSelect: () => sendRequest.mutate(child.item.id), + }, + ...httpRequestActions.map((a) => ({ + key: a.key, + label: a.label, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + leftSlot: , + onSelect: async () => { + const request = await getHttpRequest(child.item.id); + if (request != null) await a.call(request); + }, + })), + { type: 'separator' }, + ] + : []; + return [ + ...requestItems, + { + key: 'rename-request', + label: 'Rename', + leftSlot: , + onSelect: renameRequest.mutate, + }, + { + key: 'duplicate-request', + label: 'Duplicate', + hotKeyAction: 'http_request.duplicate', + hotKeyLabelOnly: true, // Would trigger for every request (bad) + leftSlot: , + onSelect: () => + child.item.model === 'http_request' + ? duplicateHttpRequest.mutate() + : duplicateGrpcRequest.mutate(), + }, + { + key: 'move-workspace', + label: 'Move', + leftSlot: , + hidden: workspaces.length <= 1, + onSelect: moveToWorkspace.mutate, + }, + { + key: 'delete-request', + variant: 'danger', + label: 'Delete', + leftSlot: , + onSelect: () => deleteRequest.mutate(), + }, + ]; + } + }, [ + child.children, + child.item.id, + child.item.model, + createDropdownItems, + deleteFolder, + deleteRequest, + dialog, + duplicateFolder, + duplicateGrpcRequest, + duplicateHttpRequest, + httpRequestActions, + moveToWorkspace.mutate, + renameRequest.mutate, + sendManyRequests, + sendRequest, + workspaces.length, + ]); + + return ; +} diff --git a/src-web/components/RouteError.tsx b/src-web/components/RouteError.tsx index 432478f5..7c034d04 100644 --- a/src-web/components/RouteError.tsx +++ b/src-web/components/RouteError.tsx @@ -1,5 +1,6 @@ import { useRouteError } from 'react-router-dom'; -import { useAppRoutes } from '../hooks/useAppRoutes'; +import { router } from '../main'; +import { Route } from '../routes/workspaces'; import { Button } from './core/Button'; import { FormattedError } from './core/FormattedError'; import { Heading } from './core/Heading'; @@ -11,7 +12,6 @@ export default function RouteError() { const stringified = JSON.stringify(error); // eslint-disable-next-line @typescript-eslint/no-explicit-any const message = (error as any).message ?? stringified; - const routes = useAppRoutes(); return ( @@ -21,7 +21,7 @@ export default function RouteError() { { - routes.navigate('workspaces'); + router.navigate({ to: Route.fullPath }); }} > Go Home diff --git a/src-web/components/Settings/Settings.tsx b/src-web/components/Settings/Settings.tsx index a7594c50..242ea28e 100644 --- a/src-web/components/Settings/Settings.tsx +++ b/src-web/components/Settings/Settings.tsx @@ -1,7 +1,6 @@ import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; import classNames from 'classnames'; import React, { useState } from 'react'; -import { useSearchParams } from 'react-router-dom'; import { useKeyPressEvent } from 'react-use'; import { useOsInfo } from '../../hooks/useOsInfo'; import { capitalize } from '../../lib/capitalize'; @@ -16,6 +15,7 @@ import { SettingsProxy } from './SettingsProxy'; interface Props { hide?: () => void; + defaultTab?: SettingsTab; } export enum SettingsTab { @@ -34,10 +34,9 @@ const tabs = [ SettingsTab.License, ]; -export default function Settings({ hide }: Props) { +export default function Settings({ hide, defaultTab }: Props) { const osInfo = useOsInfo(); - const [params] = useSearchParams(); - const [tab, setTab] = useState(params.get('tab') ?? SettingsTab.General); + const [tab, setTab] = useState(defaultTab ?? SettingsTab.General); // Close settings window on escape // TODO: Could this be put in a better place? Eg. in Rust key listener when creating the window diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index 0d92dbcb..a281fd6a 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -1,162 +1,105 @@ -import type { - AnyModel, - Folder, - GrpcConnection, - GrpcRequest, - HttpRequest, - HttpResponse, - Workspace, -} from '@yaakapp-internal/models'; +import type { Folder, GrpcRequest, HttpRequest, Workspace } from '@yaakapp-internal/models'; import classNames from 'classnames'; -import type { ReactNode } from 'react'; -import React, { Fragment, useCallback, useMemo, useRef, useState } from 'react'; -import type { XYCoord } from 'react-dnd'; -import { useDrag, useDrop } from 'react-dnd'; +import { atom, useAtom } from 'jotai'; +import React, { memo, useCallback, useMemo, useRef, useState } from 'react'; import { useKey, useKeyPressEvent } from 'react-use'; -import { useActiveCookieJar } from '../hooks/useActiveCookieJar'; -import { useActiveEnvironment } from '../hooks/useActiveEnvironment'; - -import { useActiveRequest } from '../hooks/useActiveRequest'; +import { getActiveRequest } from '../hooks/useActiveRequest'; import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; -import { useAppRoutes } from '../hooks/useAppRoutes'; import { useCreateDropdownItems } from '../hooks/useCreateDropdownItems'; -import { useDeleteFolder } from '../hooks/useDeleteFolder'; -import { useDeleteRequest } from '../hooks/useDeleteRequest'; -import { useDuplicateFolder } from '../hooks/useDuplicateFolder'; -import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest'; -import { useDuplicateHttpRequest } from '../hooks/useDuplicateHttpRequest'; import { useFolders } from '../hooks/useFolders'; import { useGrpcConnections } from '../hooks/useGrpcConnections'; import { useHotKey } from '../hooks/useHotKey'; -import type { CallableHttpRequestAction } from '../hooks/useHttpRequestActions'; -import { useHttpRequestActions } from '../hooks/useHttpRequestActions'; import { useHttpResponses } from '../hooks/useHttpResponses'; import { useKeyValue } from '../hooks/useKeyValue'; -import { useMoveToWorkspace } from '../hooks/useMoveToWorkspace'; -import { useRenameRequest } from '../hooks/useRenameRequest'; import { useRequests } from '../hooks/useRequests'; -import { useScrollIntoView } from '../hooks/useScrollIntoView'; -import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest'; -import { useSendManyRequests } from '../hooks/useSendManyRequests'; import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder'; import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest'; import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest'; -import { useWorkspaces } from '../hooks/useWorkspaces'; -import { fallbackRequestName } from '../lib/fallbackRequestName'; -import { isResponseLoading } from '../lib/model_util'; -import { getHttpRequest } from '../lib/store'; -import type { DropdownItem } from './core/Dropdown'; +import { router } from '../main'; +import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId'; import { ContextMenu } from './core/Dropdown'; -import { HttpMethodTag } from './core/HttpMethodTag'; -import { Icon } from './core/Icon'; -import { VStack } from './core/Stacks'; -import { StatusTag } from './core/StatusTag'; -import { useDialog } from './DialogContext'; -import { DropMarker } from './DropMarker'; -import { FolderSettingsDialog } from './FolderSettingsDialog'; +import type { SidebarItemProps } from './SidebarItem'; +import { SidebarItems } from './SidebarItems'; interface Props { className?: string; } -enum ItemTypes { - REQUEST = 'request', -} - -interface TreeNode { +export interface SidebarTreeNode { item: Workspace | Folder | HttpRequest | GrpcRequest; - children: TreeNode[]; + children: SidebarTreeNode[]; depth: number; } -export function Sidebar({ className }: Props) { +// This is an atom so we can use it in the child items to avoid re-rendering the entire list +export const sidebarSelectedIdAtom = atom(null); + +export const Sidebar = memo(function Sidebar({ className }: Props) { const [hidden, setHidden] = useSidebarHidden(); const sidebarRef = useRef(null); - const activeRequest = useActiveRequest(); - const [activeEnvironment] = useActiveEnvironment(); - const [activeCookieJar] = useActiveCookieJar(); const folders = useFolders(); const requests = useRequests(); const activeWorkspace = useActiveWorkspace(); - const httpRequestActions = useHttpRequestActions(); const httpResponses = useHttpResponses(); const grpcConnections = useGrpcConnections(); - const duplicateHttpRequest = useDuplicateHttpRequest({ - id: activeRequest?.id ?? null, - navigateAfter: true, - }); - const duplicateGrpcRequest = useDuplicateGrpcRequest({ - id: activeRequest?.id ?? null, - navigateAfter: true, - }); - const routes = useAppRoutes(); const [hasFocus, setHasFocus] = useState(false); - const [selectedId, setSelectedId] = useState(null); - const [selectedTree, setSelectedTree] = useState(null); - const updateAnyHttpRequest = useUpdateAnyHttpRequest(); - const updateAnyGrpcRequest = useUpdateAnyGrpcRequest(); - const updateAnyFolder = useUpdateAnyFolder(); + const [selectedId, setSelectedId] = useAtom(sidebarSelectedIdAtom); + const [selectedTree, setSelectedTree] = useState(null); + const { mutateAsync: updateAnyHttpRequest } = useUpdateAnyHttpRequest(); + const { mutateAsync: updateAnyGrpcRequest } = useUpdateAnyGrpcRequest(); + const { mutateAsync: updateAnyFolder } = useUpdateAnyFolder(); const [draggingId, setDraggingId] = useState(null); - const [hoveredTree, setHoveredTree] = useState(null); + const [hoveredTree, setHoveredTree] = useState(null); const [hoveredIndex, setHoveredIndex] = useState(null); - const collapsed = useKeyValue>({ + const { value: collapsed, set: setCollapsed } = useKeyValue>({ key: ['sidebar_collapsed', activeWorkspace?.id ?? 'n/a'], fallback: {}, namespace: 'no_sync', }); - useHotKey('http_request.duplicate', async () => { - if (activeRequest?.model === 'http_request') { - await duplicateHttpRequest.mutateAsync(); - } else { - await duplicateGrpcRequest.mutateAsync(); - } - }); - - const isCollapsed = useCallback( - (id: string) => collapsed.value?.[id] ?? false, - [collapsed.value], - ); + const isCollapsed = useCallback((id: string) => collapsed?.[id] ?? false, [collapsed]); const { tree, treeParentMap, selectableRequests } = useMemo<{ - tree: TreeNode | null; - treeParentMap: Record; - selectedRequest: HttpRequest | GrpcRequest | null; + tree: SidebarTreeNode | null; + treeParentMap: Record; selectableRequests: { id: string; index: number; - tree: TreeNode; + tree: SidebarTreeNode; }[]; }>(() => { - const treeParentMap: Record = {}; + const childrenMap: Record = {}; + for (const item of [...requests, ...folders]) { + if (item.folderId == null) { + childrenMap[item.workspaceId] = childrenMap[item.workspaceId] ?? []; + childrenMap[item.workspaceId]!.push(item); + } else { + childrenMap[item.folderId] = childrenMap[item.folderId] ?? []; + childrenMap[item.folderId]!.push(item); + } + } + + const treeParentMap: Record = {}; const selectableRequests: { id: string; index: number; - tree: TreeNode; + tree: SidebarTreeNode; }[] = []; if (activeWorkspace == null) { - return { tree: null, treeParentMap, selectableRequests, selectedRequest: null }; + return { tree: null, treeParentMap, selectableRequests }; } - let selectedRequest: HttpRequest | GrpcRequest | null = null; + const selectedRequest: HttpRequest | GrpcRequest | null = null; let selectableRequestIndex = 0; // Put requests and folders into a tree structure - const next = (node: TreeNode): TreeNode => { - if ( - node.item.id === selectedId && - (node.item.model === 'http_request' || node.item.model === 'grpc_request') - ) { - selectedRequest = node.item; - } - const childItems = [...requests, ...folders].filter((f) => - node.item.model === 'workspace' ? f.folderId == null : f.folderId === node.item.id, - ); + const next = (node: SidebarTreeNode): SidebarTreeNode => { + const childItems = childrenMap[node.item.id] ?? []; // Recurse to children - const isCollapsed = collapsed.value?.[node.item.id]; + const isCollapsed = collapsed?.[node.item.id]; const depth = node.depth + 1; childItems.sort((a, b) => a.sortPriority - b.sortPriority); for (const item of childItems) { @@ -175,18 +118,19 @@ export function Sidebar({ className }: Props) { const tree = next({ item: activeWorkspace, children: [], depth: 0 }); return { tree, treeParentMap, selectableRequests, selectedRequest }; - }, [activeWorkspace, selectedId, requests, folders, collapsed.value]); + }, [activeWorkspace, requests, folders, collapsed]); const focusActiveRequest = useCallback( ( args: { forced?: { id: string; - tree: TreeNode; + tree: SidebarTreeNode; }; noFocusSidebar?: boolean; } = {}, ) => { + const activeRequest = getActiveRequest(); const { forced, noFocusSidebar } = args; const tree = forced?.tree ?? treeParentMap[activeRequest?.id ?? 'n/a'] ?? null; const children = tree?.children ?? []; @@ -204,11 +148,11 @@ export function Sidebar({ className }: Props) { sidebarRef.current?.focus(); } }, - [activeRequest, treeParentMap], + [setHasFocus, setSelectedId, treeParentMap], ); const handleSelect = useCallback( - async (id: string, opts: { noFocus?: boolean } = {}) => { + async (id: string) => { const tree = treeParentMap[id ?? 'n/a'] ?? null; const children = tree?.children ?? []; const node = children.find((m) => m.item.id === id) ?? null; @@ -219,40 +163,36 @@ export function Sidebar({ className }: Props) { const { item } = node; if (item.model === 'folder') { - await collapsed.set((c) => ({ ...c, [item.id]: !c[item.id] })); + await setCollapsed((c) => ({ ...c, [item.id]: !c[item.id] })); } else { - routes.navigate('request', { - requestId: id, - workspaceId: item.workspaceId, - environmentId: activeEnvironment?.id ?? null, - cookieJarId: activeCookieJar?.id ?? null, + router.navigate({ + to: Route.fullPath, + params: { + requestId: id, + workspaceId: item.workspaceId, + }, + search: (prev) => ({ ...prev }), }); + + setHasFocus(true); setSelectedId(id); setSelectedTree(tree); - if (!opts.noFocus) focusActiveRequest({ forced: { id, tree } }); } }, - [ - treeParentMap, - collapsed, - routes, - activeEnvironment?.id, - activeCookieJar?.id, - focusActiveRequest, - ], + [treeParentMap, setCollapsed, setHasFocus, setSelectedId], ); const handleClearSelected = useCallback(() => { setSelectedId(null); setSelectedTree(null); - }, []); + }, [setSelectedId]); const handleFocus = useCallback(() => { if (hasFocus) return; focusActiveRequest({ noFocusSidebar: true }); }, [focusActiveRequest, hasFocus]); - const handleBlur = useCallback(() => setHasFocus(false), []); + const handleBlur = useCallback(() => setHasFocus(false), [setHasFocus]); useHotKey('sidebar.focus', async () => { // Hide the sidebar if it's already focused @@ -277,16 +217,18 @@ export function Sidebar({ className }: Props) { useKeyPressEvent('Enter', (e) => { if (!hasFocus) return; const selected = selectableRequests.find((r) => r.id === selectedId); - if (!selected || selected.id === activeRequest?.id || activeWorkspace == null) { + if (!selected || activeWorkspace == null) { return; } e.preventDefault(); - routes.navigate('request', { - requestId: selected.id, - workspaceId: activeWorkspace?.id ?? null, - environmentId: activeEnvironment?.id ?? null, - cookieJarId: activeCookieJar?.id ?? null, + router.navigate({ + to: Route.fullPath, + params: { + requestId: selected.id, + workspaceId: activeWorkspace?.id ?? null, + }, + search: (prev) => ({ ...prev }), }); }); @@ -395,13 +337,13 @@ export function Sidebar({ className }: Props) { const sortPriority = i * 1000; if (child.item.model === 'folder') { const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId }); - return updateAnyFolder.mutateAsync({ id: child.item.id, update: updateFolder }); + return updateAnyFolder({ id: child.item.id, update: updateFolder }); } else if (child.item.model === 'grpc_request') { const updateRequest = (r: GrpcRequest) => ({ ...r, sortPriority, folderId }); - return updateAnyGrpcRequest.mutateAsync({ id: child.item.id, update: updateRequest }); + return updateAnyGrpcRequest({ id: child.item.id, update: updateRequest }); } else if (child.item.model === 'http_request') { const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId }); - return updateAnyHttpRequest.mutateAsync({ id: child.item.id, update: updateRequest }); + return updateAnyHttpRequest({ id: child.item.id, update: updateRequest }); } }), ); @@ -409,13 +351,13 @@ export function Sidebar({ className }: Props) { const sortPriority = afterPriority - (afterPriority - beforePriority) / 2; if (child.item.model === 'folder') { const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId }); - await updateAnyFolder.mutateAsync({ id: child.item.id, update: updateFolder }); + await updateAnyFolder({ id: child.item.id, update: updateFolder }); } else if (child.item.model === 'grpc_request') { const updateRequest = (r: GrpcRequest) => ({ ...r, sortPriority, folderId }); - await updateAnyGrpcRequest.mutateAsync({ id: child.item.id, update: updateRequest }); + await updateAnyGrpcRequest({ id: child.item.id, update: updateRequest }); } else if (child.item.model === 'http_request') { const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId }); - await updateAnyHttpRequest.mutateAsync({ id: child.item.id, update: updateRequest }); + await updateAnyHttpRequest({ id: child.item.id, update: updateRequest }); } } setDraggingId(null); @@ -445,7 +387,7 @@ export function Sidebar({ className }: Props) { const mainContextMenuItems = useCreateDropdownItems(); // Not ready to render yet - if (tree == null || collapsed.value == null) { + if (tree == null || collapsed == null) { return null; } @@ -457,7 +399,14 @@ export function Sidebar({ className }: Props) { onBlur={handleBlur} tabIndex={hidden ? -1 : 0} onContextMenu={handleMainContextMenu} - className={classNames(className, 'h-full grid grid-rows-[minmax(0,1fr)_auto]')} + data-focused={hasFocus} + className={classNames( + className, + // Style item selection color here, because it's very hard to do in an efficient + // way in the item itself (selection ID makes it hard) + hasFocus && '[&_[data-selected=true]]:bg-surface-active', + 'h-full grid grid-rows-[minmax(0,1fr)_auto]', + )} > ); -} - -interface SidebarItemsProps { - tree: TreeNode; - focused: boolean; - draggingId: string | null; - activeId: string | null; - selectedId: string | null; - selectedTree: TreeNode | null; - treeParentMap: Record; - hoveredTree: TreeNode | null; - hoveredIndex: number | null; - handleMove: (id: string, side: 'above' | 'below') => void; - handleEnd: (id: string) => void; - handleDragStart: (id: string) => void; - onSelect: (requestId: string) => void; - isCollapsed: (id: string) => boolean; - httpRequestActions: CallableHttpRequestAction[]; - httpResponses: HttpResponse[]; - grpcConnections: GrpcConnection[]; -} - -function SidebarItems({ - tree, - focused, - activeId, - selectedId, - selectedTree, - draggingId, - onSelect, - treeParentMap, - isCollapsed, - hoveredTree, - hoveredIndex, - handleEnd, - handleMove, - handleDragStart, - httpRequestActions, - httpResponses, - grpcConnections, -}: SidebarItemsProps) { - return ( - 0 && 'border-l border-border-subtle', - tree.depth === 0 && 'ml-0', - tree.depth >= 1 && 'ml-[1.2rem]', - )} - > - {tree.children.map((child, i) => { - const selected = selectedId === child.item.id; - const active = activeId === child.item.id; - return ( - - {hoveredIndex === i && hoveredTree?.item.id === tree.item.id && } - - ) - } - httpRequestActions={httpRequestActions} - latestHttpResponse={httpResponses.find((r) => r.requestId === child.item.id) ?? null} - latestGrpcConnection={ - grpcConnections.find((c) => c.requestId === child.item.id) ?? null - } - onMove={handleMove} - onEnd={handleEnd} - onSelect={onSelect} - onDragStart={handleDragStart} - useProminentStyles={focused} - isCollapsed={isCollapsed} - child={child} - > - {child.item.model === 'folder' && - !isCollapsed(child.item.id) && - draggingId !== child.item.id && ( - - )} - - - ); - })} - {hoveredIndex === tree.children.length && hoveredTree?.item.id === tree.item.id && ( - - )} - - ); -} - -type SidebarItemProps = { - className?: string; - itemId: string; - itemName: string; - itemFallbackName: string; - itemModel: AnyModel['model']; - itemPrefix: ReactNode; - useProminentStyles?: boolean; - selected: boolean; - onMove: (id: string, side: 'above' | 'below') => void; - onEnd: (id: string) => void; - onDragStart: (id: string) => void; - children?: ReactNode; - child: TreeNode; - latestHttpResponse: HttpResponse | null; - latestGrpcConnection: GrpcConnection | null; -} & Pick; - -type DragItem = { - id: string; - itemName: string; -}; - -function SidebarItem({ - itemName, - itemId, - itemModel, - child, - onMove, - onEnd, - onDragStart, - onSelect, - isCollapsed, - itemPrefix, - className, - selected, - itemFallbackName, - useProminentStyles, - latestHttpResponse, - latestGrpcConnection, - httpRequestActions, - children, -}: SidebarItemProps) { - const ref = useRef(null); - - const [, connectDrop] = useDrop( - { - accept: ItemTypes.REQUEST, - hover: (_, monitor) => { - if (!ref.current) return; - const hoverBoundingRect = ref.current?.getBoundingClientRect(); - const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; - const clientOffset = monitor.getClientOffset(); - const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top; - onMove(itemId, hoverClientY < hoverMiddleY ? 'above' : 'below'); - }, - }, - [onMove], - ); - - const [, connectDrag] = useDrag< - DragItem, - unknown, - { - isDragging: boolean; - } - >( - () => ({ - type: ItemTypes.REQUEST, - item: () => { - // Cancel drag when editing - if (editing) return null; - onDragStart(itemId); - return { id: itemId, itemName }; - }, - collect: (m) => ({ isDragging: m.isDragging() }), - options: { dropEffect: 'move' }, - end: () => onEnd(itemId), - }), - [onEnd], - ); - - connectDrag(connectDrop(ref)); - - const dialog = useDialog(); - const activeRequest = useActiveRequest(); - const deleteFolder = useDeleteFolder(itemId); - const deleteRequest = useDeleteRequest(itemId); - const renameRequest = useRenameRequest(itemId); - const duplicateFolder = useDuplicateFolder(itemId); - const duplicateHttpRequest = useDuplicateHttpRequest({ id: itemId, navigateAfter: true }); - const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: itemId, navigateAfter: true }); - const sendRequest = useSendAnyHttpRequest(); - const moveToWorkspace = useMoveToWorkspace(itemId); - const sendManyRequests = useSendManyRequests(); - const updateHttpRequest = useUpdateAnyHttpRequest(); - const workspaces = useWorkspaces(); - const updateGrpcRequest = useUpdateAnyGrpcRequest(); - const [editing, setEditing] = useState(false); - const isActive = activeRequest?.id === itemId; - const createDropdownItems = useCreateDropdownItems({ folderId: itemId }); - - useScrollIntoView(ref.current, isActive); - - const handleSubmitNameEdit = useCallback( - async (el: HTMLInputElement) => { - if (itemModel === 'http_request') { - await updateHttpRequest.mutateAsync({ - id: itemId, - update: (r) => ({ ...r, name: el.value }), - }); - } else if (itemModel === 'grpc_request') { - await updateGrpcRequest.mutateAsync({ - id: itemId, - update: (r) => ({ ...r, name: el.value }), - }); - } - setEditing(false); - }, - [itemId, itemModel, updateGrpcRequest, updateHttpRequest], - ); - - const handleFocus = useCallback((el: HTMLInputElement | null) => { - el?.focus(); - el?.select(); - }, []); - - const handleInputKeyDown = useCallback( - async (e: React.KeyboardEvent) => { - e.stopPropagation(); - switch (e.key) { - case 'Enter': - e.preventDefault(); - await handleSubmitNameEdit(e.currentTarget); - break; - case 'Escape': - e.preventDefault(); - setEditing(false); - break; - } - }, - [handleSubmitNameEdit], - ); - - const handleStartEditing = useCallback(() => { - if (itemModel !== 'http_request' && itemModel !== 'grpc_request') return; - setEditing(true); - }, [setEditing, itemModel]); - - const handleBlur = useCallback( - async (e: React.FocusEvent) => { - await handleSubmitNameEdit(e.currentTarget); - }, - [handleSubmitNameEdit], - ); - - const handleSelect = useCallback(() => onSelect(itemId), [onSelect, itemId]); - const [showContextMenu, setShowContextMenu] = useState<{ - x: number; - y: number; - } | null>(null); - - const handleCloseContextMenu = useCallback(() => { - setShowContextMenu(null); - }, []); - - const handleContextMenu = useCallback((e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - setShowContextMenu({ x: e.clientX, y: e.clientY }); - }, []); - - const items = useMemo(() => { - if (itemModel === 'folder') { - return [ - { - key: 'send-all', - label: 'Send All', - leftSlot: , - onSelect: () => sendManyRequests.mutate(child.children.map((c) => c.item.id)), - }, - { - key: 'folder-settings', - label: 'Settings', - leftSlot: , - onSelect: () => - dialog.show({ - id: 'folder-settings', - title: 'Folder Settings', - size: 'md', - render: () => , - }), - }, - { - key: 'duplicateFolder', - label: 'Duplicate', - leftSlot: , - onSelect: () => duplicateFolder.mutate(), - }, - { - key: 'delete-folder', - label: 'Delete', - variant: 'danger', - leftSlot: , - onSelect: () => deleteFolder.mutate(), - }, - { type: 'separator' }, - ...createDropdownItems, - ]; - } else { - const requestItems: DropdownItem[] = - itemModel === 'http_request' - ? [ - { - key: 'send-request', - label: 'Send', - hotKeyAction: 'http_request.send', - hotKeyLabelOnly: true, // Already bound in URL bar - leftSlot: , - onSelect: () => sendRequest.mutate(itemId), - }, - ...httpRequestActions.map((a) => ({ - key: a.key, - label: a.label, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - leftSlot: , - onSelect: async () => { - const request = await getHttpRequest(itemId); - if (request != null) await a.call(request); - }, - })), - { type: 'separator' }, - ] - : []; - return [ - ...requestItems, - { - key: 'rename-request', - label: 'Rename', - leftSlot: , - onSelect: renameRequest.mutate, - }, - { - key: 'duplicate-request', - label: 'Duplicate', - hotKeyAction: 'http_request.duplicate', - hotKeyLabelOnly: true, // Would trigger for every request (bad) - leftSlot: , - onSelect: () => - itemModel === 'http_request' - ? duplicateHttpRequest.mutate() - : duplicateGrpcRequest.mutate(), - }, - { - key: 'move-workspace', - label: 'Move', - leftSlot: , - hidden: workspaces.length <= 1, - onSelect: moveToWorkspace.mutate, - }, - { - key: 'delete-request', - variant: 'danger', - label: 'Delete', - leftSlot: , - onSelect: () => deleteRequest.mutate(), - }, - ]; - } - }, [ - child.children, - createDropdownItems, - deleteFolder, - deleteRequest, - duplicateFolder, - duplicateGrpcRequest, - duplicateHttpRequest, - httpRequestActions, - itemId, - itemModel, - moveToWorkspace.mutate, - renameRequest.mutate, - sendManyRequests, - sendRequest, - workspaces.length, - ]); - - return ( - - - - - {itemModel === 'folder' && ( - - )} - - {itemPrefix} - {editing ? ( - - ) : ( - {itemName || itemFallbackName} - )} - - {latestGrpcConnection ? ( - - {isResponseLoading(latestGrpcConnection) && ( - - )} - - ) : latestHttpResponse ? ( - - {isResponseLoading(latestHttpResponse) ? ( - - ) : ( - - )} - - ) : null} - - - {children} - - ); -} +}); diff --git a/src-web/components/SidebarItem.tsx b/src-web/components/SidebarItem.tsx new file mode 100644 index 00000000..73781357 --- /dev/null +++ b/src-web/components/SidebarItem.tsx @@ -0,0 +1,278 @@ +import type { AnyModel, GrpcConnection, HttpResponse } from '@yaakapp-internal/models'; +import classNames from 'classnames'; +import type { ReactNode } from 'react'; +import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; +import type { XYCoord } from 'react-dnd'; +import { useDrag, useDrop } from 'react-dnd'; +import { activeRequestAtom } from '../hooks/useActiveRequest'; +import { useScrollIntoView } from '../hooks/useScrollIntoView'; +import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest'; +import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest'; +import { isResponseLoading } from '../lib/model_util'; +import { jotaiStore } from '../routes/__root'; +import { HttpMethodTag } from './core/HttpMethodTag'; +import { Icon } from './core/Icon'; +import { StatusTag } from './core/StatusTag'; +import { RequestContextMenu } from './RequestContextMenu'; +import type { SidebarTreeNode } from './Sidebar'; +import { sidebarSelectedIdAtom } from './Sidebar'; +import type { SidebarItemsProps } from './SidebarItems'; + +enum ItemTypes { + REQUEST = 'request', +} + +export type SidebarItemProps = { + className?: string; + itemId: string; + itemName: string; + itemFallbackName: string; + itemModel: AnyModel['model']; + onMove: (id: string, side: 'above' | 'below') => void; + onEnd: (id: string) => void; + onDragStart: (id: string) => void; + children?: ReactNode; + child: SidebarTreeNode; + latestHttpResponse: HttpResponse | null; + latestGrpcConnection: GrpcConnection | null; +} & Pick; + +type DragItem = { + id: string; + itemName: string; +}; + +function SidebarItem_({ + itemName, + itemId, + itemModel, + child, + onMove, + onEnd, + onDragStart, + onSelect, + isCollapsed, + className, + itemFallbackName, + latestHttpResponse, + latestGrpcConnection, + children, +}: SidebarItemProps) { + const ref = useRef(null); + + const [, connectDrop] = useDrop( + { + accept: ItemTypes.REQUEST, + hover: (_, monitor) => { + if (!ref.current) return; + const hoverBoundingRect = ref.current?.getBoundingClientRect(); + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + const clientOffset = monitor.getClientOffset(); + const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top; + onMove(itemId, hoverClientY < hoverMiddleY ? 'above' : 'below'); + }, + }, + [onMove], + ); + + const [, connectDrag] = useDrag< + DragItem, + unknown, + { + isDragging: boolean; + } + >( + () => ({ + type: ItemTypes.REQUEST, + item: () => { + // Cancel drag when editing + if (editing) return null; + onDragStart(itemId); + return { id: itemId, itemName }; + }, + collect: (m) => ({ isDragging: m.isDragging() }), + options: { dropEffect: 'move' }, + end: () => onEnd(itemId), + }), + [onEnd], + ); + + connectDrag(connectDrop(ref)); + + const updateHttpRequest = useUpdateAnyHttpRequest(); + const updateGrpcRequest = useUpdateAnyGrpcRequest(); + const [editing, setEditing] = useState(false); + + const [selected, setSelected] = useState( + jotaiStore.get(sidebarSelectedIdAtom) == itemId, + ); + useEffect(() => { + jotaiStore.sub(sidebarSelectedIdAtom, () => { + const value = jotaiStore.get(sidebarSelectedIdAtom); + setSelected(value === itemId); + }); + }, [itemId]); + + const [active, setActive] = useState(jotaiStore.get(activeRequestAtom)?.id === itemId); + useEffect(() => { + jotaiStore.sub(activeRequestAtom, () => { + const value = jotaiStore.get(activeRequestAtom); + setActive(value?.id === itemId); + }); + }, [itemId]); + + useScrollIntoView(ref.current, active); + + const handleSubmitNameEdit = useCallback( + async (el: HTMLInputElement) => { + if (itemModel === 'http_request') { + await updateHttpRequest.mutateAsync({ + id: itemId, + update: (r) => ({ ...r, name: el.value }), + }); + } else if (itemModel === 'grpc_request') { + await updateGrpcRequest.mutateAsync({ + id: itemId, + update: (r) => ({ ...r, name: el.value }), + }); + } + setEditing(false); + }, + [itemId, itemModel, updateGrpcRequest, updateHttpRequest], + ); + + const handleFocus = useCallback((el: HTMLInputElement | null) => { + el?.focus(); + el?.select(); + }, []); + + const handleInputKeyDown = useCallback( + async (e: React.KeyboardEvent) => { + e.stopPropagation(); + switch (e.key) { + case 'Enter': + e.preventDefault(); + await handleSubmitNameEdit(e.currentTarget); + break; + case 'Escape': + e.preventDefault(); + setEditing(false); + break; + } + }, + [handleSubmitNameEdit], + ); + + const handleStartEditing = useCallback(() => { + if (itemModel !== 'http_request' && itemModel !== 'grpc_request') return; + setEditing(true); + }, [setEditing, itemModel]); + + const handleBlur = useCallback( + async (e: React.FocusEvent) => { + await handleSubmitNameEdit(e.currentTarget); + }, + [handleSubmitNameEdit], + ); + + const handleSelect = useCallback(() => onSelect(itemId), [onSelect, itemId]); + const [showContextMenu, setShowContextMenu] = useState<{ + x: number; + y: number; + } | null>(null); + + const handleContextMenu = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setShowContextMenu({ x: e.clientX, y: e.clientY }); + }, []); + + const handleCloseContextMenu = useCallback(() => setShowContextMenu(null), []); + + const itemPrefix = (child.item.model === 'http_request' || + child.item.model === 'grpc_request') && ( + + ); + + return ( + + + + + {itemModel === 'folder' && ( + + )} + + {itemPrefix} + {editing ? ( + + ) : ( + {itemName || itemFallbackName} + )} + + {latestGrpcConnection ? ( + + {isResponseLoading(latestGrpcConnection) && ( + + )} + + ) : latestHttpResponse ? ( + + {isResponseLoading(latestHttpResponse) ? ( + + ) : ( + + )} + + ) : null} + + + {children} + + ); +} + +export const SidebarItem = memo(SidebarItem_, (a, b) => { + const different = []; + for (const key of Object.keys(a) as (keyof SidebarItemProps)[]) { + if (a[key] !== b[key]) { + different.push(key); + } + } + if (different.length > 0) { + console.log('ITEM DIFFERENT -------------------', different.join(', ')); + } + return different.length === 0; +}); diff --git a/src-web/components/SidebarItems.tsx b/src-web/components/SidebarItems.tsx new file mode 100644 index 00000000..dcca62c0 --- /dev/null +++ b/src-web/components/SidebarItems.tsx @@ -0,0 +1,118 @@ +import type { GrpcConnection, HttpResponse } from '@yaakapp-internal/models'; +import classNames from 'classnames'; +import React, { Fragment, memo } from 'react'; +import { fallbackRequestName } from '../lib/fallbackRequestName'; +import { VStack } from './core/Stacks'; +import { DropMarker } from './DropMarker'; +import type { SidebarTreeNode } from './Sidebar'; +import { SidebarItem } from './SidebarItem'; + +export interface SidebarItemsProps { + tree: SidebarTreeNode; + draggingId: string | null; + selectedTree: SidebarTreeNode | null; + treeParentMap: Record; + hoveredTree: SidebarTreeNode | null; + hoveredIndex: number | null; + handleMove: (id: string, side: 'above' | 'below') => void; + handleEnd: (id: string) => void; + handleDragStart: (id: string) => void; + onSelect: (requestId: string) => void; + isCollapsed: (id: string) => boolean; + httpResponses: HttpResponse[]; + grpcConnections: GrpcConnection[]; +} + +function SidebarItems_({ + tree, + selectedTree, + draggingId, + onSelect, + treeParentMap, + isCollapsed, + hoveredTree, + hoveredIndex, + handleEnd, + handleMove, + handleDragStart, + httpResponses, + grpcConnections, +}: SidebarItemsProps) { + return ( + 0 && 'border-l border-border-subtle', + tree.depth === 0 && 'ml-0', + tree.depth >= 1 && 'ml-[1.2rem]', + )} + > + {tree.children.map((child, i) => { + return ( + + {hoveredIndex === i && hoveredTree?.item.id === tree.item.id && } + r.requestId === child.item.id) ?? null} + latestGrpcConnection={ + grpcConnections.find((c) => c.requestId === child.item.id) ?? null + } + onMove={handleMove} + onEnd={handleEnd} + onSelect={onSelect} + onDragStart={handleDragStart} + isCollapsed={isCollapsed} + child={child} + > + {child.item.model === 'folder' && + !isCollapsed(child.item.id) && + draggingId !== child.item.id && ( + + )} + + + ); + })} + {hoveredIndex === tree.children.length && hoveredTree?.item.id === tree.item.id && ( + + )} + + ); +} + +export const SidebarItems = memo(SidebarItems_, (a, b) => { + const different = []; + for (const key of Object.keys(a) as (keyof SidebarItemsProps)[]) { + if (a[key] !== b[key]) { + different.push(key); + } + } + if (different.length > 0) { + console.log('ITEMS DIFFERENT -------------------', different.join(', ')); + } + return different.length === 0; +}); diff --git a/src-web/components/Workspace.tsx b/src-web/components/Workspace.tsx index df320c63..656a65ba 100644 --- a/src-web/components/Workspace.tsx +++ b/src-web/components/Workspace.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames'; import { motion } from 'framer-motion'; import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react'; -import { useCallback, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useActiveRequest } from '../hooks/useActiveRequest'; import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; import { useFloatingSidebarHidden } from '../hooks/useFloatingSidebarHidden'; @@ -9,7 +9,6 @@ import { useImportData } from '../hooks/useImportData'; import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar'; import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useSidebarWidth } from '../hooks/useSidebarWidth'; -import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTitle'; import { useWorkspaces } from '../hooks/useWorkspaces'; import { Banner } from './core/Banner'; import { Button } from './core/Button'; @@ -31,22 +30,21 @@ const head = { gridArea: 'head' }; const body = { gridArea: 'body' }; const drag = { gridArea: 'drag' }; -export default function Workspace() { - useSyncWorkspaceRequestTitle(); - +export function Workspace() { const workspaces = useWorkspaces(); - const activeWorkspace = useActiveWorkspace(); const { setWidth, width, resetWidth } = useSidebarWidth(); const [sidebarHidden, setSidebarHidden] = useSidebarHidden(); const [floatingSidebarHidden, setFloatingSidebarHidden] = useFloatingSidebarHidden(); - const activeRequest = useActiveRequest(); - const importData = useImportData(); const floating = useShouldFloatSidebar(); const [isResizing, setIsResizing] = useState(false); const moveState = useRef<{ move: (e: MouseEvent) => void; up: (e: MouseEvent) => void } | null>( null, ); + useEffect(() => { + console.log('RENDER WORKSPACE'); + }, []); + const unsub = () => { if (moveState.current !== null) { document.documentElement.removeEventListener('mousemove', moveState.current.move); @@ -161,34 +159,50 @@ export default function Workspace() { > - {activeWorkspace == null ? ( - - - The active workspace was not found. Select a workspace from the header menu or report - this bug to - - - ) : activeRequest == null ? ( - - importData.mutate()}> - Import - - - - New Request - - - - } - /> - ) : activeRequest.model === 'grpc_request' ? ( - - ) : ( - - )} + ); } + +function WorkspaceBody() { + const activeRequest = useActiveRequest(); + const activeWorkspace = useActiveWorkspace(); + const importData = useImportData(); + + if (activeWorkspace == null) { + return ( + + + The active workspace was not found. Select a workspace from the header menu or report this + bug to + + + ); + } + + if (activeRequest == null) { + return ( + + importData.mutate()}> + Import + + + + New Request + + + + } + /> + ); + } + + if (activeRequest.model === 'grpc_request') { + return ; + } + + return ; +} diff --git a/src-web/components/core/Editor/Editor.css b/src-web/components/core/Editor/Editor.css index 5280f1c1..3f972981 100644 --- a/src-web/components/core/Editor/Editor.css +++ b/src-web/components/core/Editor/Editor.css @@ -86,7 +86,7 @@ &.cm-singleline { .cm-editor { - @apply w-full h-auto; + @apply w-full h-full; } .cm-scroller { diff --git a/src-web/components/core/Link.tsx b/src-web/components/core/Link.tsx index 394c44ea..6c3c03c2 100644 --- a/src-web/components/core/Link.tsx +++ b/src-web/components/core/Link.tsx @@ -1,6 +1,6 @@ import classNames from 'classnames'; import type { HTMLAttributes } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; +import { Link as RouterLink } from '@tanstack/react-router'; import { trackEvent } from '../../lib/analytics'; import { Icon } from './Icon'; diff --git a/src-web/hooks/useActiveCookieJar.ts b/src-web/hooks/useActiveCookieJar.ts index 492f6515..2c2aeef0 100644 --- a/src-web/hooks/useActiveCookieJar.ts +++ b/src-web/hooks/useActiveCookieJar.ts @@ -1,5 +1,6 @@ +import { useSearch } from '@tanstack/react-router'; import { useCallback, useEffect, useMemo } from 'react'; -import { useSearchParams } from 'react-router-dom'; +import { Route } from '../routes/workspaces/$workspaceId'; import { useCookieJars } from './useCookieJars'; export const QUERY_COOKIE_JAR_ID = 'cookie_jar_id'; @@ -34,23 +35,18 @@ export function useEnsureActiveCookieJar() { // There's no active jar, so set it to the first one console.log('Setting active cookie jar to', firstJar.id); - setActiveCookieJarId(firstJar.id); + setActiveCookieJarId(firstJar.id).catch(console.error); }, [activeCookieJarId, cookieJars, setActiveCookieJarId]); } function useActiveCookieJarId() { // NOTE: This query param is accessed from Rust side, so do not change - const [params, setParams] = useSearchParams(); - const id = params.get(QUERY_COOKIE_JAR_ID); + const navigate = Route.useNavigate(); + const { cookieJarId: id } = useSearch({ strict: false }); const setId = useCallback( - (id: string) => { - setParams((p) => { - const existing = Object.fromEntries(p); - return { ...existing, [QUERY_COOKIE_JAR_ID]: id }; - }); - }, - [setParams], + (id: string) => navigate({ search: (prev) => ({ ...prev, cookieJarId: id }) }), + [navigate], ); return [id, setId] as const; diff --git a/src-web/hooks/useActiveEnvironment.ts b/src-web/hooks/useActiveEnvironment.ts index 98ae424d..a8e54693 100644 --- a/src-web/hooks/useActiveEnvironment.ts +++ b/src-web/hooks/useActiveEnvironment.ts @@ -1,5 +1,6 @@ +import { useSearch } from '@tanstack/react-router'; import { useCallback, useMemo } from 'react'; -import { useSearchParams } from 'react-router-dom'; +import { Route } from '../routes/workspaces/$workspaceId'; import { useEnvironments } from './useEnvironments'; export function useActiveEnvironment() { @@ -16,22 +17,13 @@ export const QUERY_ENVIRONMENT_ID = 'environment_id'; function useActiveEnvironmentId() { // NOTE: This query param is accessed from Rust side, so do not change - const [params, setParams] = useSearchParams(); - const id = params.get(QUERY_ENVIRONMENT_ID); + const navigate = Route.useNavigate(); + const { environmentId: id } = useSearch({ strict: false }); const setId = useCallback( - (id: string | null) => { - setParams((p) => { - const existing = Object.fromEntries(p); - if (id == null) { - delete existing[QUERY_ENVIRONMENT_ID]; - } else { - existing[QUERY_ENVIRONMENT_ID] = id; - } - return existing; - }); - }, - [setParams], + (environment_id: string | null) => + navigate({ search: (prev) => ({ ...prev, environment_id: environment_id ?? undefined }) }), + [navigate], ); return [id, setId] as const; diff --git a/src-web/hooks/useActiveRequest.ts b/src-web/hooks/useActiveRequest.ts index 6ddf06b9..2a1fc61f 100644 --- a/src-web/hooks/useActiveRequest.ts +++ b/src-web/hooks/useActiveRequest.ts @@ -1,24 +1,30 @@ import type { GrpcRequest, HttpRequest } from '@yaakapp-internal/models'; -import { useActiveRequestId } from './useActiveRequestId'; -import { useRequests } from './useRequests'; +import { atom, useAtomValue } from 'jotai'; +import { jotaiStore } from '../routes/__root'; +import { activeRequestIdAtom } from './useActiveRequestId'; +import { grpcRequestsAtom } from './useGrpcRequests'; +import { httpRequestsAtom } from './useHttpRequests'; interface TypeMap { http_request: HttpRequest; grpc_request: GrpcRequest; } +export const activeRequestAtom = atom((get) => { + const activeRequestId = get(activeRequestIdAtom); + const requests = [...get(httpRequestsAtom), ...get(grpcRequestsAtom)]; + return requests.find((r) => r.id === activeRequestId) ?? null; +}); + +export function getActiveRequest() { + return jotaiStore.get(activeRequestAtom); +} + export function useActiveRequest( model?: T | undefined, ): TypeMap[T] | null { - const requestId = useActiveRequestId(); - const requests = useRequests(); - - for (const request of requests) { - const modelMatch = model == null ? true : request.model === model; - if (modelMatch && request.id === requestId) { - return request as TypeMap[T]; - } - } - + const activeRequest = useAtomValue(activeRequestAtom); + if (model == null) return activeRequest as TypeMap[T]; + if (activeRequest?.model === model) return activeRequest as TypeMap[T]; return null; } diff --git a/src-web/hooks/useActiveRequestId.ts b/src-web/hooks/useActiveRequestId.ts index ce759748..0ff2c16e 100644 --- a/src-web/hooks/useActiveRequestId.ts +++ b/src-web/hooks/useActiveRequestId.ts @@ -1,6 +1,17 @@ -import { useParams } from 'react-router-dom'; +import { useParams } from '@tanstack/react-router'; +import { atom, useAtomValue } from 'jotai'; +import { useEffect } from 'react'; +import { jotaiStore } from '../routes/__root'; + +export const activeRequestIdAtom = atom(); export function useActiveRequestId(): string | null { - const { requestId } = useParams(); - return requestId ?? null; + return useAtomValue(activeRequestIdAtom) ?? null; +} + +export function useSubscribeActiveRequestId() { + const { requestId } = useParams({ strict: false }); + useEffect(() => { + jotaiStore.set(activeRequestIdAtom, requestId); + }, [requestId]); } diff --git a/src-web/hooks/useActiveWorkspace.ts b/src-web/hooks/useActiveWorkspace.ts index 0ec08723..f792ddac 100644 --- a/src-web/hooks/useActiveWorkspace.ts +++ b/src-web/hooks/useActiveWorkspace.ts @@ -1,19 +1,25 @@ +import { useParams } from '@tanstack/react-router'; import type { Workspace } from '@yaakapp-internal/models'; -import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; +import { atom, useAtomValue } from 'jotai/index'; +import { useEffect } from 'react'; +import { jotaiStore } from '../routes/__root'; import { useWorkspaces } from './useWorkspaces'; +export const activeWorkspaceIdAtom = atom(); + export function useActiveWorkspace(): Workspace | null { const workspaceId = useActiveWorkspaceId(); const workspaces = useWorkspaces(); - - return useMemo( - () => workspaces.find((w) => w.id === workspaceId) ?? null, - [workspaces, workspaceId], - ); + return workspaces.find((w) => w.id === workspaceId) ?? null; } function useActiveWorkspaceId(): string | null { - const { workspaceId } = useParams(); - return workspaceId ?? null; + return useAtomValue(activeWorkspaceIdAtom) ?? null; +} + +export function useSubscribeActiveWorkspaceId() { + const { workspaceId } = useParams({ strict: false }); + useEffect(() => { + jotaiStore.set(activeWorkspaceIdAtom, workspaceId); + }, [workspaceId]); } diff --git a/src-web/hooks/useAppRoutes.tsx b/src-web/hooks/useAppRoutes.tsx deleted file mode 100644 index 29df1e1e..00000000 --- a/src-web/hooks/useAppRoutes.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { SettingsTab } from '../components/Settings/Settings'; -import { QUERY_COOKIE_JAR_ID } from './useActiveCookieJar'; -import { QUERY_ENVIRONMENT_ID } from './useActiveEnvironment'; - -export type RouteParamsWorkspace = { - workspaceId: string; - environmentId: string | null; - cookieJarId: string | null; -}; - -export type RouteParamsRequest = RouteParamsWorkspace & { - requestId: string; -}; - -export type RouteParamsSettings = { - workspaceId: string; - tab?: SettingsTab; -}; - -export const paths = { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - workspaces(_ = {}) { - return '/workspaces'; - }, - workspaceSettings({ workspaceId, tab } = { workspaceId: ':workspaceId' } as RouteParamsSettings) { - return `/workspaces/${workspaceId}/settings?tab=${tab ?? SettingsTab.General}`; - }, - workspace( - { workspaceId, environmentId, cookieJarId } = { - workspaceId: ':workspaceId', - environmentId: ':environmentId', - cookieJarId: ':cookieJarId', - } as RouteParamsWorkspace, - ) { - const path = `/workspaces/${workspaceId}`; - const params = new URLSearchParams(); - if (environmentId != null) params.set(QUERY_ENVIRONMENT_ID, environmentId); - if (cookieJarId != null) params.set(QUERY_COOKIE_JAR_ID, cookieJarId); - return `${path}?${params}`; - }, - request( - { workspaceId, environmentId, requestId, cookieJarId } = { - workspaceId: ':workspaceId', - environmentId: ':environmentId', - requestId: ':requestId', - } as RouteParamsRequest, - ) { - const path = `/workspaces/${workspaceId}/requests/${requestId}`; - const params = new URLSearchParams(); - if (environmentId != null) params.set(QUERY_ENVIRONMENT_ID, environmentId); - if (cookieJarId != null) params.set(QUERY_COOKIE_JAR_ID, cookieJarId); - return `${path}?${params}`; - }, -}; - -export function useAppRoutes() { - const nav = useNavigate(); - - const navigate = useCallback( - (path: T, ...params: Parameters<(typeof paths)[T]>) => { - // Not sure how to make TS work here, but it's good from the - // outside caller perspective. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const resolvedPath = paths[path](...(params as any)); - nav(resolvedPath); - }, - [nav], - ); - - return { - paths, - navigate, - }; -} diff --git a/src-web/hooks/useCancelHttpResponse.ts b/src-web/hooks/useCancelHttpResponse.ts index 1fb670fc..574bdcba 100644 --- a/src-web/hooks/useCancelHttpResponse.ts +++ b/src-web/hooks/useCancelHttpResponse.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import { event } from '@tauri-apps/api'; import { trackEvent } from '../lib/analytics'; diff --git a/src-web/hooks/useCheckForUpdates.tsx b/src-web/hooks/useCheckForUpdates.tsx index 09640849..176cbd2f 100644 --- a/src-web/hooks/useCheckForUpdates.tsx +++ b/src-web/hooks/useCheckForUpdates.tsx @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import { InlineCode } from '../components/core/InlineCode'; import { minPromiseMillis } from '../lib/minPromiseMillis'; import { invokeCmd } from '../lib/tauri'; diff --git a/src-web/hooks/useCopyHttpResponse.ts b/src-web/hooks/useCopyHttpResponse.ts index 92e9057a..9df6e4b5 100644 --- a/src-web/hooks/useCopyHttpResponse.ts +++ b/src-web/hooks/useCopyHttpResponse.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { HttpResponse } from '@yaakapp-internal/models'; import { useCopy } from './useCopy'; import { getResponseBodyText } from '../lib/responseBody'; diff --git a/src-web/hooks/useCreateCookieJar.ts b/src-web/hooks/useCreateCookieJar.ts index e1793ced..2d5a0830 100644 --- a/src-web/hooks/useCreateCookieJar.ts +++ b/src-web/hooks/useCreateCookieJar.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { CookieJar } from '@yaakapp-internal/models'; import {useSetAtom} from "jotai"; import { trackEvent } from '../lib/analytics'; diff --git a/src-web/hooks/useCreateDropdownItems.tsx b/src-web/hooks/useCreateDropdownItems.tsx index 68f985bb..0dbc77fc 100644 --- a/src-web/hooks/useCreateDropdownItems.tsx +++ b/src-web/hooks/useCreateDropdownItems.tsx @@ -15,9 +15,9 @@ export function useCreateDropdownItems({ hideIcons?: boolean; folderId?: string | null; } = {}): DropdownItem[] { - const createHttpRequest = useCreateHttpRequest(); - const createGrpcRequest = useCreateGrpcRequest(); - const createFolder = useCreateFolder(); + const { mutate: createHttpRequest } = useCreateHttpRequest(); + const { mutate: createGrpcRequest } = useCreateGrpcRequest(); + const { mutate: createFolder } = useCreateFolder(); return useMemo( () => [ @@ -25,14 +25,14 @@ export function useCreateDropdownItems({ key: 'create-http-request', label: 'HTTP Request', leftSlot: hideIcons ? undefined : , - onSelect: () => createHttpRequest.mutate({ folderId }), + onSelect: () => createHttpRequest({ folderId }), }, { key: 'create-graphql-request', label: 'GraphQL Query', leftSlot: hideIcons ? undefined : , onSelect: () => - createHttpRequest.mutate({ + createHttpRequest({ folderId, bodyType: BODY_TYPE_GRAPHQL, method: 'POST', @@ -43,7 +43,7 @@ export function useCreateDropdownItems({ key: 'create-grpc-request', label: 'gRPC Call', leftSlot: hideIcons ? undefined : , - onSelect: () => createGrpcRequest.mutate({ folderId }), + onSelect: () => createGrpcRequest({ folderId }), }, ...((hideFolder ? [] @@ -55,7 +55,7 @@ export function useCreateDropdownItems({ key: 'create-folder', label: 'Folder', leftSlot: hideIcons ? undefined : , - onSelect: () => createFolder.mutate({ folderId }), + onSelect: () => createFolder({ folderId }), }, ]) as DropdownItem[]), ], diff --git a/src-web/hooks/useCreateEnvironment.ts b/src-web/hooks/useCreateEnvironment.ts index 31b74723..52e6f6a9 100644 --- a/src-web/hooks/useCreateEnvironment.ts +++ b/src-web/hooks/useCreateEnvironment.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { Environment } from '@yaakapp-internal/models'; import {useSetAtom} from "jotai"; import { trackEvent } from '../lib/analytics'; diff --git a/src-web/hooks/useCreateFolder.ts b/src-web/hooks/useCreateFolder.ts index 3804add4..f662d329 100644 --- a/src-web/hooks/useCreateFolder.ts +++ b/src-web/hooks/useCreateFolder.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { Folder } from '@yaakapp-internal/models'; import { useSetAtom } from 'jotai'; import { trackEvent } from '../lib/analytics'; @@ -20,6 +20,7 @@ export function useCreateFolder() { >({ mutationKey: ['create_folder'], mutationFn: async (patch) => { + console.log("FOLDER", workspace); if (workspace === null) { throw new Error("Cannot create folder when there's no active workspace"); } diff --git a/src-web/hooks/useCreateGrpcRequest.ts b/src-web/hooks/useCreateGrpcRequest.ts index 0b0f9580..07a8cfd3 100644 --- a/src-web/hooks/useCreateGrpcRequest.ts +++ b/src-web/hooks/useCreateGrpcRequest.ts @@ -1,22 +1,17 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { GrpcRequest } from '@yaakapp-internal/models'; -import {useSetAtom} from "jotai"; +import { useSetAtom } from 'jotai'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; -import {useActiveCookieJar} from "./useActiveCookieJar"; -import { useActiveEnvironment } from './useActiveEnvironment'; -import { useActiveRequest } from './useActiveRequest'; +import { router } from '../main'; +import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId'; +import { getActiveRequest } from './useActiveRequest'; import { useActiveWorkspace } from './useActiveWorkspace'; -import { useAppRoutes } from './useAppRoutes'; -import {grpcRequestsAtom} from "./useGrpcRequests"; -import {updateModelList} from "./useSyncModelStores"; +import { grpcRequestsAtom } from './useGrpcRequests'; +import { updateModelList } from './useSyncModelStores'; export function useCreateGrpcRequest() { const workspace = useActiveWorkspace(); - const [activeEnvironment] = useActiveEnvironment(); - const [activeCookieJar] = useActiveCookieJar(); - const activeRequest = useActiveRequest(); - const routes = useAppRoutes(); const setGrpcRequests = useSetAtom(grpcRequestsAtom); return useMutation< @@ -29,6 +24,7 @@ export function useCreateGrpcRequest() { if (workspace === null) { throw new Error("Cannot create grpc request when there's no active workspace"); } + const activeRequest = getActiveRequest(); if (patch.sortPriority === undefined) { if (activeRequest != null) { // Place above currently active request @@ -50,11 +46,13 @@ export function useCreateGrpcRequest() { // Optimistic update setGrpcRequests(updateModelList(request)); - routes.navigate('request', { - workspaceId: request.workspaceId, - requestId: request.id, - environmentId: activeEnvironment?.id ?? null, - cookieJarId: activeCookieJar?.id ?? null, + router.navigate({ + to: Route.fullPath, + params: { + workspaceId: request.workspaceId, + requestId: request.id, + }, + search: (prev) => ({ ...prev }), }); }, }); diff --git a/src-web/hooks/useCreateHttpRequest.ts b/src-web/hooks/useCreateHttpRequest.ts index 0c73b255..95206a6b 100644 --- a/src-web/hooks/useCreateHttpRequest.ts +++ b/src-web/hooks/useCreateHttpRequest.ts @@ -1,28 +1,24 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { HttpRequest } from '@yaakapp-internal/models'; import { useSetAtom } from 'jotai/index'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; -import { useActiveCookieJar } from './useActiveCookieJar'; -import { useActiveEnvironment } from './useActiveEnvironment'; -import { useActiveRequest } from './useActiveRequest'; +import { router } from '../main'; +import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId'; +import { getActiveRequest } from './useActiveRequest'; import { useActiveWorkspace } from './useActiveWorkspace'; -import { useAppRoutes } from './useAppRoutes'; import { httpRequestsAtom } from './useHttpRequests'; import { updateModelList } from './useSyncModelStores'; export function useCreateHttpRequest() { - const workspace = useActiveWorkspace(); - const [activeEnvironment] = useActiveEnvironment(); - const [activeCookieJar] = useActiveCookieJar(); - const activeRequest = useActiveRequest(); - const routes = useAppRoutes(); + const activeWorkspace = useActiveWorkspace(); const setHttpRequests = useSetAtom(httpRequestsAtom); return useMutation>({ mutationKey: ['create_http_request'], mutationFn: async (patch = {}) => { - if (workspace === null) { + const activeRequest = getActiveRequest(); + if (activeWorkspace === null) { throw new Error("Cannot create request when there's no active workspace"); } if (patch.sortPriority === undefined) { @@ -36,7 +32,7 @@ export function useCreateHttpRequest() { } patch.folderId = patch.folderId || activeRequest?.folderId; return invokeCmd('cmd_create_http_request', { - request: { workspaceId: workspace.id, ...patch }, + request: { workspaceId: activeWorkspace.id, ...patch }, }); }, onSettled: () => trackEvent('http_request', 'create'), @@ -44,11 +40,10 @@ export function useCreateHttpRequest() { // Optimistic update setHttpRequests(updateModelList(request)); - routes.navigate('request', { - workspaceId: request.workspaceId, - requestId: request.id, - environmentId: activeEnvironment?.id ?? null, - cookieJarId: activeCookieJar?.id ?? null, + await router.navigate({ + to: Route.fullPath, + params: { workspaceId: request.workspaceId, requestId: request.id }, + search: (prev) => ({ ...prev }), }); }, }); diff --git a/src-web/hooks/useCreateWorkspace.ts b/src-web/hooks/useCreateWorkspace.ts index 60cc7c34..e3dd3e49 100644 --- a/src-web/hooks/useCreateWorkspace.ts +++ b/src-web/hooks/useCreateWorkspace.ts @@ -1,14 +1,14 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { Workspace } from '@yaakapp-internal/models'; import { useSetAtom } from 'jotai/index'; import { invokeCmd } from '../lib/tauri'; -import { useAppRoutes } from './useAppRoutes'; +import { router } from '../main'; +import { Route } from '../routes/workspaces/$workspaceId'; import { usePrompt } from './usePrompt'; import { updateModelList } from './useSyncModelStores'; import { workspacesAtom } from './useWorkspaces'; export function useCreateWorkspace() { - const routes = useAppRoutes(); const prompt = usePrompt(); const setWorkspaces = useSetAtom(workspacesAtom); @@ -34,10 +34,9 @@ export function useCreateWorkspace() { // Optimistic update setWorkspaces(updateModelList(workspace)); - routes.navigate('workspace', { - workspaceId: workspace.id, - environmentId: null, - cookieJarId: null, + router.navigate({ + to: Route.fullPath, + params: { workspaceId: workspace.id }, }); }, }); diff --git a/src-web/hooks/useDeleteAnyGrpcRequest.tsx b/src-web/hooks/useDeleteAnyGrpcRequest.tsx index 46dbef1c..3752f0f8 100644 --- a/src-web/hooks/useDeleteAnyGrpcRequest.tsx +++ b/src-web/hooks/useDeleteAnyGrpcRequest.tsx @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { GrpcRequest } from '@yaakapp-internal/models'; import {useSetAtom} from "jotai"; import { InlineCode } from '../components/core/InlineCode'; diff --git a/src-web/hooks/useDeleteAnyHttpRequest.tsx b/src-web/hooks/useDeleteAnyHttpRequest.tsx index 1d37de54..671a55af 100644 --- a/src-web/hooks/useDeleteAnyHttpRequest.tsx +++ b/src-web/hooks/useDeleteAnyHttpRequest.tsx @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { HttpRequest } from '@yaakapp-internal/models'; import { useSetAtom } from 'jotai'; import { InlineCode } from '../components/core/InlineCode'; diff --git a/src-web/hooks/useDeleteCookieJar.tsx b/src-web/hooks/useDeleteCookieJar.tsx index 45ad397f..3ea374ea 100644 --- a/src-web/hooks/useDeleteCookieJar.tsx +++ b/src-web/hooks/useDeleteCookieJar.tsx @@ -1,12 +1,12 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { CookieJar } from '@yaakapp-internal/models'; -import {useSetAtom} from "jotai"; +import { useSetAtom } from 'jotai'; import { InlineCode } from '../components/core/InlineCode'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; import { useConfirm } from './useConfirm'; -import {cookieJarsAtom} from "./useCookieJars"; -import {removeModelById} from "./useSyncModelStores"; +import { cookieJarsAtom } from './useCookieJars'; +import { removeModelById } from './useSyncModelStores'; export function useDeleteCookieJar(cookieJar: CookieJar | null) { const confirm = useConfirm(); @@ -33,6 +33,6 @@ export function useDeleteCookieJar(cookieJar: CookieJar | null) { if (cookieJar == null) return; setCookieJars(removeModelById(cookieJar)); - } + }, }); } diff --git a/src-web/hooks/useDeleteEnvironment.tsx b/src-web/hooks/useDeleteEnvironment.tsx index ea32e637..f11b7df3 100644 --- a/src-web/hooks/useDeleteEnvironment.tsx +++ b/src-web/hooks/useDeleteEnvironment.tsx @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { Environment } from '@yaakapp-internal/models'; import {useSetAtom} from "jotai"; import { InlineCode } from '../components/core/InlineCode'; diff --git a/src-web/hooks/useDeleteFolder.tsx b/src-web/hooks/useDeleteFolder.tsx index 550a4dbb..058a1a4b 100644 --- a/src-web/hooks/useDeleteFolder.tsx +++ b/src-web/hooks/useDeleteFolder.tsx @@ -1,4 +1,3 @@ -import { useMutation } from '@tanstack/react-query'; import type { Folder } from '@yaakapp-internal/models'; import { useSetAtom } from 'jotai'; import { InlineCode } from '../components/core/InlineCode'; @@ -8,6 +7,7 @@ import { invokeCmd } from '../lib/tauri'; import { useConfirm } from './useConfirm'; import { foldersAtom } from './useFolders'; import { removeModelById } from './useSyncModelStores'; +import { useMutation } from './useMutation'; export function useDeleteFolder(id: string | null) { const confirm = useConfirm(); diff --git a/src-web/hooks/useDeleteGrpcConnection.ts b/src-web/hooks/useDeleteGrpcConnection.ts index 7145cc7f..be54ec1a 100644 --- a/src-web/hooks/useDeleteGrpcConnection.ts +++ b/src-web/hooks/useDeleteGrpcConnection.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { GrpcConnection } from '@yaakapp-internal/models'; import {useSetAtom} from "jotai"; import { trackEvent } from '../lib/analytics'; diff --git a/src-web/hooks/useDeleteGrpcConnections.ts b/src-web/hooks/useDeleteGrpcConnections.ts index b12be4f2..d95de704 100644 --- a/src-web/hooks/useDeleteGrpcConnections.ts +++ b/src-web/hooks/useDeleteGrpcConnections.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import { useSetAtom } from 'jotai'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; diff --git a/src-web/hooks/useDeleteHttpResponse.ts b/src-web/hooks/useDeleteHttpResponse.ts index 5d7fe641..a25933ca 100644 --- a/src-web/hooks/useDeleteHttpResponse.ts +++ b/src-web/hooks/useDeleteHttpResponse.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { HttpResponse } from '@yaakapp-internal/models'; import {useSetAtom} from "jotai"; import { trackEvent } from '../lib/analytics'; diff --git a/src-web/hooks/useDeleteHttpResponses.ts b/src-web/hooks/useDeleteHttpResponses.ts index 362a49db..98dbd85f 100644 --- a/src-web/hooks/useDeleteHttpResponses.ts +++ b/src-web/hooks/useDeleteHttpResponses.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import { useSetAtom } from 'jotai'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; diff --git a/src-web/hooks/useDeleteRequest.tsx b/src-web/hooks/useDeleteRequest.tsx index d1ea9d64..ad60af28 100644 --- a/src-web/hooks/useDeleteRequest.tsx +++ b/src-web/hooks/useDeleteRequest.tsx @@ -1,6 +1,6 @@ -import { useMutation } from '@tanstack/react-query'; import { useDeleteAnyGrpcRequest } from './useDeleteAnyGrpcRequest'; import { useDeleteAnyHttpRequest } from './useDeleteAnyHttpRequest'; +import { useMutation } from './useMutation'; export function useDeleteRequest(id: string | null) { const deleteAnyHttpRequest = useDeleteAnyHttpRequest(); diff --git a/src-web/hooks/useDeleteSendHistory.tsx b/src-web/hooks/useDeleteSendHistory.tsx index ec9973c3..1d104c10 100644 --- a/src-web/hooks/useDeleteSendHistory.tsx +++ b/src-web/hooks/useDeleteSendHistory.tsx @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import { useSetAtom } from 'jotai/index'; import { count } from '../lib/pluralize'; import { invokeCmd } from '../lib/tauri'; diff --git a/src-web/hooks/useDeleteWorkspace.tsx b/src-web/hooks/useDeleteWorkspace.tsx index 701b759a..8243e086 100644 --- a/src-web/hooks/useDeleteWorkspace.tsx +++ b/src-web/hooks/useDeleteWorkspace.tsx @@ -1,18 +1,18 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { Workspace } from '@yaakapp-internal/models'; -import {useSetAtom} from "jotai"; +import { useSetAtom } from 'jotai'; import { InlineCode } from '../components/core/InlineCode'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; +import { router } from '../main'; +import { Route } from '../routes/workspaces'; import { useActiveWorkspace } from './useActiveWorkspace'; -import { useAppRoutes } from './useAppRoutes'; import { useConfirm } from './useConfirm'; -import {removeModelById} from "./useSyncModelStores"; -import {workspacesAtom} from "./useWorkspaces"; +import { removeModelById } from './useSyncModelStores'; +import { workspacesAtom } from './useWorkspaces'; export function useDeleteWorkspace(workspace: Workspace | null) { const activeWorkspace = useActiveWorkspace(); - const routes = useAppRoutes(); const confirm = useConfirm(); const setWorkspaces = useSetAtom(workspacesAtom); @@ -41,7 +41,7 @@ export function useDeleteWorkspace(workspace: Workspace | null) { const { id: workspaceId } = workspace; if (workspaceId === activeWorkspace?.id) { - routes.navigate('workspaces'); + router.navigate({ to: Route.fullPath }); } }, }); diff --git a/src-web/hooks/useDuplicateFolder.ts b/src-web/hooks/useDuplicateFolder.ts index 31facdd0..5df8399c 100644 --- a/src-web/hooks/useDuplicateFolder.ts +++ b/src-web/hooks/useDuplicateFolder.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; diff --git a/src-web/hooks/useDuplicateGrpcRequest.ts b/src-web/hooks/useDuplicateGrpcRequest.ts index 461f271c..26b974bb 100644 --- a/src-web/hooks/useDuplicateGrpcRequest.ts +++ b/src-web/hooks/useDuplicateGrpcRequest.ts @@ -1,11 +1,9 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { GrpcRequest } from '@yaakapp-internal/models'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; -import {useActiveCookieJar} from "./useActiveCookieJar"; -import { useActiveEnvironment } from './useActiveEnvironment'; -import { useActiveWorkspace } from './useActiveWorkspace'; -import { useAppRoutes } from './useAppRoutes'; +import { router } from '../main'; +import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId'; import { getGrpcProtoFiles, setGrpcProtoFiles } from './useGrpcProtoFiles'; export function useDuplicateGrpcRequest({ @@ -15,11 +13,6 @@ export function useDuplicateGrpcRequest({ id: string | null; navigateAfter: boolean; }) { - const activeWorkspace = useActiveWorkspace(); - const [activeEnvironment] = useActiveEnvironment(); - const [activeCookieJar] = useActiveCookieJar(); - const routes = useAppRoutes(); - return useMutation({ mutationKey: ['duplicate_grpc_request', id], mutationFn: async () => { @@ -34,12 +27,11 @@ export function useDuplicateGrpcRequest({ const protoFiles = await getGrpcProtoFiles(id); await setGrpcProtoFiles(request.id, protoFiles); - if (navigateAfter && activeWorkspace !== null) { - routes.navigate('request', { - workspaceId: activeWorkspace.id, - requestId: request.id, - environmentId: activeEnvironment?.id ?? null, - cookieJarId: activeCookieJar?.id ?? null, + if (navigateAfter) { + await router.navigate({ + to: Route.fullPath, + params: { workspaceId: request.workspaceId, requestId: request.id }, + search: (prev) => ({ ...prev }), }); } }, diff --git a/src-web/hooks/useDuplicateHttpRequest.ts b/src-web/hooks/useDuplicateHttpRequest.ts index 1032ef18..c3aa9923 100644 --- a/src-web/hooks/useDuplicateHttpRequest.ts +++ b/src-web/hooks/useDuplicateHttpRequest.ts @@ -1,11 +1,9 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { HttpRequest } from '@yaakapp-internal/models'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; -import {useActiveCookieJar} from "./useActiveCookieJar"; -import { useActiveEnvironment } from './useActiveEnvironment'; -import { useActiveWorkspace } from './useActiveWorkspace'; -import { useAppRoutes } from './useAppRoutes'; +import { router } from '../main'; +import { Route } from '../routes/workspaces/$workspaceId/requests/$requestId'; export function useDuplicateHttpRequest({ id, @@ -14,10 +12,6 @@ export function useDuplicateHttpRequest({ id: string | null; navigateAfter: boolean; }) { - const activeWorkspace = useActiveWorkspace(); - const [activeEnvironment] = useActiveEnvironment(); - const [activeCookieJar] = useActiveCookieJar(); - const routes = useAppRoutes(); return useMutation({ mutationKey: ['duplicate_http_request', id], mutationFn: async () => { @@ -26,12 +20,14 @@ export function useDuplicateHttpRequest({ }, onSettled: () => trackEvent('http_request', 'duplicate'), onSuccess: async (request) => { - if (navigateAfter && activeWorkspace !== null) { - routes.navigate('request', { - workspaceId: activeWorkspace.id, - requestId: request.id, - environmentId: activeEnvironment?.id ?? null, - cookieJarId: activeCookieJar?.id ?? null, + if (navigateAfter) { + router.navigate({ + to: Route.fullPath, + params: { + workspaceId: request.workspaceId, + requestId: request.id, + }, + search: (prev) => ({ ...prev }), }); } }, diff --git a/src-web/hooks/useExportData.tsx b/src-web/hooks/useExportData.tsx index b1d4f294..3e3e230d 100644 --- a/src-web/hooks/useExportData.tsx +++ b/src-web/hooks/useExportData.tsx @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import { useDialog } from '../components/DialogContext'; import { ExportDataDialog } from '../components/ExportDataDialog'; import { useActiveWorkspace } from './useActiveWorkspace'; diff --git a/src-web/hooks/useHttpRequestActions.ts b/src-web/hooks/useHttpRequestActions.ts index 0c4bfcb3..bb4ab85d 100644 --- a/src-web/hooks/useHttpRequestActions.ts +++ b/src-web/hooks/useHttpRequestActions.ts @@ -7,6 +7,7 @@ import type { } from '@yaakapp-internal/plugin'; import { invokeCmd } from '../lib/tauri'; import { usePluginsKey } from './usePlugins'; +import { useMemo } from 'react'; export type CallableHttpRequestAction = Pick & { call: (httpRequest: HttpRequest) => Promise; @@ -15,32 +16,35 @@ export type CallableHttpRequestAction = Pick({ queryKey: ['http_request_actions', pluginsKey], queryFn: async () => { - const responses = (await invokeCmd( + const responses = await invokeCmd( 'cmd_http_request_actions', - )) as GetHttpRequestActionsResponse[]; - return responses; + ); + + return responses.flatMap((r) => + r.actions.map((a) => ({ + key: a.key, + label: a.label, + icon: a.icon, + call: async (httpRequest: HttpRequest) => { + const payload: CallHttpRequestActionRequest = { + key: a.key, + pluginRefId: r.pluginRefId, + args: { httpRequest }, + }; + await invokeCmd('cmd_call_http_request_action', { req: payload }); + }, + })), + ); }, }); - const actions: CallableHttpRequestAction[] = - httpRequestActions.data?.flatMap((r) => - r.actions.map((a) => ({ - key: a.key, - label: a.label, - icon: a.icon, - call: async (httpRequest: HttpRequest) => { - const payload: CallHttpRequestActionRequest = { - key: a.key, - pluginRefId: r.pluginRefId, - args: { httpRequest }, - }; - await invokeCmd('cmd_call_http_request_action', { req: payload }); - }, - })), - ) ?? []; + const actions = useMemo(() => { + return actionsResult.data ?? []; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(actionsResult.data)]); return actions; } diff --git a/src-web/hooks/useImportCurl.ts b/src-web/hooks/useImportCurl.ts index c5afe330..a73f68c9 100644 --- a/src-web/hooks/useImportCurl.ts +++ b/src-web/hooks/useImportCurl.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { HttpRequest } from '@yaakapp-internal/models'; import { useToast } from '../components/ToastContext'; import { invokeCmd } from '../lib/tauri'; diff --git a/src-web/hooks/useImportData.tsx b/src-web/hooks/useImportData.tsx index b1210475..2d548fd8 100644 --- a/src-web/hooks/useImportData.tsx +++ b/src-web/hooks/useImportData.tsx @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { Environment, Folder, @@ -13,12 +13,12 @@ import { useDialog } from '../components/DialogContext'; import { ImportDataDialog } from '../components/ImportDataDialog'; import { count } from '../lib/pluralize'; import { invokeCmd } from '../lib/tauri'; +import { Route } from '../routes/workspaces/$workspaceId'; import { useActiveWorkspace } from './useActiveWorkspace'; import { useAlert } from './useAlert'; -import { useAppRoutes } from './useAppRoutes'; +import { router } from '../main'; export function useImportData() { - const routes = useAppRoutes(); const dialog = useDialog(); const alert = useAlert(); const activeWorkspace = useActiveWorkspace(); @@ -64,10 +64,11 @@ export function useImportData() { }); if (importedWorkspace != null) { - routes.navigate('workspace', { - workspaceId: importedWorkspace.id, - environmentId: imported.environments[0]?.id ?? null, - cookieJarId: null, + const environmentId = imported.environments[0]?.id ?? null; + router.navigate({ + to: Route.fullPath, + params: { workspaceId: importedWorkspace.id }, + search: { environmentId }, }); } diff --git a/src-web/hooks/useImportQuerystring.ts b/src-web/hooks/useImportQuerystring.ts index d6283bf1..ef4afd39 100644 --- a/src-web/hooks/useImportQuerystring.ts +++ b/src-web/hooks/useImportQuerystring.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { HttpUrlParameter } from '@yaakapp-internal/models'; import { useToast } from '../components/ToastContext'; import { pluralize } from '../lib/pluralize'; diff --git a/src-web/hooks/useInstallPlugin.ts b/src-web/hooks/useInstallPlugin.ts index 31d030a8..6bbb170d 100644 --- a/src-web/hooks/useInstallPlugin.ts +++ b/src-web/hooks/useInstallPlugin.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; diff --git a/src-web/hooks/useKeyValue.ts b/src-web/hooks/useKeyValue.ts index b3f28107..5670d53f 100644 --- a/src-web/hooks/useKeyValue.ts +++ b/src-web/hooks/useKeyValue.ts @@ -49,7 +49,7 @@ export function useKeyValue } }, // eslint-disable-next-line react-hooks/exhaustive-deps - [fallback, key, namespace], + [typeof key === 'string' ? key : key.join('::'), namespace], ); const reset = useCallback(async () => mutate.mutateAsync(fallback), [mutate, fallback]); diff --git a/src-web/hooks/useMoveToWorkspace.tsx b/src-web/hooks/useMoveToWorkspace.tsx index 792706fc..16e090e0 100644 --- a/src-web/hooks/useMoveToWorkspace.tsx +++ b/src-web/hooks/useMoveToWorkspace.tsx @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import React from 'react'; import { useDialog } from '../components/DialogContext'; import { MoveToWorkspaceDialog } from '../components/MoveToWorkspaceDialog'; diff --git a/src-web/hooks/useMutation.ts b/src-web/hooks/useMutation.ts new file mode 100644 index 00000000..fde67993 --- /dev/null +++ b/src-web/hooks/useMutation.ts @@ -0,0 +1,42 @@ +import type { MutationKey } from '@tanstack/react-query'; +import { useCallback } from 'react'; + +export function useMutation({ + mutationKey, + mutationFn, + onSuccess, + onSettled, +}: { + mutationKey: MutationKey; + mutationFn: (vars: TVariables) => Promise; + onSettled?: () => void; + onSuccess?: (data: TData) => void; +}) { + const mutateAsync = useCallback( + async (variables: TVariables) => { + try { + const data = await mutationFn(variables); + onSuccess?.(data); + } catch (err: unknown) { + const e = err as TError; + console.log('MUTATION FAILED', mutationKey, e); + } finally { + onSettled?.(); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + mutationKey, + ); + + const mutate = useCallback( + (variables: TVariables) => { + setTimeout(() => mutateAsync(variables)); + }, + [mutateAsync], + ); + + return { + mutate, + mutateAsync, + }; +} diff --git a/src-web/hooks/useOpenSettings.tsx b/src-web/hooks/useOpenSettings.tsx index 7d61db33..e367683f 100644 --- a/src-web/hooks/useOpenSettings.tsx +++ b/src-web/hooks/useOpenSettings.tsx @@ -1,21 +1,27 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import { SettingsTab } from '../components/Settings/Settings'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; +import { router } from '../main'; +import { Route as SettingsRoute } from '../routes/workspaces/settings'; import { useActiveWorkspace } from './useActiveWorkspace'; -import { useAppRoutes } from './useAppRoutes'; export function useOpenSettings(tab = SettingsTab.General) { - const routes = useAppRoutes(); const workspace = useActiveWorkspace(); + return useMutation({ mutationKey: ['open_settings'], mutationFn: async () => { if (workspace == null) return; trackEvent('dialog', 'show', { id: 'settings', tab: `${tab}` }); + const location = router.buildLocation({ + to: SettingsRoute.fullPath, + params: { workspaceId: workspace.id }, + search: { tab }, + }); await invokeCmd('cmd_new_child_window', { - url: routes.paths.workspaceSettings({ workspaceId: workspace.id, tab }), + url: location, label: 'settings', title: 'Yaak Settings', innerSize: [600, 550], diff --git a/src-web/hooks/useOpenWorkspace.ts b/src-web/hooks/useOpenWorkspace.ts index 8472605a..5772a225 100644 --- a/src-web/hooks/useOpenWorkspace.ts +++ b/src-web/hooks/useOpenWorkspace.ts @@ -1,13 +1,13 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import { invokeCmd } from '../lib/tauri'; -import { useAppRoutes } from './useAppRoutes'; +import { router } from '../main'; +import { Route as WorkspaceRoute } from '../routes/workspaces/$workspaceId'; +import { Route as RequestRoute } from '../routes/workspaces/$workspaceId/requests/$requestId'; import { getRecentCookieJars } from './useRecentCookieJars'; import { getRecentEnvironments } from './useRecentEnvironments'; import { getRecentRequests } from './useRecentRequests'; export function useOpenWorkspace() { - const routes = useAppRoutes(); - return useMutation({ mutationKey: ['open_workspace'], mutationFn: async ({ @@ -17,22 +17,25 @@ export function useOpenWorkspace() { workspaceId: string; inNewWindow: boolean; }) => { - const environmentId = (await getRecentEnvironments(workspaceId))[0] ?? null; - const requestId = (await getRecentRequests(workspaceId))[0] ?? null; - const cookieJarId = (await getRecentCookieJars(workspaceId))[0] ?? null; - const baseArgs = { workspaceId, environmentId, cookieJarId } as const; + const environmentId = (await getRecentEnvironments(workspaceId))[0] ?? undefined; + const requestId = (await getRecentRequests(workspaceId))[0] ?? undefined; + const cookieJarId = (await getRecentCookieJars(workspaceId))[0] ?? undefined; + const search = { environmentId, cookieJarId }; + if (inNewWindow) { - const path = - requestId != null - ? routes.paths.request({ ...baseArgs, requestId }) - : routes.paths.workspace({ ...baseArgs }); - await invokeCmd('cmd_new_main_window', { url: path }); + const location = router.buildLocation({ + to: WorkspaceRoute.fullPath, + params: { workspaceId }, + search, + }); + await invokeCmd('cmd_new_main_window', { url: location }); + return; + } + + if (requestId != null) { + router.navigate({ to: RequestRoute.fullPath, params: { workspaceId, requestId }, search }); } else { - if (requestId != null) { - routes.navigate('request', { ...baseArgs, requestId }); - } else { - routes.navigate('workspace', { ...baseArgs }); - } + router.navigate({ to: WorkspaceRoute.fullPath, params: { workspaceId }, search }); } }, }); diff --git a/src-web/hooks/usePlugins.ts b/src-web/hooks/usePlugins.ts index 68a790f1..c519149a 100644 --- a/src-web/hooks/usePlugins.ts +++ b/src-web/hooks/usePlugins.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { Plugin } from '@yaakapp-internal/models'; import { atom, useAtomValue, useSetAtom } from 'jotai'; import { minPromiseMillis } from '../lib/minPromiseMillis'; diff --git a/src-web/hooks/useRenameRequest.tsx b/src-web/hooks/useRenameRequest.tsx index 4145df16..e57804d9 100644 --- a/src-web/hooks/useRenameRequest.tsx +++ b/src-web/hooks/useRenameRequest.tsx @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { GrpcRequest, HttpRequest } from '@yaakapp-internal/models'; import { InlineCode } from '../components/core/InlineCode'; import { usePrompt } from './usePrompt'; diff --git a/src-web/hooks/useSaveResponse.tsx b/src-web/hooks/useSaveResponse.tsx index 3c5bcf15..de794b16 100644 --- a/src-web/hooks/useSaveResponse.tsx +++ b/src-web/hooks/useSaveResponse.tsx @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import { save } from '@tauri-apps/plugin-dialog'; import mime from 'mime'; import slugify from 'slugify'; diff --git a/src-web/hooks/useSendAnyHttpRequest.ts b/src-web/hooks/useSendAnyHttpRequest.ts index 1bbe2bf5..8d79cb5b 100644 --- a/src-web/hooks/useSendAnyHttpRequest.ts +++ b/src-web/hooks/useSendAnyHttpRequest.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { HttpResponse } from '@yaakapp-internal/models'; import { trackEvent } from '../lib/analytics'; import { getHttpRequest } from '../lib/store'; diff --git a/src-web/hooks/useSendManyRequests.ts b/src-web/hooks/useSendManyRequests.ts index 44d0ba7d..d451ccb8 100644 --- a/src-web/hooks/useSendManyRequests.ts +++ b/src-web/hooks/useSendManyRequests.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import { useSendAnyHttpRequest } from './useSendAnyHttpRequest'; export function useSendManyRequests() { diff --git a/src-web/hooks/useSyncWorkspaceRequestTitle.ts b/src-web/hooks/useSyncWorkspaceRequestTitle.ts index 79e64e1f..5faec9e4 100644 --- a/src-web/hooks/useSyncWorkspaceRequestTitle.ts +++ b/src-web/hooks/useSyncWorkspaceRequestTitle.ts @@ -1,15 +1,14 @@ +import { emit } from '@tauri-apps/api/event'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; import { useEffect } from 'react'; import { fallbackRequestName } from '../lib/fallbackRequestName'; import { useActiveEnvironment } from './useActiveEnvironment'; -import { useActiveRequest } from './useActiveRequest'; +import { getActiveRequest } from './useActiveRequest'; import { useActiveWorkspace } from './useActiveWorkspace'; import { useAppInfo } from './useAppInfo'; import { useOsInfo } from './useOsInfo'; -import { emit } from '@tauri-apps/api/event'; export function useSyncWorkspaceRequestTitle() { - const activeRequest = useActiveRequest(); const activeWorkspace = useActiveWorkspace(); const [activeEnvironment] = useActiveEnvironment(); const osInfo = useOsInfo(); @@ -24,6 +23,7 @@ export function useSyncWorkspaceRequestTitle() { if (activeEnvironment) { newTitle += ` [${activeEnvironment.name}]`; } + const activeRequest = getActiveRequest(); if (activeRequest) { // eslint-disable-next-line @typescript-eslint/no-unused-vars newTitle += ` › ${fallbackRequestName(activeRequest)}`; @@ -40,5 +40,5 @@ export function useSyncWorkspaceRequestTitle() { } else { emit('yaak_title_changed', newTitle).catch(console.error); } - }, [activeEnvironment, activeRequest, activeWorkspace, appInfo.isDev, osInfo.osType]); + }, [activeEnvironment, activeWorkspace, appInfo.isDev, osInfo.osType]); } diff --git a/src-web/hooks/useUninstallPlugin.ts b/src-web/hooks/useUninstallPlugin.ts index 2b6ad6e0..65689bd3 100644 --- a/src-web/hooks/useUninstallPlugin.ts +++ b/src-web/hooks/useUninstallPlugin.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { Plugin } from '@yaakapp-internal/models'; import { trackEvent } from '../lib/analytics'; import { invokeCmd } from '../lib/tauri'; diff --git a/src-web/hooks/useUpdateAnyFolder.ts b/src-web/hooks/useUpdateAnyFolder.ts index 1abe625c..8b186e36 100644 --- a/src-web/hooks/useUpdateAnyFolder.ts +++ b/src-web/hooks/useUpdateAnyFolder.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { Folder } from '@yaakapp-internal/models'; import {useSetAtom} from "jotai/index"; import { getFolder } from '../lib/store'; diff --git a/src-web/hooks/useUpdateAnyGrpcRequest.ts b/src-web/hooks/useUpdateAnyGrpcRequest.ts index a6479b56..216383e0 100644 --- a/src-web/hooks/useUpdateAnyGrpcRequest.ts +++ b/src-web/hooks/useUpdateAnyGrpcRequest.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { GrpcRequest } from '@yaakapp-internal/models'; import { useSetAtom } from 'jotai/index'; import { getGrpcRequest } from '../lib/store'; diff --git a/src-web/hooks/useUpdateAnyHttpRequest.ts b/src-web/hooks/useUpdateAnyHttpRequest.ts index 3f4b6f40..2775ec18 100644 --- a/src-web/hooks/useUpdateAnyHttpRequest.ts +++ b/src-web/hooks/useUpdateAnyHttpRequest.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { HttpRequest } from '@yaakapp-internal/models'; import {useSetAtom} from "jotai/index"; import { getHttpRequest } from '../lib/store'; diff --git a/src-web/hooks/useUpdateCookieJar.ts b/src-web/hooks/useUpdateCookieJar.ts index c37ebfd8..d5baded5 100644 --- a/src-web/hooks/useUpdateCookieJar.ts +++ b/src-web/hooks/useUpdateCookieJar.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { CookieJar } from '@yaakapp-internal/models'; import { useSetAtom } from 'jotai/index'; import { getCookieJar } from '../lib/store'; diff --git a/src-web/hooks/useUpdateEnvironment.ts b/src-web/hooks/useUpdateEnvironment.ts index 3ce2981c..f953e15d 100644 --- a/src-web/hooks/useUpdateEnvironment.ts +++ b/src-web/hooks/useUpdateEnvironment.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { Environment } from '@yaakapp-internal/models'; import { useSetAtom } from 'jotai/index'; import { getEnvironment } from '../lib/store'; diff --git a/src-web/hooks/useUpdateSettings.ts b/src-web/hooks/useUpdateSettings.ts index f43b6260..bb9fe6dd 100644 --- a/src-web/hooks/useUpdateSettings.ts +++ b/src-web/hooks/useUpdateSettings.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { Settings } from '@yaakapp-internal/models'; import { useSetAtom } from 'jotai'; import { getSettings } from '../lib/store'; diff --git a/src-web/hooks/useUpdateWorkspace.ts b/src-web/hooks/useUpdateWorkspace.ts index de9b3777..eae1cc5e 100644 --- a/src-web/hooks/useUpdateWorkspace.ts +++ b/src-web/hooks/useUpdateWorkspace.ts @@ -1,4 +1,4 @@ -import { useMutation } from '@tanstack/react-query'; +import { useMutation } from './useMutation'; import type { Workspace } from '@yaakapp-internal/models'; import {useSetAtom} from "jotai/index"; import { getWorkspace } from '../lib/store'; diff --git a/src-web/main.tsx b/src-web/main.tsx index b78bd3fd..b46c4b2d 100644 --- a/src-web/main.tsx +++ b/src-web/main.tsx @@ -1,9 +1,10 @@ +import './main.css'; +import { createRouter, RouterProvider } from '@tanstack/react-router'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; import { type } from '@tauri-apps/plugin-os'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; -import { App } from './components/App'; -import './main.css'; +import { routeTree } from './routeTree.gen'; import('react-pdf').then(({ pdfjs }) => { pdfjs.GlobalWorkerOptions.workerSrc = new URL( @@ -24,8 +25,20 @@ window.addEventListener('keydown', (e) => { if (e.key === 'Backspace' && e.target === document.body) e.preventDefault(); }); +// Create a new router instance +export const router = createRouter({ + routeTree, +}); + +// Register the router instance for type safety +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} + createRoot(document.getElementById('root') as HTMLElement).render( - + , ); diff --git a/src-web/package.json b/src-web/package.json index 3ebdbd55..0eeb04d9 100644 --- a/src-web/package.json +++ b/src-web/package.json @@ -20,8 +20,9 @@ "@lezer/lr": "^1.3.3", "@react-hook/size": "^2.1.2", "@tailwindcss/container-queries": "^0.1.1", - "@tanstack/react-query": "^5.59.16", - "@tanstack/react-virtual": "^3.10.8", + "@tanstack/react-query": "^5.62.8", + "@tanstack/react-router": "^1.91.3", + "@tanstack/react-virtual": "^3.11.2", "@tauri-apps/api": "^2.0.1", "@tauri-apps/plugin-clipboard-manager": "^2.0.0", "@tauri-apps/plugin-dialog": "^2.0.0", @@ -40,6 +41,7 @@ "format-graphql": "^1.5.0", "framer-motion": "^11.5.4", "fuzzbunny": "^1.0.1", + "history": "^5.3.0", "jotai": "^2.9.3", "lucide-react": "^0.439.0", "mime": "^4.0.4", @@ -63,7 +65,9 @@ "devDependencies": { "@lezer/generator": "^1.7.1", "@tailwindcss/nesting": "^0.0.0-insiders.565cd3e", - "@tanstack/react-query-devtools": "^5.55.4", + "@tanstack/react-query-devtools": "^5.62.8", + "@tanstack/router-devtools": "^1.91.3", + "@tanstack/router-plugin": "^1.91.1", "@types/node": "^22.5.4", "@types/papaparse": "^5.3.14", "@types/parse-color": "^1.0.3", diff --git a/src-web/routeTree.gen.ts b/src-web/routeTree.gen.ts new file mode 100644 index 00000000..50ebd20c --- /dev/null +++ b/src-web/routeTree.gen.ts @@ -0,0 +1,200 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +// Import Routes + +import { Route as rootRoute } from './routes/__root' +import { Route as IndexImport } from './routes/index' +import { Route as WorkspacesIndexImport } from './routes/workspaces/index' +import { Route as WorkspacesSettingsImport } from './routes/workspaces/settings' +import { Route as WorkspacesWorkspaceIdIndexImport } from './routes/workspaces/$workspaceId/index' +import { Route as WorkspacesWorkspaceIdRequestsRequestIdImport } from './routes/workspaces/$workspaceId/requests/$requestId' + +// Create/Update Routes + +const IndexRoute = IndexImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRoute, +} as any) + +const WorkspacesIndexRoute = WorkspacesIndexImport.update({ + id: '/workspaces/', + path: '/workspaces/', + getParentRoute: () => rootRoute, +} as any) + +const WorkspacesSettingsRoute = WorkspacesSettingsImport.update({ + id: '/workspaces/settings', + path: '/workspaces/settings', + getParentRoute: () => rootRoute, +} as any) + +const WorkspacesWorkspaceIdIndexRoute = WorkspacesWorkspaceIdIndexImport.update( + { + id: '/workspaces/$workspaceId/', + path: '/workspaces/$workspaceId/', + getParentRoute: () => rootRoute, + } as any, +) + +const WorkspacesWorkspaceIdRequestsRequestIdRoute = + WorkspacesWorkspaceIdRequestsRequestIdImport.update({ + id: '/workspaces/$workspaceId/requests/$requestId', + path: '/workspaces/$workspaceId/requests/$requestId', + getParentRoute: () => rootRoute, + } as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexImport + parentRoute: typeof rootRoute + } + '/workspaces/settings': { + id: '/workspaces/settings' + path: '/workspaces/settings' + fullPath: '/workspaces/settings' + preLoaderRoute: typeof WorkspacesSettingsImport + parentRoute: typeof rootRoute + } + '/workspaces/': { + id: '/workspaces/' + path: '/workspaces' + fullPath: '/workspaces' + preLoaderRoute: typeof WorkspacesIndexImport + parentRoute: typeof rootRoute + } + '/workspaces/$workspaceId/': { + id: '/workspaces/$workspaceId/' + path: '/workspaces/$workspaceId' + fullPath: '/workspaces/$workspaceId' + preLoaderRoute: typeof WorkspacesWorkspaceIdIndexImport + parentRoute: typeof rootRoute + } + '/workspaces/$workspaceId/requests/$requestId': { + id: '/workspaces/$workspaceId/requests/$requestId' + path: '/workspaces/$workspaceId/requests/$requestId' + fullPath: '/workspaces/$workspaceId/requests/$requestId' + preLoaderRoute: typeof WorkspacesWorkspaceIdRequestsRequestIdImport + parentRoute: typeof rootRoute + } + } +} + +// Create and export the route tree + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/workspaces/settings': typeof WorkspacesSettingsRoute + '/workspaces': typeof WorkspacesIndexRoute + '/workspaces/$workspaceId': typeof WorkspacesWorkspaceIdIndexRoute + '/workspaces/$workspaceId/requests/$requestId': typeof WorkspacesWorkspaceIdRequestsRequestIdRoute +} + +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/workspaces/settings': typeof WorkspacesSettingsRoute + '/workspaces': typeof WorkspacesIndexRoute + '/workspaces/$workspaceId': typeof WorkspacesWorkspaceIdIndexRoute + '/workspaces/$workspaceId/requests/$requestId': typeof WorkspacesWorkspaceIdRequestsRequestIdRoute +} + +export interface FileRoutesById { + __root__: typeof rootRoute + '/': typeof IndexRoute + '/workspaces/settings': typeof WorkspacesSettingsRoute + '/workspaces/': typeof WorkspacesIndexRoute + '/workspaces/$workspaceId/': typeof WorkspacesWorkspaceIdIndexRoute + '/workspaces/$workspaceId/requests/$requestId': typeof WorkspacesWorkspaceIdRequestsRequestIdRoute +} + +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/workspaces/settings' + | '/workspaces' + | '/workspaces/$workspaceId' + | '/workspaces/$workspaceId/requests/$requestId' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/workspaces/settings' + | '/workspaces' + | '/workspaces/$workspaceId' + | '/workspaces/$workspaceId/requests/$requestId' + id: + | '__root__' + | '/' + | '/workspaces/settings' + | '/workspaces/' + | '/workspaces/$workspaceId/' + | '/workspaces/$workspaceId/requests/$requestId' + fileRoutesById: FileRoutesById +} + +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + WorkspacesSettingsRoute: typeof WorkspacesSettingsRoute + WorkspacesIndexRoute: typeof WorkspacesIndexRoute + WorkspacesWorkspaceIdIndexRoute: typeof WorkspacesWorkspaceIdIndexRoute + WorkspacesWorkspaceIdRequestsRequestIdRoute: typeof WorkspacesWorkspaceIdRequestsRequestIdRoute +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + WorkspacesSettingsRoute: WorkspacesSettingsRoute, + WorkspacesIndexRoute: WorkspacesIndexRoute, + WorkspacesWorkspaceIdIndexRoute: WorkspacesWorkspaceIdIndexRoute, + WorkspacesWorkspaceIdRequestsRequestIdRoute: + WorkspacesWorkspaceIdRequestsRequestIdRoute, +} + +export const routeTree = rootRoute + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/", + "/workspaces/settings", + "/workspaces/", + "/workspaces/$workspaceId/", + "/workspaces/$workspaceId/requests/$requestId" + ] + }, + "/": { + "filePath": "index.tsx" + }, + "/workspaces/settings": { + "filePath": "workspaces/settings.tsx" + }, + "/workspaces/": { + "filePath": "workspaces/index.tsx" + }, + "/workspaces/$workspaceId/": { + "filePath": "workspaces/$workspaceId/index.tsx" + }, + "/workspaces/$workspaceId/requests/$requestId": { + "filePath": "workspaces/$workspaceId/requests/$requestId.tsx" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/src-web/routes/__root.tsx b/src-web/routes/__root.tsx new file mode 100644 index 00000000..777299f3 --- /dev/null +++ b/src-web/routes/__root.tsx @@ -0,0 +1,91 @@ +import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { createRootRoute, Outlet } from '@tanstack/react-router'; +import classNames from 'classnames'; +import { MotionConfig } from 'framer-motion'; +import { createStore, Provider as JotaiProvider } from 'jotai'; +import React, { Suspense } from 'react'; +import { DndProvider } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import { HelmetProvider } from 'react-helmet-async'; +import { DialogProvider, Dialogs } from '../components/DialogContext'; +import { GlobalHooks } from '../components/GlobalHooks'; +import { ToastProvider, Toasts } from '../components/ToastContext'; +import { useOsInfo } from '../hooks/useOsInfo'; + +const queryClient = new QueryClient({ + queryCache: new QueryCache({ + onError: (err, query) => { + console.log('Query client error', { err, query }); + }, + }), + defaultOptions: { + queries: { + retry: false, + networkMode: 'always', + refetchOnWindowFocus: true, + refetchOnReconnect: false, + refetchOnMount: false, // Don't refetch when a hook mounts + }, + }, +}); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const TanStackRouterDevtools = + process.env.NODE_ENV === 'production' + ? () => null // Render nothing in production + : React.lazy(() => + import('@tanstack/router-devtools').then((res) => ({ + default: res.TanStackRouterDevtools, + })), + ); + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const ReactQueryDevtools = + process.env.NODE_ENV === 'production' + ? () => null // Render nothing in production + : React.lazy(() => + import('@tanstack/react-query-devtools').then((res) => ({ + default: res.ReactQueryDevtools, + })), + ); + +export const Route = createRootRoute({ + component: RouteComponent, +}); + +export const jotaiStore = createStore(); + +function RouteComponent() { + const osInfo = useOsInfo(); + return ( + + + + + + + + + + + + + + + + + + + + + {/**/} + {/**/} + + + ); +} diff --git a/src-web/routes/index.tsx b/src-web/routes/index.tsx new file mode 100644 index 00000000..9f89a12a --- /dev/null +++ b/src-web/routes/index.tsx @@ -0,0 +1,10 @@ +import { createFileRoute } from '@tanstack/react-router' +import { RedirectToLatestWorkspace } from '../components/RedirectToLatestWorkspace' + +export const Route = createFileRoute('/')({ + component: RouteComponent, +}) + +function RouteComponent() { + return +} diff --git a/src-web/routes/workspaces/$workspaceId/index.tsx b/src-web/routes/workspaces/$workspaceId/index.tsx new file mode 100644 index 00000000..9435105b --- /dev/null +++ b/src-web/routes/workspaces/$workspaceId/index.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { Workspace } from '../../../components/Workspace'; + +interface WorkspaceSearchSchema { + cookieJarId?: string | null; + environmentId?: string | null; +} + +export const Route = createFileRoute('/workspaces/$workspaceId/')({ + component: RouteComponent, + validateSearch: (search: Record): WorkspaceSearchSchema => ({ + environmentId: search.environment_id as string, + cookieJarId: search.cookie_jar_id as string, + }), +}); + +function RouteComponent() { + return ; +} diff --git a/src-web/routes/workspaces/$workspaceId/requests/$requestId.tsx b/src-web/routes/workspaces/$workspaceId/requests/$requestId.tsx new file mode 100644 index 00000000..f6016c0d --- /dev/null +++ b/src-web/routes/workspaces/$workspaceId/requests/$requestId.tsx @@ -0,0 +1,10 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { Workspace } from '../../../../components/Workspace'; + +export const Route = createFileRoute('/workspaces/$workspaceId/requests/$requestId')({ + component: RouteComponent, +}); + +function RouteComponent() { + return ; +} diff --git a/src-web/routes/workspaces/index.tsx b/src-web/routes/workspaces/index.tsx new file mode 100644 index 00000000..8a96da8b --- /dev/null +++ b/src-web/routes/workspaces/index.tsx @@ -0,0 +1,10 @@ +import { createFileRoute } from '@tanstack/react-router' +import { RedirectToLatestWorkspace } from '../../components/RedirectToLatestWorkspace' + +export const Route = createFileRoute('/workspaces/')({ + component: RouteComponent, +}) + +function RouteComponent() { + return +} diff --git a/src-web/routes/workspaces/settings.tsx b/src-web/routes/workspaces/settings.tsx new file mode 100644 index 00000000..f8628edd --- /dev/null +++ b/src-web/routes/workspaces/settings.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from '@tanstack/react-router' +import Settings, { SettingsTab } from '../../components/Settings/Settings' + +interface SettingsSearchSchema { + tab?: SettingsTab +} + +export const Route = createFileRoute('/workspaces/settings')({ + component: RouteComponent, + validateSearch: (search: Record): SettingsSearchSchema => ({ + tab: (search.tab ?? SettingsTab.General) as SettingsTab, + }), +}) + +function RouteComponent() { + return +} diff --git a/src-web/tsr.config.json b/src-web/tsr.config.json new file mode 100644 index 00000000..7f36ce52 --- /dev/null +++ b/src-web/tsr.config.json @@ -0,0 +1,3 @@ +{ + "autoCodeSplitting": true +} diff --git a/src-web/vite.config.ts b/src-web/vite.config.ts index dd098b97..87c6fa44 100644 --- a/src-web/vite.config.ts +++ b/src-web/vite.config.ts @@ -1,7 +1,8 @@ -import path from 'node:path'; +import { TanStackRouterVite } from '@tanstack/router-plugin/vite'; import react from '@vitejs/plugin-react'; import { internalIpV4 } from 'internal-ip'; import { createRequire } from 'node:module'; +import path from 'node:path'; import { defineConfig, normalizePath } from 'vite'; import { viteStaticCopy } from 'vite-plugin-static-copy'; import svgr from 'vite-plugin-svgr'; @@ -20,6 +21,10 @@ const mobile = !!/android|ios/.exec(process.env.TAURI_ENV_PLATFORM ?? ''); // https://vitejs.dev/config/ export default defineConfig(async () => ({ plugins: [ + TanStackRouterVite({ + routesDirectory: './routes', + generatedRouteTree: './routeTree.gen.ts', + }), svgr(), react(), topLevelAwait(),