mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-17 08:07:46 +01:00
Compare commits
27 Commits
v2024.8.2
...
v2024.9.0-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e90e672d2 | ||
|
|
d73b692edc | ||
|
|
b3adbc1860 | ||
|
|
f7949c9909 | ||
|
|
baa4577601 | ||
|
|
db935f0130 | ||
|
|
d47de9a999 | ||
|
|
82252f920f | ||
|
|
fedf356576 | ||
|
|
3601410fb8 | ||
|
|
f67cecf1b4 | ||
|
|
8619c66ea4 | ||
|
|
230e1f55c2 | ||
|
|
d4ab8897e2 | ||
|
|
184feaa22b | ||
|
|
428aaad877 | ||
|
|
33f1aa29e4 | ||
|
|
002acd05ee | ||
|
|
e6d7f4a928 | ||
|
|
0bfafb284a | ||
|
|
f8b317e94b | ||
|
|
ef0fdb4b16 | ||
|
|
f2f1d9affa | ||
|
|
c73262b037 | ||
|
|
f8936e7b76 | ||
|
|
0d20e0fe29 | ||
|
|
ba626a6b3e |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -81,7 +81,7 @@ jobs:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install yaak CLI
|
||||
run: go install github.com/yaakapp/yaakcli@latest
|
||||
run: go install github.com/yaakapp/cli/cmd/yaakcli@latest
|
||||
|
||||
- name: Run lint
|
||||
run: npm run lint
|
||||
|
||||
173
package-lock.json
generated
173
package-lock.json
generated
@@ -20,20 +20,21 @@
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@tailwindcss/container-queries": "^0.1.0",
|
||||
"@tanstack/react-query": "^5.45.1",
|
||||
"@tauri-apps/api": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-log": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-rc.0",
|
||||
"@yaakapp/api": "^0.1.13",
|
||||
"@tauri-apps/api": "^2.0.0-rc.4",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.0.0-rc.1",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-rc.1",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-rc.2",
|
||||
"@tauri-apps/plugin-log": "^2.0.0-rc.1",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-rc.1",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-rc.1",
|
||||
"@yaakapp/api": "^0.1.17",
|
||||
"buffer": "^6.0.3",
|
||||
"classnames": "^2.3.2",
|
||||
"cm6-graphql": "^0.0.9",
|
||||
"codemirror": "^6.0.1",
|
||||
"codemirror-json-schema": "^0.6.1",
|
||||
"date-fns": "^3.3.1",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"fast-fuzzy": "^1.12.0",
|
||||
"focus-trap-react": "^10.1.1",
|
||||
"format-graphql": "^1.4.0",
|
||||
@@ -58,7 +59,7 @@
|
||||
"devDependencies": {
|
||||
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
|
||||
"@tanstack/react-query-devtools": "^5.45.1",
|
||||
"@tauri-apps/cli": "^2.0.0-rc.2",
|
||||
"@tauri-apps/cli": "^2.0.0-rc.12",
|
||||
"@types/node": "^18.7.10",
|
||||
"@types/papaparse": "^5.3.7",
|
||||
"@types/parse-color": "^1.0.1",
|
||||
@@ -2344,23 +2345,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/api": {
|
||||
"version": "2.0.0-rc.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-rc.0.tgz",
|
||||
"integrity": "sha512-v454Qs3REHc3Za59U+/eSmBsdmF+3NE5+76+lFDaitVqN4ZglDHENDaMARYKGJVZuxiSkzyqG0SeG7lLQjVkPA==",
|
||||
"engines": {
|
||||
"node": ">= 18.18",
|
||||
"npm": ">= 6.6.0",
|
||||
"yarn": ">= 1.19.1"
|
||||
},
|
||||
"version": "2.0.0-rc.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.0-rc.4.tgz",
|
||||
"integrity": "sha512-UNiIhhKG08j4ooss2oEEVexffmWkgkYlC2M3GcX3VPtNsqFgVNL8Mcw/4Y7rO9M9S+ffAMnLOF5ypzyuyb8tyg==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/tauri"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "2.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-iNF95pieBmverl1EmQyqh+fhcIClS544fN5Ex5lAbYLTiHZ/gm3lOfVBhF6NPaKd/sfLuy7K1tfDXlHztBfANw==",
|
||||
"version": "2.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-rNcVSyGHGz8vNk542isYKPk5fEMAsgmzER+1s9YYbGZCH7m4e0rH89p/P9W40I/Z4AZk4ZqjpEeajeS5izDI4g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tauri": "tauri.js"
|
||||
@@ -2373,22 +2369,22 @@
|
||||
"url": "https://opencollective.com/tauri"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tauri-apps/cli-darwin-arm64": "2.0.0-rc.3",
|
||||
"@tauri-apps/cli-darwin-x64": "2.0.0-rc.3",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.0.0-rc.3",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.0.0-rc.3",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.0.0-rc.3",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.0.0-rc.3",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.0.0-rc.3",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.0.0-rc.3",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.0.0-rc.3",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.0.0-rc.3"
|
||||
"@tauri-apps/cli-darwin-arm64": "2.0.0-rc.12",
|
||||
"@tauri-apps/cli-darwin-x64": "2.0.0-rc.12",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.0.0-rc.12",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.0.0-rc.12",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.0.0-rc.12",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.0.0-rc.12",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.0.0-rc.12",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.0.0-rc.12",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.0.0-rc.12",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.0.0-rc.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||
"version": "2.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-szYCSr/ChbCF+S6Wnr15TYpI2cZR07d+AQOiFGuScP0preM8Pbsk/sb0hfLwqzepjVFFNVWQba9sG7FEW2Y2XA==",
|
||||
"version": "2.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-zYxcAH4reyqKkqCAybggszFWkBvC+ZyZPTWFKXXVQ20MZc1q+e/0UJYC8UKsaumrbi1uptgamvnM8yql56x5QQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2402,9 +2398,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||
"version": "2.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-BJv6EJOY1DJbRzVtfg8CcBAlnS5OjhBAc5YKjh4BT7EyOcop8HStBSxhL6yjWrUP7eLR1iIsW/uSehVJwzYIdQ==",
|
||||
"version": "2.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-eme7pQzEzeGCk13V3uxUNRnkVZJukqwHotqEb2RdovXqJWSyESrighBy4PBG5Xn6wNYTOyoquY9+In4TOfJAzw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2418,9 +2414,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"version": "2.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-fwx805/xL4sF/EdMYqcUHQHzMYwo+OVTBTz5x/JWK8D57rnmLHAP+ZhnfFsZQLRo2QRT2l1Ye3bDyU+QRA1JFA==",
|
||||
"version": "2.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-113T2NsLeoy6GXsqc0yjMoozt+KXzkAtUB7DL9Kcvx9IMfA87cUVaTNjnb2GFsoQqpCWGfHei3nr9n1PGEbwMg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2434,9 +2430,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"version": "2.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-3KauzO1Ls4kuY0nr82S4X8XFxlQAMN+Mqp8LLqvQ+PPMp92XQAkPH7osQdoHIEoW5gsE69U2JaiQ5tHSqNM9og==",
|
||||
"version": "2.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-9TrUyNg0vmsYF7IbG+/sybEeiz6ikA1Kjd6JjC4iwfXjRff8fuTR7CIOb06imabxbLzGP79qSAnGAeTXz8E7qA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2450,9 +2446,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||
"version": "2.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-ngHS0foffm1xO5gqnDKGeYMKj8ceGmrFP5dDldoaaMQubw1SyFa0pRUjb7fZSYiO7F4SOSa8NYeMqlF9peZmnQ==",
|
||||
"version": "2.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-YvE40+wdkNcXhwUAJNPyhNzJ8YS3saJoxGj7mtNQeNeNrKhxyj6hA5T6gw9KtMkwBOp+HGtqn+eDXiu0X7BBHQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2466,9 +2462,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||
"version": "2.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-0/am9pVvuUHGmz32M8ffz1fpLnc08j3nzcRe5wUdL2AxfT+wKMII+Dn99GtCVgcdDW4jSXDMRUwrBkGocGC2OA==",
|
||||
"version": "2.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-q+MJp/lSA5WINs78dCFMlU0/jQeUkGr9GHbKeppcVcpkcY/1vog70b4KhneyvbuklKBn/V8kd0FtIKCn8VP+KQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2482,9 +2478,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||
"version": "2.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-r7mRi8q8TqTFVjb9kAsU7IgwUgno2s8Ip4xwq9psQhlRE3JGEZQmSEcy1jqTjfl6KFh6lJcDR7l+9/EMhL/D3Q==",
|
||||
"version": "2.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-5zodtleH2GFsB9lszDYrzPTLcr+MMqtpQpJWHATC1K03bLEA8ia8zSdBqRwm7u8NraMLl8TE7hc7hwq0uxGEcg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2498,9 +2494,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"version": "2.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-2J6KjmDIQCw6HF1X6/yPcd+JLl7pxrH2zVMGmNllaoWhHeByvRobqFWnT7gcdHaA3dGTo432CwWvOgTgrINQpQ==",
|
||||
"version": "2.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-nSu6VHpuq61DYM2YowLDLDwkK8im7dzYxIHXs+h8/rhkmadTujGhbyUhHPI1STA6hNyITUtSFpo6P2mEbfpAIg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2514,9 +2510,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"version": "2.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-8q75CsHDSEDdgi6xPwim+BaQZFCswK2Dn/qL38V3Mh9kmVvC8oGJMPC66bC20dF+v3KWeFm2FNNGQqOSXCveHg==",
|
||||
"version": "2.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-d/4y57OisMuB+MUkTpZsryQRi9ZQXQ8SsMhrvEgu8sbX8/WRm0iZyGuIZ01RlZZHLIasXbKTkPX+hPQC5Juk8Q==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -2530,9 +2526,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||
"version": "2.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-qeBRJYalahxEXolekcpZJ/HBrIJacG2NWJBGhhi797mIwnbmlpbHMc8blIJtNNNwVUb2BjXuxKQVfojQ5YYrcg==",
|
||||
"version": "2.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-RsPUvsbFza03ysh0nU2nM3P2CVWz9cu7CRHwOEdtXjWWNREHUYCaVpqQKz0tn2sG19yXiNIB40iqrIBUmb/IoA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2546,51 +2542,51 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-clipboard-manager": {
|
||||
"version": "2.0.0-rc.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.0.0-rc.0.tgz",
|
||||
"integrity": "sha512-2fS3wbRQEtorkk3Np2msJUeKCXRqLQ9sSo2FzlFdUPYNzThsu43uWCF55McGLAfltNOvXQIcQLUBf05jbBL/5w==",
|
||||
"version": "2.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.0.0-rc.1.tgz",
|
||||
"integrity": "sha512-hFgUABMmQuVGKwHb8PR9fuqfk0WRkedbWUt/ZV5sL4Q6kLrsp3JYJvtzVPeMYdeBvMqHl8WXNxAc/zwSld2h9w==",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0-rc.0"
|
||||
"@tauri-apps/api": "^2.0.0-rc.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-dialog": {
|
||||
"version": "2.0.0-rc.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.0-rc.0.tgz",
|
||||
"integrity": "sha512-DPOXYe8SQ6Radk/67EOdaomlxL7oF99JO/ZUaPp1IBEs3Wro7lhlz63CfdKIBfKIZTLJLzP1R7/EiPL/GTA3Bg==",
|
||||
"version": "2.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.0.0-rc.1.tgz",
|
||||
"integrity": "sha512-H28gh6BfZtjflHQ+HrmWwunDriBI3AQLAKnMs50GA6zeNUULqbQr7VXbAAKeJL/0CmWcecID4PKXVoSlaWRhEg==",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0-rc.0"
|
||||
"@tauri-apps/api": "^2.0.0-rc.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-fs": {
|
||||
"version": "2.0.0-rc.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.0.0-rc.0.tgz",
|
||||
"integrity": "sha512-74VCXEZlzTJ+Jv1V3KrV0qIHhSePpE/ljsF78rcEuvSfyTxLtt/Sb5CIUmVhFlKTRFOH9dX50T4dTZ3qFLyRnA==",
|
||||
"version": "2.0.0-rc.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.0.0-rc.2.tgz",
|
||||
"integrity": "sha512-TFjCfso3tN4b5s2EBjqP8N2gYrPh93Ds3VNKj8pCXv4wbvnItyfG0aHO0haUsedBOHQryDwv9vDAdPX6/T0a+g==",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0-rc.0"
|
||||
"@tauri-apps/api": "^2.0.0-rc.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-log": {
|
||||
"version": "2.0.0-rc.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-log/-/plugin-log-2.0.0-rc.0.tgz",
|
||||
"integrity": "sha512-ztKfzUcq03dtr08vBu+xcwIEPusP6mCpuLAt6kpXEwG+HvYsC8e1/KFFokn3xvfwD+oBJ3UTL1h4kdM30GAqGw==",
|
||||
"version": "2.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-log/-/plugin-log-2.0.0-rc.1.tgz",
|
||||
"integrity": "sha512-+Tz0zo4FDtC/5j7neeIq5ievgKbUXBV2+X5HtbaR8ZZ2bcksCp8UqeHd6cyyN+FSk4qaU01LIGkuExtxk1h/FA==",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0-rc.0"
|
||||
"@tauri-apps/api": "^2.0.0-rc.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-os": {
|
||||
"version": "2.0.0-rc.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.0.0-rc.0.tgz",
|
||||
"integrity": "sha512-OWAl8mooKnGykSD4iog8WRqcnOSx0gGmTJBlEExHdFeIuOHg0Ezvd+WiVLhT9LBg7go3ibNWRWpe/ZG7YEp4Vw==",
|
||||
"version": "2.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.0.0-rc.1.tgz",
|
||||
"integrity": "sha512-PV8zlSTmYfiN2xzILUmlDSEycS7UYbH2yXk/ZqF+qQU6/s+OVQvmSth4EhllFjcpvPbtqELvpzfjw+2qEouchA==",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0-rc.0"
|
||||
"@tauri-apps/api": "^2.0.0-rc.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-shell": {
|
||||
"version": "2.0.0-rc.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0-rc.0.tgz",
|
||||
"integrity": "sha512-bhUcQcrqZoK8H1DFXapr5r1Z75oh6Kd5Tltz97XpZFLREEqp+KhN2Fvyh8r/fKAyenYsTYUIsDsyGdjdueuF9g==",
|
||||
"version": "2.0.0-rc.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.0.0-rc.1.tgz",
|
||||
"integrity": "sha512-JtNROc0rqEwN/g93ig5pK4cl1vUo2yn+osCpY9de64cy/d9hRzof7AuYOgvt/Xcd5VPQmlgo2AGvUh5sQRSR1A==",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0-rc.0"
|
||||
"@tauri-apps/api": "^2.0.0-rc.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
@@ -2990,9 +2986,9 @@
|
||||
"integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ=="
|
||||
},
|
||||
"node_modules/@yaakapp/api": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/api/-/api-0.1.13.tgz",
|
||||
"integrity": "sha512-FSYPHZV0mP967w63VXi9zYP81hPo3vjSW3/UElJLuF/8ig6WmG4p1q2oYos4Ik267Z3qSQAGN5dPMfuk3DAnBA==",
|
||||
"version": "0.1.17",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/api/-/api-0.1.17.tgz",
|
||||
"integrity": "sha512-VE9A0FDZwczZkTAbMOYjQOKzbW1KmaItq/mPSuTgU87Lf570JUepcHVtL7QFLV1U/R5q+n7I6xQg9Q2mDj/OWQ==",
|
||||
"dependencies": {
|
||||
"@types/node": "^22.0.0"
|
||||
}
|
||||
@@ -5750,8 +5746,7 @@
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "7.2.0",
|
||||
|
||||
19
package.json
19
package.json
@@ -35,20 +35,21 @@
|
||||
"@react-hook/resize-observer": "^1.2.6",
|
||||
"@tailwindcss/container-queries": "^0.1.0",
|
||||
"@tanstack/react-query": "^5.45.1",
|
||||
"@tauri-apps/api": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-log": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-rc.0",
|
||||
"@yaakapp/api": "^0.1.13",
|
||||
"@tauri-apps/api": "^2.0.0-rc.4",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.0.0-rc.1",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-rc.1",
|
||||
"@tauri-apps/plugin-fs": "^2.0.0-rc.2",
|
||||
"@tauri-apps/plugin-log": "^2.0.0-rc.1",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-rc.1",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-rc.1",
|
||||
"@yaakapp/api": "^0.1.17",
|
||||
"buffer": "^6.0.3",
|
||||
"classnames": "^2.3.2",
|
||||
"cm6-graphql": "^0.0.9",
|
||||
"codemirror": "^6.0.1",
|
||||
"codemirror-json-schema": "^0.6.1",
|
||||
"date-fns": "^3.3.1",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"fast-fuzzy": "^1.12.0",
|
||||
"focus-trap-react": "^10.1.1",
|
||||
"format-graphql": "^1.4.0",
|
||||
@@ -73,7 +74,7 @@
|
||||
"devDependencies": {
|
||||
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
|
||||
"@tanstack/react-query-devtools": "^5.45.1",
|
||||
"@tauri-apps/cli": "^2.0.0-rc.2",
|
||||
"@tauri-apps/cli": "^2.0.0-rc.12",
|
||||
"@types/node": "^18.7.10",
|
||||
"@types/papaparse": "^5.3.7",
|
||||
"@types/parse-color": "^1.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@yaakapp/api",
|
||||
"version": "0.1.13",
|
||||
"version": "0.1.17",
|
||||
"main": "lib/index.js",
|
||||
"typings": "./lib/index.d.ts",
|
||||
"files": [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { GrpcEventType } from "./GrpcEventType";
|
||||
|
||||
export type GrpcEvent = { id: string, model: "grpc_event", workspaceId: string, requestId: string, connectionId: string, createdAt: string, content: string, eventType: GrpcEventType, metadata: { [key: string]: string }, status: number | null, error: string | null, };
|
||||
export type GrpcEvent = { id: string, model: "grpc_event", workspaceId: string, requestId: string, connectionId: string, createdAt: string, updatedAt: string, content: string, eventType: GrpcEventType, metadata: { [key: string]: string }, status: number | null, error: string | null, };
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { BootRequest } from "./BootRequest";
|
||||
import type { BootResponse } from "./BootResponse";
|
||||
import type { CallHttpRequestActionRequest } from "./CallHttpRequestActionRequest";
|
||||
import type { CallTemplateFunctionRequest } from "./CallTemplateFunctionRequest";
|
||||
import type { CallTemplateFunctionResponse } from "./CallTemplateFunctionResponse";
|
||||
@@ -19,10 +17,12 @@ import type { GetHttpRequestByIdResponse } from "./GetHttpRequestByIdResponse";
|
||||
import type { GetTemplateFunctionsResponse } from "./GetTemplateFunctionsResponse";
|
||||
import type { ImportRequest } from "./ImportRequest";
|
||||
import type { ImportResponse } from "./ImportResponse";
|
||||
import type { PluginBootRequest } from "./PluginBootRequest";
|
||||
import type { PluginBootResponse } from "./PluginBootResponse";
|
||||
import type { RenderHttpRequestRequest } from "./RenderHttpRequestRequest";
|
||||
import type { RenderHttpRequestResponse } from "./RenderHttpRequestResponse";
|
||||
import type { SendHttpRequestRequest } from "./SendHttpRequestRequest";
|
||||
import type { SendHttpRequestResponse } from "./SendHttpRequestResponse";
|
||||
import type { ShowToastRequest } from "./ShowToastRequest";
|
||||
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & GetHttpRequestActionsRequest | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyResponse;
|
||||
export type InternalEventPayload = { "type": "boot_request" } & PluginBootRequest | { "type": "boot_response" } & PluginBootResponse | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & GetHttpRequestActionsRequest | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyResponse;
|
||||
|
||||
@@ -8,7 +8,8 @@ import type { GrpcRequest } from "./GrpcRequest";
|
||||
import type { HttpRequest } from "./HttpRequest";
|
||||
import type { HttpResponse } from "./HttpResponse";
|
||||
import type { KeyValue } from "./KeyValue";
|
||||
import type { Plugin } from "./Plugin";
|
||||
import type { Settings } from "./Settings";
|
||||
import type { Workspace } from "./Workspace";
|
||||
|
||||
export type Model = Environment | Folder | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | KeyValue | Workspace | CookieJar | Settings;
|
||||
export type Model = Environment | Folder | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | KeyValue | Workspace | CookieJar | Settings | Plugin;
|
||||
|
||||
3
plugin-runtime-types/src/gen/Plugin.ts
Normal file
3
plugin-runtime-types/src/gen/Plugin.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Plugin = { id: string, model: "plugin", createdAt: string, updatedAt: string, checkedAt: string | null, name: string, version: string, uri: string, enabled: boolean, };
|
||||
@@ -1,3 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type BootRequest = { dir: string, };
|
||||
export type PluginBootRequest = { dir: string, };
|
||||
@@ -1,3 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type BootResponse = { name: string, version: string, capabilities: Array<string>, };
|
||||
export type PluginBootResponse = { name: string, version: string, capabilities: Array<string>, };
|
||||
@@ -1,3 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type RenderRequest = { template: string, };
|
||||
@@ -1,3 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type RenderResponse = { rendered: string, };
|
||||
@@ -2,8 +2,6 @@ export type * from './plugins';
|
||||
export type * from './themes';
|
||||
|
||||
// TODO: The next ts-rs release includes the ability to put everything in 1 file!
|
||||
export * from './gen/BootRequest';
|
||||
export * from './gen/BootResponse';
|
||||
export * from './gen/CallHttpRequestActionArgs';
|
||||
export * from './gen/CallHttpRequestActionRequest';
|
||||
export * from './gen/CallTemplateFunctionRequest';
|
||||
@@ -45,6 +43,8 @@ export * from './gen/InternalEvent';
|
||||
export * from './gen/InternalEventPayload';
|
||||
export * from './gen/KeyValue';
|
||||
export * from './gen/Model';
|
||||
export * from './gen/PluginBootRequest';
|
||||
export * from './gen/PluginBootResponse';
|
||||
export * from './gen/RenderHttpRequestRequest';
|
||||
export * from './gen/RenderHttpRequestResponse';
|
||||
export * from './gen/RenderPurpose';
|
||||
@@ -63,3 +63,4 @@ export * from './gen/TemplateFunctionSelectOption';
|
||||
export * from './gen/TemplateFunctionTextArg';
|
||||
export * from './gen/ToastVariant';
|
||||
export * from './gen/Workspace';
|
||||
export * from './gen/Plugin';
|
||||
|
||||
@@ -9,7 +9,7 @@ export type { Context } from './Context';
|
||||
/**
|
||||
* The global structure of a Yaak plugin
|
||||
*/
|
||||
export type Plugin = {
|
||||
export type PluginDefinition = {
|
||||
importer?: ImporterPlugin;
|
||||
theme?: ThemePlugin;
|
||||
filter?: FilterPlugin;
|
||||
|
||||
402
src-tauri/Cargo.lock
generated
402
src-tauri/Cargo.lock
generated
@@ -124,7 +124,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb4009533e8ff8f1450a5bcbc30f4242a1d34442221f72314bea1f5dc9c7f89"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"core-graphics",
|
||||
"core-graphics 0.23.2",
|
||||
"image 0.25.2",
|
||||
"log",
|
||||
"objc2",
|
||||
@@ -188,7 +188,7 @@ version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa"
|
||||
dependencies = [
|
||||
"brotli 6.0.0",
|
||||
"brotli",
|
||||
"flate2",
|
||||
"futures-core",
|
||||
"memchr",
|
||||
@@ -577,17 +577,6 @@ dependencies = [
|
||||
"syn_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
"brotli-decompressor 2.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "6.0.0"
|
||||
@@ -596,17 +585,7 @@ checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
"brotli-decompressor 4.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli-decompressor"
|
||||
version = "2.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
"brotli-decompressor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -835,9 +814,25 @@ checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"block",
|
||||
"cocoa-foundation",
|
||||
"core-foundation",
|
||||
"core-graphics",
|
||||
"cocoa-foundation 0.1.2",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics 0.23.2",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"block",
|
||||
"cocoa-foundation 0.2.0",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics 0.24.0",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
"objc",
|
||||
@@ -851,8 +846,22 @@ checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"block",
|
||||
"core-foundation",
|
||||
"core-graphics-types",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics-types 0.1.3",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cocoa-foundation"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"block",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics-types 0.2.0",
|
||||
"libc",
|
||||
"objc",
|
||||
]
|
||||
@@ -954,10 +963,20 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
name = "core-foundation"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics"
|
||||
@@ -966,8 +985,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"core-graphics-types",
|
||||
"core-foundation 0.9.4",
|
||||
"core-graphics-types 0.1.3",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics-types 0.2.0",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
@@ -979,7 +1011,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"core-foundation 0.9.4",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-graphics-types"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"core-foundation 0.10.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -1596,6 +1639,15 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluent-uri"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.0"
|
||||
@@ -2588,9 +2640,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.15.0"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
|
||||
checksum = "bc150e5ce2330295b8616ce0e3f53250e53af31759a9dbedad1621ba29151847"
|
||||
dependencies = [
|
||||
"cfb",
|
||||
]
|
||||
@@ -2755,15 +2807,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "json-patch"
|
||||
version = "1.4.0"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b"
|
||||
checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc"
|
||||
dependencies = [
|
||||
"jsonptr",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonptr"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627"
|
||||
dependencies = [
|
||||
"fluent-uri",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyboard-types"
|
||||
version = "0.7.0"
|
||||
@@ -3053,11 +3117,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "muda"
|
||||
version = "0.13.5"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b959f97c97044e4c96e32e1db292a7d594449546a3c6b77ae613dc3a5b5145"
|
||||
checksum = "ba8ac4080fb1e097c2c22acae467e46e4da72d941f02e82b67a87a2a89fa38b1"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"cocoa 0.26.0",
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
"gtk",
|
||||
@@ -3067,7 +3131,7 @@ dependencies = [
|
||||
"png",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3095,15 +3159,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.7.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0"
|
||||
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.6.0",
|
||||
"jni-sys",
|
||||
"log",
|
||||
"ndk-sys",
|
||||
"num_enum",
|
||||
"raw-window-handle 0.5.2",
|
||||
"raw-window-handle",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -3115,9 +3180,9 @@ checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
|
||||
|
||||
[[package]]
|
||||
name = "ndk-sys"
|
||||
version = "0.4.1+23.1.7779620"
|
||||
version = "0.6.0+11769913"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3"
|
||||
checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
|
||||
dependencies = [
|
||||
"jni-sys",
|
||||
]
|
||||
@@ -3231,23 +3296,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.11"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
|
||||
checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.11"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
|
||||
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro-crate 2.0.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.72",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4302,12 +4367,6 @@ dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9"
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
@@ -4505,7 +4564,7 @@ dependencies = [
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"raw-window-handle 0.6.2",
|
||||
"raw-window-handle",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
@@ -4872,7 +4931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"core-foundation",
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
@@ -5202,7 +5261,7 @@ checksum = "d623bff5d06f60d738990980d782c8c866997d9194cfe79ecad00aa2f76826dd"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cfg_aliases 0.2.1",
|
||||
"core-graphics",
|
||||
"core-graphics 0.23.2",
|
||||
"foreign-types 0.5.0",
|
||||
"js-sys",
|
||||
"log",
|
||||
@@ -5210,7 +5269,7 @@ dependencies = [
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-quartz-core",
|
||||
"raw-window-handle 0.6.2",
|
||||
"raw-window-handle",
|
||||
"redox_syscall 0.5.3",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
@@ -5541,9 +5600,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.6"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bbdb58577b6301f8d17ae2561f32002a5bae056d444e0f69e611e504a276204"
|
||||
checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"serde",
|
||||
@@ -5612,7 +5671,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
@@ -5641,14 +5700,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.28.1"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea538df05fbc2dcbbd740ba0cfe8607688535f4798d213cbbfa13ce494f3451f"
|
||||
checksum = "2a93f2c6b8fdaeb7f417bda89b5bc767999745c3052969664ae1fa65892deb7e"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cocoa",
|
||||
"core-foundation",
|
||||
"core-graphics",
|
||||
"cocoa 0.26.0",
|
||||
"core-foundation 0.10.0",
|
||||
"core-graphics 0.24.0",
|
||||
"crossbeam-channel",
|
||||
"dispatch",
|
||||
"dlopen2",
|
||||
@@ -5667,13 +5726,13 @@ dependencies = [
|
||||
"objc",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"raw-window-handle 0.6.2",
|
||||
"raw-window-handle",
|
||||
"scopeguard",
|
||||
"tao-macros",
|
||||
"unicode-segmentation",
|
||||
"url",
|
||||
"windows 0.57.0",
|
||||
"windows-core 0.57.0",
|
||||
"windows 0.58.0",
|
||||
"windows-core 0.58.0",
|
||||
"windows-version",
|
||||
"x11-dl",
|
||||
]
|
||||
@@ -5714,13 +5773,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.0.0-rc.2"
|
||||
version = "2.0.0-rc.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19ee93e545e49458813d4ed16179c67ee6141dba140ec3d4f078dda3b8d4e0d1"
|
||||
checksum = "a6327f79726c508efbbc3826b343fd7d39ebce786bdeff5881077b35d335d9e0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"cocoa",
|
||||
"cocoa 0.26.0",
|
||||
"dirs",
|
||||
"dunce",
|
||||
"embed_plist",
|
||||
@@ -5738,7 +5797,7 @@ dependencies = [
|
||||
"muda",
|
||||
"objc",
|
||||
"percent-encoding",
|
||||
"raw-window-handle 0.6.2",
|
||||
"raw-window-handle",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -5759,14 +5818,14 @@ dependencies = [
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"window-vibrancy",
|
||||
"windows 0.57.0",
|
||||
"windows 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "2.0.0-rc.2"
|
||||
version = "2.0.0-rc.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96a58b3a716b51d7f671f729bb8c0a53cd2551eec8450c64e828ef4e6c9f948e"
|
||||
checksum = "7938a610d1474435fa38dfba66c95ce9be7f17b500672b6e00072bca5e52fef3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@@ -5786,12 +5845,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "2.0.0-rc.2"
|
||||
version = "2.0.0-rc.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90a9e63ecd827d57228864764e0234935c9aac230099cf145197c8c08e754ced"
|
||||
checksum = "467d3e95b57c860bea13b7c812820d9e7425e4b700e5e69b358d906f22022007"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"brotli 3.5.0",
|
||||
"brotli",
|
||||
"ico",
|
||||
"json-patch",
|
||||
"plist",
|
||||
@@ -5813,9 +5872,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "2.0.0-rc.2"
|
||||
version = "2.0.0-rc.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a54f5d5b289aa6215ffcfed7d4ff9960a04b7a854436d04519a9fcf911050cba"
|
||||
checksum = "c4585a906bd96bf57d063c3d60c52577ccc5de592d08f9d112e873ada79af9b9"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@@ -5827,9 +5886,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin"
|
||||
version = "2.0.0-rc.2"
|
||||
version = "2.0.0-rc.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03ce2ac5e182251ff932750d69c9b240a78e44901a7a6234814d63c595b43660"
|
||||
checksum = "b905ecef194245bb35baba0447703f9fe40e4c03e946c7aba54c21c23e3452c7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"glob",
|
||||
@@ -5860,13 +5919,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.0.0-rc.0"
|
||||
version = "2.0.0-rc.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c538457a755a75b8bb1594ed40d1512f8f6386251d3fcde492f8f46768ec85b"
|
||||
checksum = "0e0c5b6a7ea7db72623e9a6f7e5a8044dae4bb9cb3d154557cb082d10288bc9b"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"log",
|
||||
"raw-window-handle 0.6.2",
|
||||
"raw-window-handle",
|
||||
"rfd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -5874,15 +5932,17 @@ dependencies = [
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-fs",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.0.0-rc.0"
|
||||
version = "2.0.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5df6b25b1f2b7b61565e66c4dbee9eb39e5635d2a763206e380e07cc3f601a67"
|
||||
checksum = "8a4fa21faf29a0b18f1ab509c2f75f9b793ec2ebdbc9a81b75f9c8f1399f20ae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"glob",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -5897,13 +5957,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-log"
|
||||
version = "2.0.0-rc.0"
|
||||
version = "2.0.0-rc.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "380d27f23c39cde6a73024e65d8ec9b5b0af861e968dbe16b3aad86cd2c578e5"
|
||||
checksum = "b57e4666c4a5d81f81b7bb8eacf51ae32c4e69c35071aabb480ad20a80836e4e"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"byte-unit",
|
||||
"cocoa",
|
||||
"cocoa 0.25.0",
|
||||
"fern",
|
||||
"log",
|
||||
"objc",
|
||||
@@ -5919,9 +5979,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-os"
|
||||
version = "2.0.0-rc.0"
|
||||
version = "2.0.0-rc.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b54cfeb26356822d3be3db4282041b03552f573a694b6b28aded7d95c62a039"
|
||||
checksum = "ebc4ee761edd532fce2232453e9c8e0f7d9c0b6fe125c4b90b3eb4362ee84224"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"log",
|
||||
@@ -5937,9 +5997,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-shell"
|
||||
version = "2.0.0-rc.0"
|
||||
version = "2.0.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9209f6c32caec61e156a5616f7d80ba7683ca4a0a5641cbe5d3086ab371aaab2"
|
||||
checksum = "e83800ddf78b820172efb5ed7310344e8e4f97fd30cd8237a3f20c12a79eb136"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"log",
|
||||
@@ -5958,9 +6018,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-updater"
|
||||
version = "2.0.0-rc.0"
|
||||
version = "2.0.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b5f10ba18d2fc65e16bdf053b7beccb621dcf880c52d2ab08bdeb2d685e3e14"
|
||||
checksum = "391ebb8ae8cd6aec44b5d96d3005659d88cde69c57326f639bbc660116a30d63"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"dirs",
|
||||
@@ -5981,15 +6041,15 @@ dependencies = [
|
||||
"time",
|
||||
"tokio",
|
||||
"url",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-window-state"
|
||||
version = "2.0.0-rc.0"
|
||||
version = "2.0.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a86654a4937427217c7531594a60bede289e0e6d27baef0527d5678fb3d263a"
|
||||
checksum = "303569dd7858361d4c623845448b136b4c95d53b5d2bde6630bea9d7f0022d45"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"log",
|
||||
@@ -6002,36 +6062,36 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.0.0-rc.2"
|
||||
version = "2.0.0-rc.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f01b129b1ebdf09563c354760dbe7c0e96a166b4e33362d9c8d207f527c7ea5"
|
||||
checksum = "b72cd110a6699ef44963504d4fa4f6c535677bb0177da2d178f4f822a53058ed"
|
||||
dependencies = [
|
||||
"dpi",
|
||||
"gtk",
|
||||
"http 1.1.0",
|
||||
"jni",
|
||||
"raw-window-handle 0.6.2",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"thiserror",
|
||||
"url",
|
||||
"windows 0.57.0",
|
||||
"windows 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "2.0.0-rc.2"
|
||||
version = "2.0.0-rc.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcda27639094ace2bf25f00bc10e35ea4e3af2f92753b1bdd2a174d1fa5a6292"
|
||||
checksum = "1eb325cca17496ccbb469e7e2fef7f3e31a1005ab0c658dc3331c7781a573401"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"cocoa 0.26.0",
|
||||
"gtk",
|
||||
"http 1.1.0",
|
||||
"jni",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"raw-window-handle 0.6.2",
|
||||
"raw-window-handle",
|
||||
"softbuffer",
|
||||
"tao",
|
||||
"tauri-runtime",
|
||||
@@ -6039,17 +6099,17 @@ dependencies = [
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
"windows 0.57.0",
|
||||
"windows 0.58.0",
|
||||
"wry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "2.0.0-rc.2"
|
||||
version = "2.0.0-rc.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28bb83cffa26e9cb7a2b3d0c31ab87bf277f44aaaa90f17159aef4d37aabd051"
|
||||
checksum = "6746b87c4755f493b94920622e245aef2d771f001ddeffc203e315872d323e1c"
|
||||
dependencies = [
|
||||
"brotli 3.5.0",
|
||||
"brotli",
|
||||
"cargo_metadata",
|
||||
"ctor",
|
||||
"dunce",
|
||||
@@ -6548,22 +6608,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tray-icon"
|
||||
version = "0.14.3"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ad8319cca93189ea9ab1b290de0595960529750b6b8b501a399ed1ec3775d60"
|
||||
checksum = "044d7738b3d50f288ddef035b793228740ad4d927f5466b0af55dc15e7e03cfe"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"core-graphics",
|
||||
"core-graphics 0.24.0",
|
||||
"crossbeam-channel",
|
||||
"dirs",
|
||||
"libappindicator",
|
||||
"muda",
|
||||
"objc",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6739,12 +6800,17 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlpattern"
|
||||
version = "0.2.0"
|
||||
name = "urlencoding"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9bd5ff03aea02fa45b13a7980151fe45009af1980ba69f651ec367121a31609"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "urlpattern"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"regex",
|
||||
"serde",
|
||||
"unic-ucd-ident",
|
||||
@@ -7016,23 +7082,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com"
|
||||
version = "0.31.0"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6516cfa64c6b3212686080eeec378e662c2af54bb2a5b2a22749673f5cb2226f"
|
||||
checksum = "6f61ff3d9d0ee4efcb461b14eb3acfda2702d10dc329f339303fc3e57215ae2c"
|
||||
dependencies = [
|
||||
"webview2-com-macros",
|
||||
"webview2-com-sys",
|
||||
"windows 0.57.0",
|
||||
"windows-core 0.57.0",
|
||||
"windows 0.58.0",
|
||||
"windows-core 0.58.0",
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com-macros"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac1345798ecd8122468840bcdf1b95e5dc6d2206c5e4b0eafa078d061f59c9bc"
|
||||
checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -7041,13 +7107,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "webview2-com-sys"
|
||||
version = "0.31.0"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c76d5b77320ff155660be1df3e6588bc85c75f1a9feef938cc4dc4dd60d1d7cf"
|
||||
checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"windows 0.57.0",
|
||||
"windows-core 0.57.0",
|
||||
"windows 0.58.0",
|
||||
"windows-core 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7103,9 +7169,9 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33082acd404763b315866e14a0d5193f3422c81086657583937a750cdd3ec340"
|
||||
dependencies = [
|
||||
"cocoa",
|
||||
"cocoa 0.25.0",
|
||||
"objc",
|
||||
"raw-window-handle 0.6.2",
|
||||
"raw-window-handle",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-version",
|
||||
]
|
||||
@@ -7121,11 +7187,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.57.0"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
|
||||
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
||||
dependencies = [
|
||||
"windows-core 0.57.0",
|
||||
"windows-core 0.58.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
@@ -7140,21 +7206,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.57.0"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
||||
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.57.0"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
|
||||
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -7163,9 +7230,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.57.0"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
|
||||
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -7174,13 +7241,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.1.2"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
@@ -7425,14 +7502,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.41.0"
|
||||
version = "0.43.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68b00c945786b02d7805d09a969fa36d0eee4e0bd4fb3ec2a79d2bf45a1b44cd"
|
||||
checksum = "f4d715cf5fe88e9647f3d17b207b6d060d4a88e7171d4ccb2d2c657dd1d44728"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block",
|
||||
"cocoa",
|
||||
"core-graphics",
|
||||
"cocoa 0.26.0",
|
||||
"core-graphics 0.24.0",
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
"dunce",
|
||||
@@ -7445,13 +7522,11 @@ dependencies = [
|
||||
"kuchikiki",
|
||||
"libc",
|
||||
"ndk",
|
||||
"ndk-context",
|
||||
"ndk-sys",
|
||||
"objc",
|
||||
"objc_id",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"raw-window-handle 0.6.2",
|
||||
"raw-window-handle",
|
||||
"sha2",
|
||||
"soup3",
|
||||
"tao-macros",
|
||||
@@ -7459,8 +7534,8 @@ dependencies = [
|
||||
"webkit2gtk",
|
||||
"webkit2gtk-sys",
|
||||
"webview2-com",
|
||||
"windows 0.57.0",
|
||||
"windows-core 0.57.0",
|
||||
"windows 0.58.0",
|
||||
"windows-core 0.58.0",
|
||||
"windows-version",
|
||||
"x11-dl",
|
||||
]
|
||||
@@ -7540,7 +7615,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"cocoa",
|
||||
"cocoa 0.26.0",
|
||||
"datetime",
|
||||
"hex_color",
|
||||
"http 1.1.0",
|
||||
@@ -7568,6 +7643,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"urlencoding",
|
||||
"uuid",
|
||||
"yaak_grpc",
|
||||
"yaak_models",
|
||||
|
||||
@@ -20,7 +20,7 @@ tauri-build = { version = "2.0.0-rc.0", features = [] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
objc = "0.2.7"
|
||||
cocoa = "0.25.0"
|
||||
cocoa = "0.26.0"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installation to work
|
||||
@@ -47,20 +47,21 @@ serde_yaml = "0.9.34"
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-shell = { workspace = true }
|
||||
tauri-plugin-clipboard-manager = "2.1.0-beta.7"
|
||||
tauri-plugin-dialog = "2.0.0-rc.0"
|
||||
tauri-plugin-fs = "2.0.0-rc.0"
|
||||
tauri-plugin-log = { version = "2.0.0-rc.0", features = ["colored"] }
|
||||
tauri-plugin-os = "2.0.0-rc.0"
|
||||
tauri-plugin-updater = "2.0.0-rc.0"
|
||||
tauri-plugin-window-state = "2.0.0-rc.0"
|
||||
tauri-plugin-dialog = "2.0.0-rc.5"
|
||||
tauri-plugin-fs = "2.0.0-rc.3"
|
||||
tauri-plugin-log = { version = "2.0.0-rc.2", features = ["colored"] }
|
||||
tauri-plugin-os = "2.0.0-rc.1"
|
||||
tauri-plugin-updater = "2.0.0-rc.3"
|
||||
tauri-plugin-window-state = "2.0.0-rc.3"
|
||||
tokio = { version = "1.36.0", features = ["sync"] }
|
||||
tokio-stream = "0.1.15"
|
||||
uuid = "1.7.0"
|
||||
thiserror = "1.0.61"
|
||||
mime_guess = "2.0.5"
|
||||
urlencoding = "2.1.3"
|
||||
|
||||
[workspace.dependencies]
|
||||
yaak_models = { path = "yaak_models" }
|
||||
yaak_plugin_runtime = { path = "yaak_plugin_runtime" }
|
||||
tauri = { version = "2.0.0-rc.2", features = ["devtools", "protocol-asset"] }
|
||||
tauri-plugin-shell = "2.0.0-rc.0"
|
||||
tauri-plugin-shell = "2.0.0-rc.3"
|
||||
tauri = { version = "2.0.0-rc.10", features = ["devtools", "protocol-asset"] }
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3320,6 +3320,13 @@
|
||||
"core:webview:allow-create-webview-window"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:webview:allow-get-all-webviews -> Enables the get_all_webviews command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"core:webview:allow-get-all-webviews"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:webview:allow-internal-toggle-devtools -> Enables the internal_toggle_devtools command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -3404,6 +3411,13 @@
|
||||
"core:webview:deny-create-webview-window"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:webview:deny-get-all-webviews -> Denies the get_all_webviews command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"core:webview:deny-get-all-webviews"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:webview:deny-internal-toggle-devtools -> Denies the internal_toggle_devtools command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -3530,6 +3544,13 @@
|
||||
"core:window:allow-destroy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:window:allow-get-all-windows -> Enables the get_all_windows command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"core:window:allow-get-all-windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:window:allow-hide -> Enables the hide command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -3985,6 +4006,13 @@
|
||||
"core:window:deny-destroy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:window:deny-get-all-windows -> Denies the get_all_windows command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"core:window:deny-get-all-windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:window:deny-hide -> Denies the hide command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
@@ -3320,6 +3320,13 @@
|
||||
"core:webview:allow-create-webview-window"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:webview:allow-get-all-webviews -> Enables the get_all_webviews command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"core:webview:allow-get-all-webviews"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:webview:allow-internal-toggle-devtools -> Enables the internal_toggle_devtools command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -3404,6 +3411,13 @@
|
||||
"core:webview:deny-create-webview-window"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:webview:deny-get-all-webviews -> Denies the get_all_webviews command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"core:webview:deny-get-all-webviews"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:webview:deny-internal-toggle-devtools -> Denies the internal_toggle_devtools command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -3530,6 +3544,13 @@
|
||||
"core:window:allow-destroy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:window:allow-get-all-windows -> Enables the get_all_windows command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"core:window:allow-get-all-windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:window:allow-hide -> Enables the hide command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -3985,6 +4006,13 @@
|
||||
"core:window:deny-destroy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:window:deny-get-all-windows -> Denies the get_all_windows command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"core:window:deny-get-all-windows"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "core:window:deny-hide -> Denies the hide command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
12
src-tauri/migrations/20240829131004_plugins.sql
Normal file
12
src-tauri/migrations/20240829131004_plugins.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE plugins
|
||||
(
|
||||
id TEXT NOT NULL
|
||||
PRIMARY KEY,
|
||||
model TEXT DEFAULT 'plugin' NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
checked_at DATETIME NULL,
|
||||
enabled BOOLEAN NOT NULL,
|
||||
directory TEXT NULL NOT NULL,
|
||||
url TEXT NULL
|
||||
);
|
||||
@@ -28,6 +28,7 @@ pub enum AnalyticsResource {
|
||||
HttpRequest,
|
||||
HttpResponse,
|
||||
KeyValue,
|
||||
Plugin,
|
||||
Setting,
|
||||
Sidebar,
|
||||
Theme,
|
||||
|
||||
@@ -23,7 +23,7 @@ use tauri::{Manager, Runtime, WebviewWindow};
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::watch::Receiver;
|
||||
use yaak_models::models::{
|
||||
Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader,
|
||||
Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader, HttpUrlParameter,
|
||||
};
|
||||
use yaak_models::queries::{get_workspace, update_response_if_id, upsert_cookie_jar};
|
||||
|
||||
@@ -40,13 +40,8 @@ pub async fn send_http_request<R: Runtime>(
|
||||
.expect("Failed to get Workspace");
|
||||
let cb = &*window.app_handle().state::<PluginTemplateCallback>();
|
||||
let cb = cb.for_send();
|
||||
let rendered_request = render_http_request(
|
||||
&request,
|
||||
&workspace,
|
||||
environment.as_ref(),
|
||||
&cb,
|
||||
)
|
||||
.await;
|
||||
let rendered_request =
|
||||
render_http_request(&request, &workspace, environment.as_ref(), &cb).await;
|
||||
|
||||
let mut url_string = rendered_request.url;
|
||||
|
||||
@@ -101,6 +96,23 @@ pub async fn send_http_request<R: Runtime>(
|
||||
|
||||
let client = client_builder.build().expect("Failed to build client");
|
||||
|
||||
// Render query parameters
|
||||
let mut query_params = Vec::new();
|
||||
for p in rendered_request.url_parameters {
|
||||
if !p.enabled || p.name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace path parameters with values from URL parameters
|
||||
let old_url_string = url_string.clone();
|
||||
url_string = replace_path_placeholder(&p, url_string.as_str());
|
||||
|
||||
// Treat as regular param if wasn't used as path param
|
||||
if old_url_string == url_string {
|
||||
query_params.push((p.name, p.value));
|
||||
}
|
||||
}
|
||||
|
||||
let uri = match http::Uri::from_str(url_string.as_str()) {
|
||||
Ok(u) => u,
|
||||
Err(e) => {
|
||||
@@ -127,7 +139,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
|
||||
let m = Method::from_bytes(rendered_request.method.to_uppercase().as_bytes())
|
||||
.expect("Failed to create method");
|
||||
let mut request_builder = client.request(m, url);
|
||||
let mut request_builder = client.request(m, url).query(&query_params);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(USER_AGENT, HeaderValue::from_static("yaak"));
|
||||
@@ -210,15 +222,6 @@ pub async fn send_http_request<R: Runtime>(
|
||||
}
|
||||
}
|
||||
|
||||
let mut query_params = Vec::new();
|
||||
for p in rendered_request.url_parameters {
|
||||
if !p.enabled || p.name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
query_params.push((p.name, p.value));
|
||||
}
|
||||
request_builder = request_builder.query(&query_params);
|
||||
|
||||
let request_body = rendered_request.body;
|
||||
if let Some(body_type) = &rendered_request.body_type {
|
||||
if request_body.contains_key("text") {
|
||||
@@ -489,3 +492,119 @@ fn get_str_h<'a>(v: &'a HashMap<String, Value>, key: &str) -> &'a str {
|
||||
Some(v) => v.as_str().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_path_placeholder(p: &HttpUrlParameter, url: &str) -> String {
|
||||
if !p.enabled {
|
||||
return url.to_string();
|
||||
}
|
||||
|
||||
let re = regex::Regex::new(format!("(/){}([/?#]|$)", p.name).as_str()).unwrap();
|
||||
let result = re
|
||||
.replace_all(url, |cap: ®ex::Captures| {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
cap[1].to_string(),
|
||||
urlencoding::encode(p.value.as_str()),
|
||||
cap[2].to_string()
|
||||
)
|
||||
})
|
||||
.into_owned();
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::http_request::replace_path_placeholder;
|
||||
use yaak_models::models::HttpUrlParameter;
|
||||
|
||||
#[test]
|
||||
fn placeholder_middle() {
|
||||
let p = HttpUrlParameter {
|
||||
name: ":foo".into(),
|
||||
value: "xxx".into(),
|
||||
enabled: true,
|
||||
};
|
||||
assert_eq!(
|
||||
replace_path_placeholder(&p, "https://example.com/:foo/bar"),
|
||||
"https://example.com/xxx/bar",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placeholder_end() {
|
||||
let p = HttpUrlParameter {
|
||||
name: ":foo".into(),
|
||||
value: "xxx".into(),
|
||||
enabled: true,
|
||||
};
|
||||
assert_eq!(
|
||||
replace_path_placeholder(&p, "https://example.com/:foo"),
|
||||
"https://example.com/xxx",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placeholder_query() {
|
||||
let p = HttpUrlParameter {
|
||||
name: ":foo".into(),
|
||||
value: "xxx".into(),
|
||||
enabled: true,
|
||||
};
|
||||
assert_eq!(
|
||||
replace_path_placeholder(&p, "https://example.com/:foo?:foo"),
|
||||
"https://example.com/xxx?:foo",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placeholder_missing() {
|
||||
let p = HttpUrlParameter {
|
||||
enabled: true,
|
||||
name: "".to_string(),
|
||||
value: "".to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
replace_path_placeholder(&p, "https://example.com/:missing"),
|
||||
"https://example.com/:missing",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placeholder_disabled() {
|
||||
let p = HttpUrlParameter {
|
||||
enabled: false,
|
||||
name: ":foo".to_string(),
|
||||
value: "xxx".to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
replace_path_placeholder(&p, "https://example.com/:foo"),
|
||||
"https://example.com/:foo",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placeholder_prefix() {
|
||||
let p = HttpUrlParameter {
|
||||
name: ":foo".into(),
|
||||
value: "xxx".into(),
|
||||
enabled: true,
|
||||
};
|
||||
assert_eq!(
|
||||
replace_path_placeholder(&p, "https://example.com/:foooo"),
|
||||
"https://example.com/:foooo",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placeholder_encode() {
|
||||
let p = HttpUrlParameter {
|
||||
name: ":foo".into(),
|
||||
value: "Hello World".into(),
|
||||
enabled: true,
|
||||
};
|
||||
assert_eq!(
|
||||
replace_path_placeholder(&p, "https://example.com/:foo"),
|
||||
"https://example.com/Hello%20World",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
extern crate core;
|
||||
#[cfg(target_os = "macos")]
|
||||
#[macro_use]
|
||||
extern crate objc;
|
||||
|
||||
use std::collections::HashMap;
|
||||
@@ -41,7 +40,7 @@ use crate::updates::{UpdateMode, YaakUpdater};
|
||||
use crate::window_menu::app_menu;
|
||||
use yaak_models::models::{
|
||||
CookieJar, Environment, EnvironmentVariable, Folder, GrpcConnection, GrpcEvent, GrpcEventType,
|
||||
GrpcRequest, HttpRequest, HttpResponse, KeyValue, ModelType, Settings, Workspace,
|
||||
GrpcRequest, HttpRequest, HttpResponse, KeyValue, ModelType, Plugin, Settings, Workspace,
|
||||
};
|
||||
use yaak_models::queries::{
|
||||
cancel_pending_grpc_connections, cancel_pending_responses, create_default_http_response,
|
||||
@@ -50,16 +49,17 @@ use yaak_models::queries::{
|
||||
delete_http_response, delete_workspace, duplicate_grpc_request, duplicate_http_request,
|
||||
generate_model_id, get_cookie_jar, get_environment, get_folder, get_grpc_connection,
|
||||
get_grpc_request, get_http_request, get_http_response, get_key_value_raw,
|
||||
get_or_create_settings, get_workspace, list_cookie_jars, list_environments, list_folders,
|
||||
list_grpc_connections, list_grpc_events, list_grpc_requests, list_http_requests,
|
||||
list_http_responses, list_workspaces, set_key_value_raw, update_response_if_id,
|
||||
get_or_create_settings, get_plugin, get_workspace, list_cookie_jars, list_environments,
|
||||
list_folders, list_grpc_connections, list_grpc_events, list_grpc_requests, list_http_requests,
|
||||
list_http_responses, list_plugins, list_workspaces, set_key_value_raw, update_response_if_id,
|
||||
update_settings, upsert_cookie_jar, upsert_environment, upsert_folder, upsert_grpc_connection,
|
||||
upsert_grpc_event, upsert_grpc_request, upsert_http_request, upsert_workspace,
|
||||
upsert_grpc_event, upsert_grpc_request, upsert_http_request, upsert_plugin, upsert_workspace,
|
||||
};
|
||||
use yaak_plugin_runtime::events::{
|
||||
CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse,
|
||||
GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse,
|
||||
InternalEvent, InternalEventPayload, RenderHttpRequestResponse, SendHttpRequestResponse,
|
||||
InternalEvent, InternalEventPayload, PluginBootResponse, RenderHttpRequestResponse,
|
||||
SendHttpRequestResponse,
|
||||
};
|
||||
use yaak_templates::{Parser, Tokens};
|
||||
|
||||
@@ -78,6 +78,9 @@ mod window_menu;
|
||||
const DEFAULT_WINDOW_WIDTH: f64 = 1100.0;
|
||||
const DEFAULT_WINDOW_HEIGHT: f64 = 600.0;
|
||||
|
||||
const MIN_WINDOW_WIDTH: f64 = 300.0;
|
||||
const MIN_WINDOW_HEIGHT: f64 = 300.0;
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
struct AppMetaData {
|
||||
@@ -842,16 +845,33 @@ async fn cmd_import_data(
|
||||
imported_resources.environments.len()
|
||||
);
|
||||
|
||||
for mut v in resources.folders {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeFolder, &mut id_map);
|
||||
v.workspace_id = maybe_gen_id(
|
||||
v.workspace_id.as_str(),
|
||||
ModelType::TypeWorkspace,
|
||||
&mut id_map,
|
||||
);
|
||||
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
|
||||
let x = upsert_folder(&w, v).await.map_err(|e| e.to_string())?;
|
||||
imported_resources.folders.push(x.clone());
|
||||
// Folders can foreign-key to themselves, so we need to import from
|
||||
// the top of the tree to the bottom to avoid foreign key conflicts.
|
||||
// We do this by looping until we've imported them all, only importing if:
|
||||
// - The parent folder has been imported
|
||||
// - The folder hasn't already been imported
|
||||
// The loop exits when imported.len == to_import.len
|
||||
while imported_resources.folders.len() < resources.folders.len() {
|
||||
for mut v in resources.folders.clone() {
|
||||
v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeFolder, &mut id_map);
|
||||
v.workspace_id = maybe_gen_id(
|
||||
v.workspace_id.as_str(),
|
||||
ModelType::TypeWorkspace,
|
||||
&mut id_map,
|
||||
);
|
||||
v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map);
|
||||
if let Some(fid) = v.folder_id.clone() {
|
||||
let imported_parent = imported_resources.folders.iter().find(|f| f.id == fid);
|
||||
if imported_parent.is_none() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(_) = imported_resources.folders.iter().find(|f| f.id == v.id) {
|
||||
continue;
|
||||
}
|
||||
let x = upsert_folder(&w, v).await.map_err(|e| e.to_string())?;
|
||||
imported_resources.folders.push(x.clone());
|
||||
}
|
||||
}
|
||||
info!("Imported {} folders", imported_resources.folders.len());
|
||||
|
||||
@@ -1149,6 +1169,20 @@ async fn cmd_create_workspace(name: &str, w: WebviewWindow) -> Result<Workspace,
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_create_plugin(directory: &str, url: Option<String>, w: WebviewWindow) -> Result<Plugin, String> {
|
||||
upsert_plugin(
|
||||
&w,
|
||||
Plugin {
|
||||
directory: directory.into(),
|
||||
url,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_update_cookie_jar(
|
||||
cookie_jar: CookieJar,
|
||||
@@ -1392,10 +1426,9 @@ async fn cmd_list_grpc_requests(
|
||||
workspace_id: &str,
|
||||
w: WebviewWindow,
|
||||
) -> Result<Vec<GrpcRequest>, String> {
|
||||
let requests = list_grpc_requests(&w, workspace_id)
|
||||
list_grpc_requests(&w, workspace_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(requests)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -1403,11 +1436,9 @@ async fn cmd_list_http_requests(
|
||||
workspace_id: &str,
|
||||
w: WebviewWindow,
|
||||
) -> Result<Vec<HttpRequest>, String> {
|
||||
let requests = list_http_requests(&w, workspace_id)
|
||||
list_http_requests(&w, workspace_id)
|
||||
.await
|
||||
.expect("Failed to find requests");
|
||||
// .map_err(|e| e.to_string())
|
||||
Ok(requests)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -1415,11 +1446,27 @@ async fn cmd_list_environments(
|
||||
workspace_id: &str,
|
||||
w: WebviewWindow,
|
||||
) -> Result<Vec<Environment>, String> {
|
||||
let environments = list_environments(&w, workspace_id)
|
||||
list_environments(&w, workspace_id)
|
||||
.await
|
||||
.expect("Failed to find environments");
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
Ok(environments)
|
||||
#[tauri::command]
|
||||
async fn cmd_list_plugins(w: WebviewWindow) -> Result<Vec<Plugin>, String> {
|
||||
list_plugins(&w).await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_plugin_info(
|
||||
id: &str,
|
||||
w: WebviewWindow,
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
) -> Result<PluginBootResponse, String> {
|
||||
let plugin = get_plugin(&w, id).await.map_err(|e| e.to_string())?;
|
||||
plugin_manager
|
||||
.get_plugin_info(plugin.directory.as_str())
|
||||
.await
|
||||
.ok_or("Failed to find plugin info".to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -1673,6 +1720,7 @@ pub fn run() {
|
||||
cmd_create_folder,
|
||||
cmd_create_grpc_request,
|
||||
cmd_create_http_request,
|
||||
cmd_create_plugin,
|
||||
cmd_create_workspace,
|
||||
cmd_curl_to_request,
|
||||
cmd_delete_all_grpc_connections,
|
||||
@@ -1714,6 +1762,8 @@ pub fn run() {
|
||||
cmd_list_grpc_requests,
|
||||
cmd_list_http_requests,
|
||||
cmd_list_http_responses,
|
||||
cmd_list_plugins,
|
||||
cmd_plugin_info,
|
||||
cmd_list_workspaces,
|
||||
cmd_metadata,
|
||||
cmd_new_nested_window,
|
||||
@@ -1815,6 +1865,7 @@ fn create_nested_window(
|
||||
.title(title)
|
||||
.parent(&window)
|
||||
.unwrap()
|
||||
.min_inner_size(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
|
||||
.inner_size(DEFAULT_WINDOW_WIDTH * 0.7, DEFAULT_WINDOW_HEIGHT * 0.9);
|
||||
|
||||
// Add macOS-only things
|
||||
@@ -1825,7 +1876,7 @@ fn create_nested_window(
|
||||
.title_bar_style(TitleBarStyle::Overlay);
|
||||
}
|
||||
|
||||
// Add non-MacOS things
|
||||
// Add non-macOS things
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
win_builder = win_builder.decorations(false);
|
||||
@@ -1858,7 +1909,7 @@ fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
|
||||
100.0 + random::<f64>() * 30.0,
|
||||
100.0 + random::<f64>() * 30.0,
|
||||
)
|
||||
.min_inner_size(300.0, 300.0)
|
||||
.min_inner_size(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
|
||||
.title(handle.package_info().name.to_string());
|
||||
|
||||
// Add macOS-only things
|
||||
@@ -1884,7 +1935,8 @@ fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
|
||||
return;
|
||||
}
|
||||
|
||||
match event.id().0.as_str() {
|
||||
let event_id = event.id().0.as_str();
|
||||
match event_id {
|
||||
"quit" => exit(0),
|
||||
"close" => w.close().unwrap(),
|
||||
"zoom_reset" => w.emit("zoom_reset", true).unwrap(),
|
||||
@@ -1955,7 +2007,7 @@ fn monitor_plugin_events<R: Runtime>(app_handle: &AppHandle<R>) {
|
||||
}
|
||||
|
||||
async fn handle_plugin_event<R: Runtime>(app_handle: &AppHandle<R>, event: &InternalEvent) {
|
||||
info!("Got event to app {}", event.id);
|
||||
// info!("Got event to app {}", event.id);
|
||||
let response_event: Option<InternalEventPayload> = match event.clone().payload {
|
||||
InternalEventPayload::CopyTextRequest(req) => {
|
||||
app_handle
|
||||
|
||||
@@ -10,8 +10,9 @@ use yaak_plugin_runtime::manager::PluginManager;
|
||||
|
||||
use crate::is_dev;
|
||||
|
||||
// Check for updates every 3 hours
|
||||
const MAX_UPDATE_CHECK_SECONDS: u64 = 60 * 60 * 3;
|
||||
const MAX_UPDATE_CHECK_HOURS_STABLE: u64 = 12;
|
||||
const MAX_UPDATE_CHECK_HOURS_BETA: u64 = 3;
|
||||
const MAX_UPDATE_CHECK_HOURS_ALPHA: u64 = 1;
|
||||
|
||||
// Create updater struct
|
||||
pub struct YaakUpdater {
|
||||
@@ -129,8 +130,13 @@ impl YaakUpdater {
|
||||
app_handle: &AppHandle,
|
||||
mode: UpdateMode,
|
||||
) -> Result<bool, tauri_plugin_updater::Error> {
|
||||
let ignore_check =
|
||||
self.last_update_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS;
|
||||
let update_period_seconds = match mode {
|
||||
UpdateMode::Stable => MAX_UPDATE_CHECK_HOURS_STABLE,
|
||||
UpdateMode::Beta => MAX_UPDATE_CHECK_HOURS_BETA,
|
||||
UpdateMode::Alpha => MAX_UPDATE_CHECK_HOURS_ALPHA,
|
||||
} * (60 * 60);
|
||||
let seconds_since_last_check = self.last_update_check.elapsed().unwrap().as_secs();
|
||||
let ignore_check = seconds_since_last_check < update_period_seconds;
|
||||
if ignore_check {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("SQL error")]
|
||||
#[error("SQL error: {0}")]
|
||||
SqlError(#[from] rusqlite::Error),
|
||||
#[error("JSON error")]
|
||||
#[error("JSON error: {0}")]
|
||||
JsonError(#[from] serde_json::Error),
|
||||
#[error("unknown error")]
|
||||
Unknown,
|
||||
|
||||
@@ -20,7 +20,7 @@ pub struct Settings {
|
||||
pub theme_light: String,
|
||||
pub update_channel: String,
|
||||
pub interface_font_size: i32,
|
||||
pub interface_scale: i32,
|
||||
pub interface_scale: f32,
|
||||
pub editor_font_size: i32,
|
||||
pub editor_soft_wrap: bool,
|
||||
pub telemetry: bool,
|
||||
@@ -655,6 +655,7 @@ pub struct GrpcEvent {
|
||||
pub request_id: String,
|
||||
pub connection_id: String,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub content: String,
|
||||
pub event_type: GrpcEventType,
|
||||
pub metadata: HashMap<String, String>,
|
||||
@@ -693,6 +694,7 @@ impl<'s> TryFrom<&Row<'s>> for GrpcEvent {
|
||||
request_id: r.get("request_id")?,
|
||||
connection_id: r.get("connection_id")?,
|
||||
created_at: r.get("created_at")?,
|
||||
updated_at: r.get("updated_at")?,
|
||||
content: r.get("content")?,
|
||||
event_type: serde_json::from_str(event_type.as_str()).unwrap_or_default(),
|
||||
metadata: serde_json::from_str(metadata.as_str()).unwrap_or_default(),
|
||||
@@ -702,6 +704,51 @@ impl<'s> TryFrom<&Row<'s>> for GrpcEvent {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct Plugin {
|
||||
pub id: String,
|
||||
#[ts(type = "\"plugin\"")]
|
||||
pub model: String,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub checked_at: Option<NaiveDateTime>,
|
||||
pub directory: String,
|
||||
pub url: Option<String>,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
pub enum PluginIden {
|
||||
#[iden = "plugins"]
|
||||
Table,
|
||||
Id,
|
||||
Model,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
CheckedAt,
|
||||
Directory,
|
||||
Url,
|
||||
Enabled,
|
||||
}
|
||||
|
||||
impl<'s> TryFrom<&Row<'s>> for Plugin {
|
||||
type Error = rusqlite::Error;
|
||||
|
||||
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
|
||||
Ok(Plugin {
|
||||
id: r.get("id")?,
|
||||
model: r.get("model")?,
|
||||
created_at: r.get("created_at")?,
|
||||
updated_at: r.get("updated_at")?,
|
||||
checked_at: r.get("checked_at")?,
|
||||
url: r.get("url")?,
|
||||
directory: r.get("directory")?,
|
||||
enabled: r.get("enabled")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct KeyValue {
|
||||
@@ -758,6 +805,7 @@ pub enum ModelType {
|
||||
TypeGrpcRequest,
|
||||
TypeHttpRequest,
|
||||
TypeHttpResponse,
|
||||
TypePlugin,
|
||||
TypeWorkspace,
|
||||
}
|
||||
|
||||
@@ -772,6 +820,7 @@ impl ModelType {
|
||||
ModelType::TypeGrpcRequest => "gr",
|
||||
ModelType::TypeHttpRequest => "rq",
|
||||
ModelType::TypeHttpResponse => "rs",
|
||||
ModelType::TypePlugin => "pg",
|
||||
ModelType::TypeWorkspace => "wk",
|
||||
}
|
||||
.to_string()
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
use std::fs;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::models::{
|
||||
CookieJar, CookieJarIden, Environment, EnvironmentIden, Folder, FolderIden, GrpcConnection,
|
||||
GrpcConnectionIden, GrpcEvent, GrpcEventIden, GrpcRequest, GrpcRequestIden, HttpRequest,
|
||||
HttpRequestIden, HttpResponse, HttpResponseHeader, HttpResponseIden, KeyValue, KeyValueIden,
|
||||
ModelType, Settings, SettingsIden, Workspace, WorkspaceIden,
|
||||
};
|
||||
use crate::models::{CookieJar, CookieJarIden, Environment, EnvironmentIden, Folder, FolderIden, GrpcConnection, GrpcConnectionIden, GrpcEvent, GrpcEventIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestIden, HttpResponse, HttpResponseHeader, HttpResponseIden, KeyValue, KeyValueIden, ModelType, Plugin, PluginIden, Settings, SettingsIden, Workspace, WorkspaceIden};
|
||||
use crate::plugin::SqliteConnection;
|
||||
use log::{debug, error};
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
@@ -848,7 +843,7 @@ pub async fn upsert_environment<R: Runtime>(
|
||||
serde_json::to_string(&environment.variables)?.into(),
|
||||
])
|
||||
.on_conflict(
|
||||
OnConflict::column(GrpcEventIden::Id)
|
||||
OnConflict::column(EnvironmentIden::Id)
|
||||
.update_columns([
|
||||
EnvironmentIden::UpdatedAt,
|
||||
EnvironmentIden::Name,
|
||||
@@ -877,6 +872,88 @@ pub async fn get_environment<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Res
|
||||
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
|
||||
}
|
||||
|
||||
pub async fn get_plugin<R: Runtime>(
|
||||
mgr: &impl Manager<R>,
|
||||
id: &str
|
||||
) -> Result<Plugin> {
|
||||
let dbm = &*mgr.state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
|
||||
let (sql, params) = Query::select()
|
||||
.from(PluginIden::Table)
|
||||
.column(Asterisk)
|
||||
.cond_where(Expr::col(EnvironmentIden::Id).eq(id))
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
let mut stmt = db.prepare(sql.as_str())?;
|
||||
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
|
||||
}
|
||||
|
||||
pub async fn list_plugins<R: Runtime>(
|
||||
mgr: &impl Manager<R>,
|
||||
) -> Result<Vec<Plugin>> {
|
||||
let dbm = &*mgr.state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
|
||||
let (sql, params) = Query::select()
|
||||
.from(PluginIden::Table)
|
||||
.column(Asterisk)
|
||||
.order_by(PluginIden::CreatedAt, Order::Desc)
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
let mut stmt = db.prepare(sql.as_str())?;
|
||||
let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?;
|
||||
Ok(items.map(|v| v.unwrap()).collect())
|
||||
}
|
||||
|
||||
pub async fn upsert_plugin<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
plugin: Plugin,
|
||||
) -> Result<Plugin> {
|
||||
let id = match plugin.id.as_str() {
|
||||
"" => generate_model_id(ModelType::TypePlugin),
|
||||
_ => plugin.id.to_string(),
|
||||
};
|
||||
let dbm = &*window.app_handle().state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
|
||||
let (sql, params) = Query::insert()
|
||||
.into_table(PluginIden::Table)
|
||||
.columns([
|
||||
PluginIden::Id,
|
||||
PluginIden::CreatedAt,
|
||||
PluginIden::UpdatedAt,
|
||||
PluginIden::CheckedAt,
|
||||
PluginIden::Directory,
|
||||
PluginIden::Url,
|
||||
PluginIden::Enabled,
|
||||
])
|
||||
.values_panic([
|
||||
id.as_str().into(),
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
plugin.checked_at.into(),
|
||||
plugin.directory.into(),
|
||||
plugin.url.into(),
|
||||
plugin.enabled.into(),
|
||||
])
|
||||
.on_conflict(
|
||||
OnConflict::column(PluginIden::Id)
|
||||
.update_columns([
|
||||
PluginIden::UpdatedAt,
|
||||
PluginIden::CheckedAt,
|
||||
PluginIden::Directory,
|
||||
PluginIden::Url,
|
||||
PluginIden::Enabled,
|
||||
])
|
||||
.to_owned(),
|
||||
)
|
||||
.returning_all()
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
|
||||
let mut stmt = db.prepare(sql.as_str())?;
|
||||
let m = stmt.query_row(&*params.as_params(), |row| row.try_into())?;
|
||||
Ok(emit_upserted_model(window, m))
|
||||
}
|
||||
|
||||
pub async fn get_folder<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<Folder> {
|
||||
let dbm = &*mgr.state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
|
||||
@@ -2,10 +2,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use ts_rs::TS;
|
||||
|
||||
use yaak_models::models::{
|
||||
CookieJar, Environment, Folder, GrpcConnection, GrpcEvent, GrpcRequest, HttpRequest,
|
||||
HttpResponse, KeyValue, Settings, Workspace,
|
||||
};
|
||||
use yaak_models::models::{CookieJar, Environment, Folder, GrpcConnection, GrpcEvent, GrpcRequest, HttpRequest, HttpResponse, KeyValue, Plugin, Settings, Workspace};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -21,8 +18,8 @@ pub struct InternalEvent {
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export)]
|
||||
pub enum InternalEventPayload {
|
||||
BootRequest(BootRequest),
|
||||
BootResponse(BootResponse),
|
||||
BootRequest(PluginBootRequest),
|
||||
BootResponse(PluginBootResponse),
|
||||
|
||||
ImportRequest(ImportRequest),
|
||||
ImportResponse(ImportResponse),
|
||||
@@ -71,14 +68,14 @@ pub struct EmptyResponse {}
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct BootRequest {
|
||||
pub struct PluginBootRequest {
|
||||
pub dir: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct BootResponse {
|
||||
pub struct PluginBootResponse {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub capabilities: Vec<String>,
|
||||
@@ -401,4 +398,5 @@ pub enum Model {
|
||||
Workspace(Workspace),
|
||||
CookieJar(CookieJar),
|
||||
Settings(Settings),
|
||||
Plugin(Plugin),
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::error::Result;
|
||||
use crate::events::{
|
||||
CallHttpRequestActionRequest, CallTemplateFunctionArgs, RenderPurpose,
|
||||
PluginBootResponse, CallHttpRequestActionRequest, CallTemplateFunctionArgs,
|
||||
CallTemplateFunctionRequest, CallTemplateFunctionResponse, FilterRequest, FilterResponse,
|
||||
GetHttpRequestActionsRequest, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse,
|
||||
ImportRequest, ImportResponse, InternalEvent, InternalEventPayload,
|
||||
ImportRequest, ImportResponse, InternalEvent, InternalEventPayload, RenderPurpose,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -64,6 +64,10 @@ impl PluginManager {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_plugin_info(&self, dir: &str) -> Option<PluginBootResponse> {
|
||||
self.server.plugin_by_dir(dir).await.ok()?.info().await
|
||||
}
|
||||
|
||||
pub async fn get_http_request_actions(&self) -> Result<Vec<GetHttpRequestActionsResponse>> {
|
||||
let reply_events = self
|
||||
.server
|
||||
@@ -155,7 +159,9 @@ impl PluginManager {
|
||||
});
|
||||
|
||||
match result {
|
||||
None => Err(PluginErr("No importers found for file contents".to_string())),
|
||||
None => Err(PluginErr(
|
||||
"No importers found for file contents".to_string(),
|
||||
)),
|
||||
Some((resp, ref_id)) => {
|
||||
let plugin = self.server.plugin_by_ref_id(ref_id.as_str()).await?;
|
||||
let plugin_name = plugin.name().await;
|
||||
|
||||
@@ -15,21 +15,30 @@ use tokio::fs::read_dir;
|
||||
use tokio::net::TcpListener;
|
||||
use tonic::codegen::tokio_stream;
|
||||
use tonic::transport::Server;
|
||||
use yaak_models::queries::list_plugins;
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("yaak_plugin_runtime")
|
||||
.setup(|app, _| {
|
||||
let plugins_dir = app
|
||||
.setup(|app_handle, _| {
|
||||
let plugins_dir = app_handle
|
||||
.path()
|
||||
.resolve("plugins", BaseDirectory::Resource)
|
||||
.expect("failed to resolve plugin directory resource");
|
||||
|
||||
tauri::async_runtime::block_on(async move {
|
||||
let plugin_dirs = read_plugins_dir(&plugins_dir)
|
||||
let bundled_plugin_dirs = read_plugins_dir(&plugins_dir)
|
||||
.await
|
||||
.expect("Failed to read plugins dir");
|
||||
let manager = PluginManager::new(&app, plugin_dirs).await;
|
||||
app.manage(manager);
|
||||
.expect(format!("Failed to read plugins dir: {:?}", plugins_dir).as_str());
|
||||
|
||||
let plugins = list_plugins(app_handle).await.unwrap_or_default();
|
||||
let installed_plugin_dirs = plugins
|
||||
.iter()
|
||||
.map(|p| p.directory.to_owned())
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let plugin_dirs = [installed_plugin_dirs, bundled_plugin_dirs].concat();
|
||||
let manager = PluginManager::new(&app_handle, plugin_dirs).await;
|
||||
app_handle.manage(manager);
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use log::info;
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
use std::collections::HashMap;
|
||||
use std::pin::Pin;
|
||||
@@ -11,7 +10,7 @@ use tonic::{Request, Response, Status, Streaming};
|
||||
|
||||
use crate::error::Error::PluginNotFoundErr;
|
||||
use crate::error::Result;
|
||||
use crate::events::{BootRequest, BootResponse, InternalEvent, InternalEventPayload};
|
||||
use crate::events::{PluginBootRequest, PluginBootResponse, InternalEvent, InternalEventPayload};
|
||||
use crate::server::plugin_runtime::plugin_runtime_server::PluginRuntime;
|
||||
use plugin_runtime::EventStreamEvent;
|
||||
use yaak_models::queries::generate_id;
|
||||
@@ -28,7 +27,7 @@ pub struct PluginHandle {
|
||||
dir: String,
|
||||
to_plugin_tx: Arc<Mutex<mpsc::Sender<tonic::Result<EventStreamEvent>>>>,
|
||||
ref_id: String,
|
||||
boot_resp: Arc<Mutex<Option<BootResponse>>>,
|
||||
boot_resp: Arc<Mutex<Option<PluginBootResponse>>>,
|
||||
}
|
||||
|
||||
impl PluginHandle {
|
||||
@@ -38,6 +37,11 @@ impl PluginHandle {
|
||||
Some(r) => r.name.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn info(&self) -> Option<PluginBootResponse> {
|
||||
let resp = &*self.boot_resp.lock().await;
|
||||
resp.clone()
|
||||
}
|
||||
|
||||
pub fn build_event_to_send(
|
||||
&self,
|
||||
@@ -53,11 +57,11 @@ impl PluginHandle {
|
||||
}
|
||||
|
||||
pub async fn send(&self, event: &InternalEvent) -> Result<()> {
|
||||
info!(
|
||||
"Sending event to plugin {} {:?}",
|
||||
event.id,
|
||||
self.name().await
|
||||
);
|
||||
// info!(
|
||||
// "Sending event to plugin {} {:?}",
|
||||
// event.id,
|
||||
// self.name().await
|
||||
// );
|
||||
self.to_plugin_tx
|
||||
.lock()
|
||||
.await
|
||||
@@ -68,7 +72,7 @@ impl PluginHandle {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn boot(&self, resp: &BootResponse) {
|
||||
pub async fn boot(&self, resp: &PluginBootResponse) {
|
||||
let mut boot_resp = self.boot_resp.lock().await;
|
||||
*boot_resp = Some(resp.clone());
|
||||
}
|
||||
@@ -120,7 +124,7 @@ impl PluginRuntimeGrpcServer {
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn boot_plugin(&self, id: &str, resp: &BootResponse) {
|
||||
pub async fn boot_plugin(&self, id: &str, resp: &PluginBootResponse) {
|
||||
match self.plugin_ref_to_plugin.lock().await.get(id) {
|
||||
None => {
|
||||
println!("Tried booting non-existing plugin {}", id);
|
||||
@@ -151,61 +155,27 @@ impl PluginRuntimeGrpcServer {
|
||||
plugin_handle
|
||||
}
|
||||
|
||||
// pub async fn callback(
|
||||
// &self,
|
||||
// source_event: InternalEvent,
|
||||
// payload: InternalEventPayload,
|
||||
// ) -> Result<InternalEvent> {
|
||||
// let reply_id = match source_event.clone().reply_id {
|
||||
// None => {
|
||||
// let msg = format!("Source event missing reply Id {:?}", source_event.clone());
|
||||
// return Err(MissingCallbackIdErr(msg));
|
||||
// }
|
||||
// Some(id) => id,
|
||||
// };
|
||||
//
|
||||
// let callbacks = self.callbacks.lock().await;
|
||||
// let plugin_name = match callbacks.get(reply_id.as_str()) {
|
||||
// None => {
|
||||
// let msg = format!("Callback not found {:?}", source_event);
|
||||
// return Err(MissingCallbackErr(msg));
|
||||
// }
|
||||
// Some(n) => n,
|
||||
// };
|
||||
//
|
||||
// let plugins = self.plugins.lock().await;
|
||||
// let plugin = match plugins.get(plugin_name) {
|
||||
// None => {
|
||||
// let msg = format!(
|
||||
// "Plugin not found {plugin_name}. Choices were {:?}",
|
||||
// plugins.keys()
|
||||
// );
|
||||
// return Err(UnknownPluginErr(msg));
|
||||
// }
|
||||
// Some(n) => n,
|
||||
// };
|
||||
//
|
||||
// plugin.send(&payload, Some(reply_id)).await
|
||||
// }
|
||||
|
||||
pub async fn plugin_by_ref_id(&self, ref_id: &str) -> Result<PluginHandle> {
|
||||
let plugins = self.plugin_ref_to_plugin.lock().await;
|
||||
if plugins.is_empty() {
|
||||
return Err(PluginNotFoundErr(ref_id.into()));
|
||||
}
|
||||
|
||||
match plugins.get(ref_id) {
|
||||
None => Err(PluginNotFoundErr(ref_id.into())),
|
||||
Some(p) => Ok(p.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn plugin_by_dir(&self, dir: &str) -> Result<PluginHandle> {
|
||||
let plugins = self.plugin_ref_to_plugin.lock().await;
|
||||
for p in plugins.values() {
|
||||
if p.dir == dir {
|
||||
return Ok(p.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
Err(PluginNotFoundErr(dir.into()))
|
||||
}
|
||||
|
||||
pub async fn plugin_by_name(&self, plugin_name: &str) -> Result<PluginHandle> {
|
||||
let plugins = self.plugin_ref_to_plugin.lock().await;
|
||||
if plugins.is_empty() {
|
||||
return Err(PluginNotFoundErr(plugin_name.into()));
|
||||
}
|
||||
|
||||
for p in plugins.values() {
|
||||
if p.name().await == plugin_name {
|
||||
return Ok(p.to_owned());
|
||||
@@ -341,7 +311,7 @@ impl PluginRuntimeGrpcServer {
|
||||
plugin_ids.push(plugin.clone().ref_id);
|
||||
|
||||
let event = plugin.build_event_to_send(
|
||||
&InternalEventPayload::BootRequest(BootRequest {
|
||||
&InternalEventPayload::BootRequest(PluginBootRequest {
|
||||
dir: dir.to_string(),
|
||||
}),
|
||||
None,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useCreateDropdownItems } from '../hooks/useCreateDropdownItems';
|
||||
import type { DropdownProps } from './core/Dropdown';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
@@ -7,7 +8,13 @@ interface Props extends Omit<DropdownProps, 'items'> {
|
||||
}
|
||||
|
||||
export function CreateDropdown({ hideFolder, children, ...props }: Props) {
|
||||
const items = useCreateDropdownItems({ hideFolder, hideIcons: true });
|
||||
const activeRequest = useActiveRequest();
|
||||
const folderId = activeRequest?.folderId ?? null;
|
||||
const items = useCreateDropdownItems({
|
||||
hideFolder,
|
||||
hideIcons: true,
|
||||
folderId,
|
||||
});
|
||||
return (
|
||||
<Dropdown items={items} {...props}>
|
||||
{children}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { httpResponsesQueryKey } from '../hooks/useHttpResponses';
|
||||
import { keyValueQueryKey } from '../hooks/useKeyValue';
|
||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||
import { useNotificationToast } from '../hooks/useNotificationToast';
|
||||
import { pluginsAtom } from '../hooks/usePlugins';
|
||||
import { useRecentCookieJars } from '../hooks/useRecentCookieJars';
|
||||
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||
@@ -67,6 +68,7 @@ export function GlobalHooks() {
|
||||
|
||||
const setSettings = useSetAtom(settingsAtom);
|
||||
const setWorkspaces = useSetAtom(workspacesAtom);
|
||||
const setPlugins = useSetAtom(pluginsAtom);
|
||||
const setHttpRequests = useSetAtom(httpRequestsAtom);
|
||||
const setGrpcRequests = useSetAtom(grpcRequestsAtom);
|
||||
const setEnvironments = useSetAtom(environmentsAtom);
|
||||
@@ -100,6 +102,8 @@ export function GlobalHooks() {
|
||||
|
||||
if (model.model === 'workspace') {
|
||||
setWorkspaces(updateModelList(model, pushToFront));
|
||||
} else if (model.model === 'plugin') {
|
||||
setPlugins(updateModelList(model, pushToFront));
|
||||
} else if (model.model === 'http_request') {
|
||||
setHttpRequests(updateModelList(model, pushToFront));
|
||||
} else if (model.model === 'grpc_request') {
|
||||
@@ -129,6 +133,8 @@ export function GlobalHooks() {
|
||||
|
||||
if (model.model === 'workspace') {
|
||||
setWorkspaces(removeById(model));
|
||||
} else if (model.model === 'plugin') {
|
||||
setPlugins(removeById(model));
|
||||
} else if (model.model === 'http_request') {
|
||||
setHttpRequests(removeById(model));
|
||||
} else if (model.model === 'http_response') {
|
||||
@@ -162,15 +168,16 @@ export function GlobalHooks() {
|
||||
document.documentElement.style.setProperty('--editor-font-size', `${editorFontSize}px`);
|
||||
}, [settings]);
|
||||
|
||||
// Handle Zoom. Note, Mac handles it in app menu, so need to also handle keyboard
|
||||
// Handle Zoom.
|
||||
// Note, Mac handles it in the app menu, so need to also handle keyboard
|
||||
// shortcuts for Windows/Linux
|
||||
const zoom = useZoom();
|
||||
useHotKey('app.zoom_in', () => zoom.zoomIn);
|
||||
useListenToTauriEvent('zoom_in', () => zoom.zoomIn);
|
||||
useHotKey('app.zoom_out', () => zoom.zoomOut);
|
||||
useListenToTauriEvent('zoom_out', () => zoom.zoomOut);
|
||||
useHotKey('app.zoom_reset', () => zoom.zoomReset);
|
||||
useListenToTauriEvent('zoom_reset', () => zoom.zoomReset);
|
||||
useHotKey('app.zoom_in', zoom.zoomIn);
|
||||
useListenToTauriEvent('zoom_in', zoom.zoomIn);
|
||||
useHotKey('app.zoom_out', zoom.zoomOut);
|
||||
useListenToTauriEvent('zoom_out', zoom.zoomOut);
|
||||
useHotKey('app.zoom_reset', zoom.zoomReset);
|
||||
useListenToTauriEvent('zoom_reset', zoom.zoomReset);
|
||||
|
||||
const copy = useCopy();
|
||||
useListenToTauriEvent('generate_theme_css', () => {
|
||||
|
||||
@@ -128,7 +128,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
||||
return (
|
||||
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto]">
|
||||
<Editor
|
||||
contentType="application/graphql"
|
||||
language="graphql"
|
||||
defaultValue={query ?? ''}
|
||||
format={formatGraphQL}
|
||||
heightMode="auto"
|
||||
@@ -144,7 +144,7 @@ export function GraphQLEditor({ defaultValue, onChange, baseRequest, ...extraEdi
|
||||
</Separator>
|
||||
<Editor
|
||||
format={tryFormatJson}
|
||||
contentType="application/json"
|
||||
language="json"
|
||||
defaultValue={JSON.stringify(variables, null, 2)}
|
||||
heightMode="auto"
|
||||
onChange={handleChangeVariables}
|
||||
|
||||
@@ -185,7 +185,7 @@ export function GrpcEditor({
|
||||
return (
|
||||
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto_auto_minmax(0,auto)]">
|
||||
<Editor
|
||||
contentType="application/json"
|
||||
language="json"
|
||||
autocompleteVariables
|
||||
useTemplating
|
||||
forceUpdateKey={request.id}
|
||||
|
||||
@@ -45,10 +45,9 @@ export function GrpcProtoSelection({ requestId }: Props) {
|
||||
multiple: true,
|
||||
filters: [{ name: 'Proto Files', extensions: ['proto'] }],
|
||||
});
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
const newFiles = selected.map((f) => f.path).filter((p) => !protoFiles.includes(p));
|
||||
if (selected == null) return;
|
||||
|
||||
const newFiles = selected.filter((p) => !protoFiles.includes(p));
|
||||
await protoFilesKv.set([...protoFiles, ...newFiles]);
|
||||
await grpc.reflect.refetch();
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { HttpResponse } from '@yaakapp/api';
|
||||
import { useCopyHttpResponse } from '../hooks/useCopyHttpResponse';
|
||||
import { useDeleteHttpResponse } from '../hooks/useDeleteHttpResponse';
|
||||
import { useDeleteHttpResponses } from '../hooks/useDeleteHttpResponses';
|
||||
import { useSaveResponse } from '../hooks/useSaveResponse';
|
||||
@@ -25,6 +26,7 @@ export const RecentResponsesDropdown = function ResponsePane({
|
||||
const deleteAllResponses = useDeleteHttpResponses(activeResponse?.requestId);
|
||||
const latestResponseId = responses[0]?.id ?? 'n/a';
|
||||
const saveResponse = useSaveResponse(activeResponse);
|
||||
const copyResponse = useCopyHttpResponse(activeResponse);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
@@ -37,6 +39,14 @@ export const RecentResponsesDropdown = function ResponsePane({
|
||||
hidden: responses.length === 0,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
{
|
||||
key: 'copy',
|
||||
label: 'Copy to Clipboard',
|
||||
onSelect: copyResponse.mutate,
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
hidden: responses.length === 0,
|
||||
disabled: responses.length === 0,
|
||||
},
|
||||
{
|
||||
key: 'clear-single',
|
||||
label: 'Delete',
|
||||
|
||||
@@ -8,10 +8,12 @@ import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders';
|
||||
import { useImportCurl } from '../hooks/useImportCurl';
|
||||
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
|
||||
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
||||
import { useRequestEditorEvent } from '../hooks/useRequestEditor';
|
||||
import { useRequests } from '../hooks/useRequests';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
||||
import { languageFromContentType } from '../lib/contentType';
|
||||
import { tryFormatJson } from '../lib/formatters';
|
||||
import {
|
||||
AUTH_TYPE_BASIC,
|
||||
@@ -33,6 +35,7 @@ import { CountBadge } from './core/CountBadge';
|
||||
import { Editor } from './core/Editor';
|
||||
import type { GenericCompletionOption } from './core/Editor/genericCompletion';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import type { Pair } from './core/PairEditor';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import { EmptyStateText } from './EmptyStateText';
|
||||
@@ -51,7 +54,14 @@ interface Props {
|
||||
activeRequest: HttpRequest;
|
||||
}
|
||||
|
||||
const useActiveTab = createGlobalState<string>('body');
|
||||
const useActiveTab = createGlobalState<Record<string, string>>({});
|
||||
|
||||
const TAB_BODY = 'body';
|
||||
const TAB_PARAMS = 'params';
|
||||
const TAB_HEADERS = 'headers';
|
||||
const TAB_AUTH = 'auth';
|
||||
|
||||
const DEFAULT_TAB = TAB_BODY;
|
||||
|
||||
export const RequestPane = memo(function RequestPane({
|
||||
style,
|
||||
@@ -62,7 +72,7 @@ export const RequestPane = memo(function RequestPane({
|
||||
const requests = useRequests();
|
||||
const activeRequestId = activeRequest.id;
|
||||
const updateRequest = useUpdateAnyHttpRequest();
|
||||
const [activeTab, setActiveTab] = useActiveTab();
|
||||
const [activeTabs, setActiveTabs] = useActiveTab();
|
||||
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
|
||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
||||
const contentType = useContentTypeFromHeaders(activeRequest.headers);
|
||||
@@ -88,10 +98,27 @@ export const RequestPane = memo(function RequestPane({
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const { urlParameterPairs, urlParametersKey } = useMemo(() => {
|
||||
const placeholderNames = Array.from(activeRequest.url.matchAll(/\/(:[^/]+)/g)).map(
|
||||
(m) => m[1] ?? '',
|
||||
);
|
||||
const nonEmptyParameters = activeRequest.urlParameters.filter((p) => p.name || p.value);
|
||||
const items: Pair[] = [...nonEmptyParameters];
|
||||
for (const name of placeholderNames) {
|
||||
const index = items.findIndex((p) => p.name === name);
|
||||
if (index >= 0) {
|
||||
items[index]!.readOnlyName = true;
|
||||
} else {
|
||||
items.push({ name, value: '', enabled: true, readOnlyName: true });
|
||||
}
|
||||
}
|
||||
return { urlParameterPairs: items, urlParametersKey: placeholderNames.join(',') };
|
||||
}, [activeRequest.url, activeRequest.urlParameters]);
|
||||
|
||||
const tabs: TabItem[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
value: 'body',
|
||||
value: TAB_BODY,
|
||||
options: {
|
||||
value: activeRequest.bodyType,
|
||||
items: [
|
||||
@@ -157,16 +184,16 @@ export const RequestPane = memo(function RequestPane({
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 'params',
|
||||
value: TAB_PARAMS,
|
||||
label: (
|
||||
<div className="flex items-center">
|
||||
Params
|
||||
<CountBadge count={activeRequest.urlParameters.filter((p) => p.name).length} />
|
||||
<CountBadge count={urlParameterPairs.length} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'headers',
|
||||
value: TAB_HEADERS,
|
||||
label: (
|
||||
<div className="flex items-center">
|
||||
Headers
|
||||
@@ -175,7 +202,7 @@ export const RequestPane = memo(function RequestPane({
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'auth',
|
||||
value: TAB_AUTH,
|
||||
label: 'Auth',
|
||||
options: {
|
||||
value: activeRequest.authenticationType,
|
||||
@@ -211,11 +238,11 @@ export const RequestPane = memo(function RequestPane({
|
||||
activeRequest.bodyType,
|
||||
activeRequest.headers,
|
||||
activeRequest.method,
|
||||
activeRequest.urlParameters,
|
||||
activeRequestId,
|
||||
handleContentTypeChange,
|
||||
toast,
|
||||
updateRequest,
|
||||
urlParameterPairs,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -269,6 +296,18 @@ export const RequestPane = memo(function RequestPane({
|
||||
const { updateKey } = useRequestUpdateKey(activeRequestId ?? null);
|
||||
const importCurl = useImportCurl();
|
||||
|
||||
const activeTab = activeTabs[activeRequestId] ?? DEFAULT_TAB;
|
||||
const setActiveTab = useCallback(
|
||||
(tab: string) => {
|
||||
setActiveTabs((r) => ({ ...r, [activeRequest.id]: tab }));
|
||||
},
|
||||
[activeRequest.id, setActiveTabs],
|
||||
);
|
||||
|
||||
useRequestEditorEvent('request_pane.focus_tab', () => {
|
||||
setActiveTab(TAB_PARAMS);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
@@ -315,13 +354,14 @@ export const RequestPane = memo(function RequestPane({
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<Tabs
|
||||
key={activeRequest.id} // Freshen tabs on request change
|
||||
value={activeTab}
|
||||
label="Request"
|
||||
onChangeValue={setActiveTab}
|
||||
tabs={tabs}
|
||||
tabListClassName="mt-2 !mb-1.5"
|
||||
>
|
||||
<TabContent value="auth">
|
||||
<TabContent value={TAB_AUTH}>
|
||||
{activeRequest.authenticationType === AUTH_TYPE_BASIC ? (
|
||||
<BasicAuth key={forceUpdateKey} request={activeRequest} />
|
||||
) : activeRequest.authenticationType === AUTH_TYPE_BEARER ? (
|
||||
@@ -332,21 +372,21 @@ export const RequestPane = memo(function RequestPane({
|
||||
</EmptyStateText>
|
||||
)}
|
||||
</TabContent>
|
||||
<TabContent value="headers">
|
||||
<TabContent value={TAB_HEADERS}>
|
||||
<HeadersEditor
|
||||
forceUpdateKey={`${forceUpdateHeaderEditorKey}::${forceUpdateKey}`}
|
||||
headers={activeRequest.headers}
|
||||
onChange={handleHeadersChange}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value="params">
|
||||
<TabContent value={TAB_PARAMS}>
|
||||
<UrlParametersEditor
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
urlParameters={activeRequest.urlParameters}
|
||||
forceUpdateKey={forceUpdateKey + urlParametersKey}
|
||||
pairs={urlParameterPairs}
|
||||
onChange={handleUrlParametersChange}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value="body">
|
||||
<TabContent value={TAB_BODY}>
|
||||
{activeRequest.bodyType === BODY_TYPE_JSON ? (
|
||||
<Editor
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
@@ -355,7 +395,7 @@ export const RequestPane = memo(function RequestPane({
|
||||
placeholder="..."
|
||||
heightMode={fullHeight ? 'full' : 'auto'}
|
||||
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
||||
contentType="application/json"
|
||||
language="json"
|
||||
onChange={handleBodyTextChange}
|
||||
format={tryFormatJson}
|
||||
/>
|
||||
@@ -367,7 +407,7 @@ export const RequestPane = memo(function RequestPane({
|
||||
placeholder="..."
|
||||
heightMode={fullHeight ? 'full' : 'auto'}
|
||||
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
||||
contentType="text/xml"
|
||||
language="xml"
|
||||
onChange={handleBodyTextChange}
|
||||
/>
|
||||
) : activeRequest.bodyType === BODY_TYPE_GRAPHQL ? (
|
||||
@@ -402,6 +442,7 @@ export const RequestPane = memo(function RequestPane({
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
useTemplating
|
||||
autocompleteVariables
|
||||
language={languageFromContentType(contentType)}
|
||||
placeholder="..."
|
||||
heightMode={fullHeight ? 'full' : 'auto'}
|
||||
defaultValue={`${activeRequest.body?.text ?? ''}`}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { HttpRequest } from '@yaakapp/api';
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { createGlobalState } from 'react-use';
|
||||
import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders';
|
||||
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
||||
import { useResponseViewMode } from '../hooks/useResponseViewMode';
|
||||
import type { HttpRequest } from '@yaakapp/api';
|
||||
import { isResponseLoading } from '../lib/models';
|
||||
import { Banner } from './core/Banner';
|
||||
import { CountBadge } from './core/CountBadge';
|
||||
@@ -23,11 +23,10 @@ import { ResponseHeaders } from './ResponseHeaders';
|
||||
import { ResponseInfo } from './ResponseInfo';
|
||||
import { AudioViewer } from './responseViewers/AudioViewer';
|
||||
import { CsvViewer } from './responseViewers/CsvViewer';
|
||||
import { HTMLOrTextViewer } from './responseViewers/HTMLOrTextViewer';
|
||||
import { ImageViewer } from './responseViewers/ImageViewer';
|
||||
import { PdfViewer } from './responseViewers/PdfViewer';
|
||||
import { TextViewer } from './responseViewers/TextViewer';
|
||||
import { VideoViewer } from './responseViewers/VideoViewer';
|
||||
import { WebPageViewer } from './responseViewers/WebPageViewer';
|
||||
|
||||
interface Props {
|
||||
style?: CSSProperties;
|
||||
@@ -35,18 +34,30 @@ interface Props {
|
||||
activeRequest: HttpRequest;
|
||||
}
|
||||
|
||||
const useActiveTab = createGlobalState<string>('body');
|
||||
const useActiveTab = createGlobalState<Record<string, string>>({});
|
||||
|
||||
const TAB_BODY = 'body';
|
||||
const TAB_HEADERS = 'headers';
|
||||
const TAB_INFO = 'info';
|
||||
const DEFAULT_TAB = TAB_BODY;
|
||||
|
||||
export const ResponsePane = memo(function ResponsePane({ style, className, activeRequest }: Props) {
|
||||
const { activeResponse, setPinnedResponseId, responses } = usePinnedHttpResponse(activeRequest);
|
||||
const [viewMode, setViewMode] = useResponseViewMode(activeResponse?.requestId);
|
||||
const [activeTab, setActiveTab] = useActiveTab();
|
||||
const [activeTabs, setActiveTabs] = useActiveTab();
|
||||
const contentType = useContentTypeFromHeaders(activeResponse?.headers ?? null);
|
||||
const activeTab = activeTabs[activeRequest.id] ?? DEFAULT_TAB;
|
||||
const setActiveTab = useCallback(
|
||||
(tab: string) => {
|
||||
setActiveTabs((r) => ({ ...r, [activeRequest.id]: tab }));
|
||||
},
|
||||
[activeRequest.id, setActiveTabs],
|
||||
);
|
||||
|
||||
const tabs = useMemo<TabItem[]>(
|
||||
() => [
|
||||
{
|
||||
value: 'body',
|
||||
value: TAB_BODY,
|
||||
label: 'Preview Mode',
|
||||
options: {
|
||||
value: viewMode,
|
||||
@@ -58,6 +69,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
||||
},
|
||||
},
|
||||
{
|
||||
value: TAB_HEADERS,
|
||||
label: (
|
||||
<div className="flex items-center">
|
||||
Headers
|
||||
@@ -66,11 +78,10 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
value: 'headers',
|
||||
},
|
||||
{
|
||||
value: TAB_INFO,
|
||||
label: 'Info',
|
||||
value: 'info',
|
||||
},
|
||||
],
|
||||
[activeResponse?.headers, contentType, setViewMode, viewMode],
|
||||
@@ -143,20 +154,15 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
||||
</Banner>
|
||||
) : (
|
||||
<Tabs
|
||||
key={activeRequest.id} // Freshen tabs on request change
|
||||
value={activeTab}
|
||||
onChangeValue={setActiveTab}
|
||||
label="Response"
|
||||
tabs={tabs}
|
||||
label="Response"
|
||||
className="ml-3 mr-3 mb-3"
|
||||
tabListClassName="mt-1.5"
|
||||
>
|
||||
<TabContent value="headers">
|
||||
<ResponseHeaders response={activeResponse} />
|
||||
</TabContent>
|
||||
<TabContent value="info">
|
||||
<ResponseInfo response={activeResponse} />
|
||||
</TabContent>
|
||||
<TabContent value="body">
|
||||
<TabContent value={TAB_BODY}>
|
||||
{!activeResponse.contentLength ? (
|
||||
<div className="pb-2 h-full">
|
||||
<EmptyStateText>Empty Body</EmptyStateText>
|
||||
@@ -171,18 +177,22 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
||||
<PdfViewer response={activeResponse} />
|
||||
) : contentType?.match(/csv|tab-separated/) ? (
|
||||
<CsvViewer className="pb-2" response={activeResponse} />
|
||||
) : viewMode === 'pretty' && contentType?.includes('html') ? (
|
||||
<WebPageViewer response={activeResponse} />
|
||||
) : (
|
||||
// ) : viewMode === 'pretty' && contentType?.includes('json') ? (
|
||||
// <JsonAttributeTree attrValue={activeResponse} />
|
||||
<TextViewer
|
||||
className="-mr-2 bg-surface" // Pull to the right
|
||||
<HTMLOrTextViewer
|
||||
textViewerClassName="-mr-2 bg-surface" // Pull to the right
|
||||
response={activeResponse}
|
||||
pretty={viewMode === 'pretty'}
|
||||
/>
|
||||
)}
|
||||
</TabContent>
|
||||
<TabContent value={TAB_HEADERS}>
|
||||
<ResponseHeaders response={activeResponse} />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_INFO}>
|
||||
<ResponseInfo response={activeResponse} />
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -9,22 +9,32 @@ import { HStack } from './core/Stacks';
|
||||
type Props = ButtonProps & {
|
||||
onChange: (value: { filePath: string | null; contentType: string | null }) => void;
|
||||
filePath: string | null;
|
||||
directory?: boolean;
|
||||
inline?: boolean;
|
||||
noun?: string;
|
||||
};
|
||||
|
||||
// Special character to insert ltr text in rtl element
|
||||
const rtlEscapeChar = <>‎</>;
|
||||
|
||||
export function SelectFile({ onChange, filePath, inline, className }: Props) {
|
||||
export function SelectFile({
|
||||
onChange,
|
||||
filePath,
|
||||
inline,
|
||||
className,
|
||||
directory,
|
||||
noun,
|
||||
size = 'sm',
|
||||
...props
|
||||
}: Props) {
|
||||
const handleClick = async () => {
|
||||
const selected = await open({
|
||||
const filePath = await open({
|
||||
title: 'Select File',
|
||||
multiple: false,
|
||||
directory,
|
||||
});
|
||||
if (selected == null) return;
|
||||
|
||||
const filePath = selected.path;
|
||||
const contentType = typeof filePath === 'string' && filePath ? mime.getType(filePath) : null;
|
||||
if (filePath == null) return;
|
||||
const contentType = filePath ? mime.getType(filePath) : null;
|
||||
onChange({ filePath, contentType });
|
||||
};
|
||||
|
||||
@@ -32,31 +42,41 @@ export function SelectFile({ onChange, filePath, inline, className }: Props) {
|
||||
onChange({ filePath: null, contentType: null });
|
||||
};
|
||||
|
||||
const itemLabel = noun ?? (directory ? 'Folder' : 'File');
|
||||
const selectOrChange = (filePath ? 'Change ' : 'Select ') + itemLabel;
|
||||
|
||||
return (
|
||||
<HStack space={1.5} className="group relative justify-stretch">
|
||||
<HStack space={1.5} className="group relative justify-stretch overflow-hidden">
|
||||
<Button
|
||||
className={classNames(className, 'font-mono text-xs rtl', inline && 'w-full')}
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={handleClick}
|
||||
size={size}
|
||||
{...props}
|
||||
>
|
||||
{rtlEscapeChar}
|
||||
{inline ? <>{filePath || 'Select File'}</> : <>Select File</>}
|
||||
{inline ? filePath || selectOrChange : selectOrChange}
|
||||
</Button>
|
||||
{!inline && (
|
||||
<>
|
||||
{filePath && (
|
||||
<IconButton
|
||||
size="sm"
|
||||
size={size}
|
||||
variant="border"
|
||||
icon="x"
|
||||
title="Unset File"
|
||||
title={'Unset ' + itemLabel}
|
||||
onClick={handleClear}
|
||||
/>
|
||||
)}
|
||||
<div className="text-sm font-mono truncate rtl pr-3 text">
|
||||
<div
|
||||
className={classNames(
|
||||
'font-mono truncate rtl pl-1.5 pr-3 text-text',
|
||||
size === 'xs' && 'text-xs',
|
||||
size === 'sm' && 'text-sm',
|
||||
)}
|
||||
>
|
||||
{rtlEscapeChar}
|
||||
{filePath ?? 'No file selected'}
|
||||
{filePath ?? `No ${itemLabel.toLowerCase()} selected`}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -10,13 +10,15 @@ import { HeaderSize } from '../HeaderSize';
|
||||
import { WindowControls } from '../WindowControls';
|
||||
import { SettingsAppearance } from './SettingsAppearance';
|
||||
import { SettingsGeneral } from './SettingsGeneral';
|
||||
import { SettingsPlugins } from './SettingsPlugins';
|
||||
|
||||
enum Tab {
|
||||
General = 'general',
|
||||
Appearance = 'appearance',
|
||||
Plugins = 'plugins',
|
||||
}
|
||||
|
||||
const tabs = [Tab.General, Tab.Appearance];
|
||||
const tabs = [Tab.General, Tab.Appearance, Tab.Plugins];
|
||||
|
||||
export const Settings = () => {
|
||||
const osInfo = useOsInfo();
|
||||
@@ -58,6 +60,9 @@ export const Settings = () => {
|
||||
<TabContent value={Tab.Appearance} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<SettingsAppearance />
|
||||
</TabContent>
|
||||
<TabContent value={Tab.Plugins} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<SettingsPlugins />
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -198,7 +198,7 @@ export function SettingsAppearance() {
|
||||
'};',
|
||||
].join('\n')}
|
||||
heightMode="auto"
|
||||
contentType="application/javascript"
|
||||
language="javascript"
|
||||
/>
|
||||
</VStack>
|
||||
</VStack>
|
||||
|
||||
@@ -117,7 +117,7 @@ export function SettingsDesign() {
|
||||
'};',
|
||||
].join('\n')}
|
||||
heightMode="auto"
|
||||
contentType="application/javascript"
|
||||
language="javascript"
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
|
||||
95
src-web/components/Settings/SettingsPlugins.tsx
Normal file
95
src-web/components/Settings/SettingsPlugins.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import type { Plugin } from '@yaakapp/api';
|
||||
import React from 'react';
|
||||
import { useCreatePlugin } from '../../hooks/useCreatePlugin';
|
||||
import { usePluginInfo } from '../../hooks/usePluginInfo';
|
||||
import { usePlugins, useRefreshPlugins } from '../../hooks/usePlugins';
|
||||
import { Button } from '../core/Button';
|
||||
import { Checkbox } from '../core/Checkbox';
|
||||
import { IconButton } from '../core/IconButton';
|
||||
import { InlineCode } from '../core/InlineCode';
|
||||
import { HStack } from '../core/Stacks';
|
||||
import { SelectFile } from '../SelectFile';
|
||||
|
||||
export function SettingsPlugins() {
|
||||
const [directory, setDirectory] = React.useState<string | null>(null);
|
||||
const plugins = usePlugins();
|
||||
const createPlugin = useCreatePlugin();
|
||||
const refreshPlugins = useRefreshPlugins();
|
||||
return (
|
||||
<div className="grid grid-rows-[minmax(0,1fr)_auto] h-full">
|
||||
<table className="w-full text-sm mb-auto min-w-full max-w-full divide-y divide-surface-highlight">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th className="py-2 text-left">Plugin</th>
|
||||
<th className="py-2 text-right">Version</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-surface-highlight">
|
||||
{plugins.map((p) => (
|
||||
<PluginInfo key={p.id} plugin={p} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
if (directory == null) return;
|
||||
createPlugin.mutate(directory);
|
||||
setDirectory(null);
|
||||
}}
|
||||
>
|
||||
<footer className="grid grid-cols-[minmax(0,1fr)_auto] -mx-4 py-2 px-4 border-t bg-surface-highlight border-border-subtle min-w-0">
|
||||
<SelectFile
|
||||
size="xs"
|
||||
noun="Plugin"
|
||||
directory
|
||||
onChange={({ filePath }) => setDirectory(filePath)}
|
||||
filePath={directory}
|
||||
/>
|
||||
<HStack>
|
||||
{directory && (
|
||||
<Button size="xs" type="submit" color="primary" className="ml-auto">
|
||||
Add Plugin
|
||||
</Button>
|
||||
)}
|
||||
<IconButton
|
||||
size="sm"
|
||||
icon="refresh"
|
||||
title="Reload plugins"
|
||||
spin={refreshPlugins.isPending}
|
||||
onClick={() => refreshPlugins.mutate()}
|
||||
/>
|
||||
</HStack>
|
||||
</footer>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PluginInfo({ plugin }: { plugin: Plugin }) {
|
||||
const pluginInfo = usePluginInfo(plugin.id);
|
||||
if (pluginInfo.data == null) return null;
|
||||
return (
|
||||
<tr className="group">
|
||||
<td className="pr-2">
|
||||
<Checkbox hideLabel checked={true} title="foo" onChange={() => null} />
|
||||
</td>
|
||||
<td className="py-2 select-text cursor-text w-full">
|
||||
<InlineCode>{pluginInfo.data?.name}</InlineCode>
|
||||
</td>
|
||||
<td className="py-2 select-text cursor-text text-right">
|
||||
<InlineCode>{pluginInfo.data?.version}</InlineCode>
|
||||
</td>
|
||||
<td className="py-2 select-text cursor-text pl-2">
|
||||
<IconButton
|
||||
size="sm"
|
||||
icon="trash"
|
||||
title="Uninstall plugin"
|
||||
className="text-text-subtlest"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@@ -67,7 +67,7 @@ export const UrlBar = memo(function UrlBar({
|
||||
wrapLines={isFocused}
|
||||
hideLabel
|
||||
useTemplating
|
||||
contentType="url"
|
||||
language="url"
|
||||
className="pl-0 pr-1.5 py-0.5"
|
||||
name="url"
|
||||
label="Enter URL"
|
||||
|
||||
@@ -1,23 +1,45 @@
|
||||
import type { HttpRequest } from '@yaakapp/api';
|
||||
import { useRequestEditorEvent } from '../hooks/useRequestEditor';
|
||||
import type { PairEditorRef } from './core/PairEditor';
|
||||
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { useRef } from 'react';
|
||||
|
||||
type Props = {
|
||||
forceUpdateKey: string;
|
||||
urlParameters: HttpRequest['headers'];
|
||||
pairs: HttpRequest['headers'];
|
||||
onChange: (headers: HttpRequest['urlParameters']) => void;
|
||||
};
|
||||
|
||||
export function UrlParametersEditor({ urlParameters, forceUpdateKey, onChange }: Props) {
|
||||
export function UrlParametersEditor({ pairs, forceUpdateKey, onChange }: Props) {
|
||||
const pairEditor = useRef<PairEditorRef>(null);
|
||||
|
||||
useRequestEditorEvent(
|
||||
'request_params.focus_value',
|
||||
(name) => {
|
||||
const pairIndex = pairs.findIndex((p) => p.name === name);
|
||||
if (pairIndex >= 0) {
|
||||
pairEditor.current?.focusValue(pairIndex);
|
||||
} else {
|
||||
console.log("Couldn't find pair to focus", { name, pairs });
|
||||
}
|
||||
},
|
||||
[pairs],
|
||||
);
|
||||
|
||||
return (
|
||||
<PairOrBulkEditor
|
||||
preferenceName="url_parameters"
|
||||
valueAutocompleteVariables
|
||||
nameAutocompleteVariables
|
||||
namePlaceholder="param_name"
|
||||
valuePlaceholder="Value"
|
||||
pairs={urlParameters}
|
||||
onChange={onChange}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
/>
|
||||
<VStack className="h-full">
|
||||
<PairOrBulkEditor
|
||||
ref={pairEditor}
|
||||
preferenceName="url_parameters"
|
||||
valueAutocompleteVariables
|
||||
nameAutocompleteVariables
|
||||
namePlaceholder="param_name"
|
||||
valuePlaceholder="Value"
|
||||
pairs={pairs}
|
||||
onChange={onChange}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
/>
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export function WindowControls({ className, onlyX }: Props) {
|
||||
)}
|
||||
<Button
|
||||
color="custom"
|
||||
className="!h-full px-4 text-text-subtle rounded-none hocus:bg-fg-danger hocus:text"
|
||||
className="!h-full px-4 text-text-subtle rounded-none hocus:bg-danger hocus:text"
|
||||
onClick={() => getCurrentWebviewWindow().close()}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
|
||||
@@ -14,8 +14,9 @@ export function Banner({ children, className, color = 'secondary' }: Props) {
|
||||
className={classNames(
|
||||
className,
|
||||
`x-theme-banner--${color}`,
|
||||
'border border-dashed italic px-3 py-2 rounded select-auto cursor-text',
|
||||
'border-border-subtle bg-surface-highlight text',
|
||||
'border border-dashed border-border-subtle bg-surface-highlight',
|
||||
'italic px-3 py-2 rounded select-auto cursor-text',
|
||||
'overflow-x-auto text-text',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -36,18 +36,19 @@ export function BulkPairEditor({
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
placeholder={`${namePlaceholder ?? 'name'}: ${valuePlaceholder ?? 'value'}`}
|
||||
defaultValue={pairsText}
|
||||
contentType="pairs"
|
||||
language="pairs"
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function lineToPair(l: string): PairEditorProps['pairs'][0] {
|
||||
const [name, ...values] = l.split(':');
|
||||
function lineToPair(line: string): PairEditorProps['pairs'][0] {
|
||||
const [, name, value] = line.match(/^(:?[^:]+):\s+([^$]*)/) ?? [];
|
||||
|
||||
const pair: PairEditorProps['pairs'][0] = {
|
||||
enabled: true,
|
||||
name: (name ?? '').trim(),
|
||||
value: values.join(':').trim(),
|
||||
value: (value ?? '').trim(),
|
||||
};
|
||||
return pair;
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
|
||||
size === 'md' && 'h-md px-3 rounded-md',
|
||||
size === 'sm' && 'h-sm px-2.5 rounded-md',
|
||||
size === 'xs' && 'h-xs px-2 text-sm rounded-md',
|
||||
size === '2xs' && 'h-5 px-1 text-xs rounded',
|
||||
size === '2xs' && 'h-2xs px-1 text-xs rounded',
|
||||
|
||||
// Solids
|
||||
variant === 'solid' && 'border-transparent',
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
}
|
||||
|
||||
.cm-line {
|
||||
@apply w-full; /* Important! Ensure it spans the entire width */
|
||||
@apply w-full;
|
||||
/* Important! Ensure it spans the entire width */
|
||||
@apply w-full text-text pl-1 pr-1.5;
|
||||
}
|
||||
|
||||
@@ -169,9 +170,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.cm-wrapper.cm-readonly .cm-editor {
|
||||
.cm-cursor {
|
||||
@apply border-danger !important;
|
||||
/* Cursor and mouse cursor for readonly mode */
|
||||
.cm-wrapper.cm-readonly {
|
||||
.cm-editor .cm-cursor {
|
||||
@apply hidden !important;
|
||||
}
|
||||
|
||||
&.cm-singleline .cm-line {
|
||||
@apply cursor-default;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from 'react';
|
||||
import { useActiveEnvironmentVariables } from '../../../hooks/useActiveEnvironmentVariables';
|
||||
import { parseTemplate } from '../../../hooks/useParseTemplate';
|
||||
import { useRequestEditor } from '../../../hooks/useRequestEditor';
|
||||
import { useSettings } from '../../../hooks/useSettings';
|
||||
import { useTemplateFunctions } from '../../../hooks/useTemplateFunctions';
|
||||
import { useDialog } from '../../DialogContext';
|
||||
@@ -39,10 +40,11 @@ export { formatSdl } from 'format-graphql';
|
||||
export interface EditorProps {
|
||||
id?: string;
|
||||
readOnly?: boolean;
|
||||
disabled?: boolean;
|
||||
type?: 'text' | 'password';
|
||||
className?: string;
|
||||
heightMode?: 'auto' | 'full';
|
||||
contentType?: string | null;
|
||||
language?: 'javascript' | 'json' | 'html' | 'xml' | 'graphql' | 'url' | 'pairs' | 'text';
|
||||
forceUpdateKey?: string | number;
|
||||
autoFocus?: boolean;
|
||||
autoSelect?: boolean;
|
||||
@@ -71,7 +73,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
readOnly,
|
||||
type = 'text',
|
||||
heightMode,
|
||||
contentType,
|
||||
language = 'text',
|
||||
autoFocus,
|
||||
autoSelect,
|
||||
placeholder,
|
||||
@@ -226,12 +228,20 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
[dialog],
|
||||
);
|
||||
|
||||
// Update language extension when contentType changes
|
||||
const { focusParamValue } = useRequestEditor();
|
||||
const onClickPathParameter = useCallback(
|
||||
async (name: string) => {
|
||||
focusParamValue(name);
|
||||
},
|
||||
[focusParamValue],
|
||||
);
|
||||
|
||||
// Update the language extension when the language changes
|
||||
useEffect(() => {
|
||||
if (cm.current === null) return;
|
||||
const { view, languageCompartment } = cm.current;
|
||||
const ext = getLanguageExtension({
|
||||
contentType,
|
||||
language,
|
||||
environmentVariables,
|
||||
useTemplating,
|
||||
autocomplete,
|
||||
@@ -239,10 +249,11 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
onClickFunction,
|
||||
onClickVariable,
|
||||
onClickMissingVariable,
|
||||
onClickPathParameter,
|
||||
});
|
||||
view.dispatch({ effects: languageCompartment.reconfigure(ext) });
|
||||
}, [
|
||||
contentType,
|
||||
language,
|
||||
autocomplete,
|
||||
useTemplating,
|
||||
environmentVariables,
|
||||
@@ -250,6 +261,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
onClickFunction,
|
||||
onClickVariable,
|
||||
onClickMissingVariable,
|
||||
onClickPathParameter,
|
||||
]);
|
||||
|
||||
// Initialize the editor when ref mounts
|
||||
@@ -265,7 +277,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
try {
|
||||
const languageCompartment = new Compartment();
|
||||
const langExt = getLanguageExtension({
|
||||
contentType,
|
||||
language,
|
||||
useTemplating,
|
||||
autocomplete,
|
||||
environmentVariables,
|
||||
@@ -273,6 +285,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
onClickVariable,
|
||||
onClickFunction,
|
||||
onClickMissingVariable,
|
||||
onClickPathParameter,
|
||||
});
|
||||
|
||||
const state = EditorState.create({
|
||||
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
} from '@codemirror/view';
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import type { EnvironmentVariable, TemplateFunction } from '@yaakapp/api';
|
||||
import { graphql, graphqlLanguageSupport } from 'cm6-graphql';
|
||||
import { graphql } from 'cm6-graphql';
|
||||
import { EditorView } from 'codemirror';
|
||||
import type { EditorProps } from './index';
|
||||
import { pairs } from './pairs/extension';
|
||||
@@ -46,6 +46,10 @@ export const syntaxHighlightStyle = HighlightStyle.define([
|
||||
color: 'var(--textSubtlest)',
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
{
|
||||
tag: [t.emphasis],
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
{
|
||||
tag: [t.paren, t.bracket, t.brace],
|
||||
color: 'var(--textSubtle)',
|
||||
@@ -64,19 +68,19 @@ export const syntaxHighlightStyle = HighlightStyle.define([
|
||||
|
||||
const syntaxTheme = EditorView.theme({}, { dark: true });
|
||||
|
||||
const syntaxExtensions: Record<string, LanguageSupport> = {
|
||||
'application/graphql': graphqlLanguageSupport(),
|
||||
'application/json': json(),
|
||||
'application/javascript': javascript(),
|
||||
'text/html': xml(), // HTML as XML because HTML is oddly slow
|
||||
'application/xml': xml(),
|
||||
'text/xml': xml(),
|
||||
const syntaxExtensions: Record<NonNullable<EditorProps['language']>, LanguageSupport | null> = {
|
||||
graphql: null,
|
||||
json: json(),
|
||||
javascript: javascript(),
|
||||
html: xml(), // HTML as XML because HTML is oddly slow
|
||||
xml: xml(),
|
||||
url: url(),
|
||||
pairs: pairs(),
|
||||
text: text(),
|
||||
};
|
||||
|
||||
export function getLanguageExtension({
|
||||
contentType,
|
||||
language,
|
||||
useTemplating = false,
|
||||
environmentVariables,
|
||||
autocomplete,
|
||||
@@ -84,18 +88,20 @@ export function getLanguageExtension({
|
||||
onClickVariable,
|
||||
onClickFunction,
|
||||
onClickMissingVariable,
|
||||
onClickPathParameter,
|
||||
}: {
|
||||
environmentVariables: EnvironmentVariable[];
|
||||
templateFunctions: TemplateFunction[];
|
||||
onClickFunction: (option: TemplateFunction, tagValue: string, startPos: number) => void;
|
||||
onClickVariable: (option: EnvironmentVariable, tagValue: string, startPos: number) => void;
|
||||
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void;
|
||||
} & Pick<EditorProps, 'contentType' | 'useTemplating' | 'autocomplete'>) {
|
||||
const justContentType = contentType?.split(';')[0] ?? contentType ?? '';
|
||||
if (justContentType === 'application/graphql') {
|
||||
onClickPathParameter: (name: string) => void;
|
||||
} & Pick<EditorProps, 'language' | 'useTemplating' | 'autocomplete'>) {
|
||||
if (language === 'graphql') {
|
||||
return graphql();
|
||||
}
|
||||
const base = syntaxExtensions[justContentType] ?? text();
|
||||
|
||||
const base = syntaxExtensions[language ?? 'text'] ?? text();
|
||||
if (!useTemplating) {
|
||||
return base;
|
||||
}
|
||||
@@ -108,6 +114,7 @@ export function getLanguageExtension({
|
||||
onClickFunction,
|
||||
onClickVariable,
|
||||
onClickMissingVariable,
|
||||
onClickPathParameter,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@top pairs { (Key? Sep Value)* }
|
||||
@top pairs { (Key Sep Value "\n")* }
|
||||
|
||||
@tokens {
|
||||
Sep { ":" }
|
||||
Key { ![:]+ }
|
||||
Key { ":"? ![:]+ }
|
||||
Value { ![\n]+ }
|
||||
}
|
||||
|
||||
|
||||
6
src-web/components/core/Editor/pairs/pairs.terms.ts
Normal file
6
src-web/components/core/Editor/pairs/pairs.terms.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
export const
|
||||
pairs = 1,
|
||||
Key = 2,
|
||||
Sep = 3,
|
||||
Value = 4
|
||||
@@ -3,17 +3,17 @@ import {LRParser} from "@lezer/lr"
|
||||
import {highlight} from "./highlight"
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "!QQQOPOOOYOQO'#CaO_OPO'#CaQQOPOOOOOO,58{,58{OdOQO,58{OOOO-E6_-E6_OOOO1G.g1G.g",
|
||||
stateData: "i~OQQORPO~OSSO~ORTO~OSVO~O",
|
||||
goto: "]UPPPPPVQRORUR",
|
||||
states: "zQQOPOOOVOQO'#CaQQOPOOO[OSO,58{OOOO-E6_-E6_OaOQO1G.gOOOO7+$R7+$R",
|
||||
stateData: "f~OQPO~ORRO~OSTO~OVUO~O",
|
||||
goto: "]UPPPPPVQQORSQ",
|
||||
nodeNames: "⚠ pairs Key Sep Value",
|
||||
maxTerm: 6,
|
||||
maxTerm: 7,
|
||||
propSources: [highlight],
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 1,
|
||||
tokenData: "#oRRVOYhYZ!UZ![h![!]#[!];'Sh;'S;=`#U<%lOhRoVQPSQOYhYZ!UZ![h![!]!m!];'Sh;'S;=`#U<%lOhP!ZSQPO![!U!];'S!U;'S;=`!g<%lO!UP!jP;=`<%l!UQ!rSSQOY!mZ;'S!m;'S;=`#O<%lO!mQ#RP;=`<%l!mR#XP;=`<%lhR#cSRPSQOY!mZ;'S!m;'S;=`#O<%lO!m",
|
||||
tokenizers: [0, 1],
|
||||
tokenData: "$]VRVOYhYZ#[Z![h![!]#o!];'Sh;'S;=`#U<%lOhToVQPSSOYhYZ!UZ![h![!]!m!];'Sh;'S;=`#U<%lOhP!ZSQPO![!U!];'S!U;'S;=`!g<%lO!UP!jP;=`<%l!US!rSSSOY!mZ;'S!m;'S;=`#O<%lO!mS#RP;=`<%l!mT#XP;=`<%lhR#cSVQQPO![!U!];'S!U;'S;=`!g<%lO!UV#vVRQSSOYhYZ!UZ![h![!]!m!];'Sh;'S;=`#U<%lOh",
|
||||
tokenizers: [0, 1, 2],
|
||||
topRules: {"pairs":[0,1]},
|
||||
tokenPrec: 0
|
||||
tokenPrec: 0,
|
||||
termNames: {"0":"⚠","1":"@top","2":"Key","3":"Sep","4":"Value","5":"(Key Sep Value \"\\n\")+","6":"␄","7":"\"\\n\""}
|
||||
})
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export function twig({
|
||||
onClickFunction,
|
||||
onClickVariable,
|
||||
onClickMissingVariable,
|
||||
onClickPathParameter,
|
||||
}: {
|
||||
base: LanguageSupport;
|
||||
environmentVariables: EnvironmentVariable[];
|
||||
@@ -26,6 +27,7 @@ export function twig({
|
||||
onClickFunction: (option: TemplateFunction, tagValue: string, startPos: number) => void;
|
||||
onClickVariable: (option: EnvironmentVariable, tagValue: string, startPos: number) => void;
|
||||
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void;
|
||||
onClickPathParameter: (name: string) => void;
|
||||
}) {
|
||||
const language = mixLanguage(base);
|
||||
|
||||
@@ -62,11 +64,11 @@ export function twig({
|
||||
return [
|
||||
language,
|
||||
base.support,
|
||||
templateTagsPlugin(options, onClickMissingVariable),
|
||||
language.data.of({ autocomplete: completions }),
|
||||
base.language.data.of({ autocomplete: completions }),
|
||||
language.data.of({ autocomplete: genericCompletion(autocomplete) }),
|
||||
base.language.data.of({ autocomplete: genericCompletion(autocomplete) }),
|
||||
templateTagsPlugin(options, onClickMissingVariable, onClickPathParameter),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,42 @@ import { syntaxTree } from '@codemirror/language';
|
||||
import type { Range } from '@codemirror/state';
|
||||
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
|
||||
import { Decoration, ViewPlugin, WidgetType } from '@codemirror/view';
|
||||
import type { SyntaxNodeRef } from '@lezer/common';
|
||||
import { EditorView } from 'codemirror';
|
||||
import type { TwigCompletionOption } from './completion';
|
||||
|
||||
class PathPlaceholderWidget extends WidgetType {
|
||||
readonly #clickListenerCallback: () => void;
|
||||
|
||||
constructor(readonly rawText: string, readonly startPos: number, readonly onClick: () => void) {
|
||||
super();
|
||||
this.#clickListenerCallback = () => {
|
||||
this.onClick?.();
|
||||
};
|
||||
}
|
||||
|
||||
eq(other: PathPlaceholderWidget) {
|
||||
return this.startPos === other.startPos && this.rawText === other.rawText;
|
||||
}
|
||||
|
||||
toDOM() {
|
||||
const elt = document.createElement('span');
|
||||
elt.className = `x-theme-templateTag x-theme-templateTag--secondary template-tag`;
|
||||
elt.textContent = this.rawText;
|
||||
elt.addEventListener('click', this.#clickListenerCallback);
|
||||
return elt;
|
||||
}
|
||||
|
||||
destroy(dom: HTMLElement) {
|
||||
dom.removeEventListener('click', this.#clickListenerCallback);
|
||||
super.destroy(dom);
|
||||
}
|
||||
|
||||
ignoreEvent() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class TemplateTagWidget extends WidgetType {
|
||||
readonly #clickListenerCallback: () => void;
|
||||
|
||||
@@ -62,20 +95,40 @@ function templateTags(
|
||||
view: EditorView,
|
||||
options: TwigCompletionOption[],
|
||||
onClickMissingVariable: (name: string, rawTag: string, startPos: number) => void,
|
||||
onClickPathParameter: (name: string) => void,
|
||||
): DecorationSet {
|
||||
const widgets: Range<Decoration>[] = [];
|
||||
for (const { from, to } of view.visibleRanges) {
|
||||
syntaxTree(view.state).iterate({
|
||||
const tree = syntaxTree(view.state);
|
||||
tree.iterate({
|
||||
from,
|
||||
to,
|
||||
enter(node) {
|
||||
if (node.name == 'Tag') {
|
||||
// Don't decorate if the cursor is inside the match
|
||||
for (const r of view.state.selection.ranges) {
|
||||
if (r.from > node.from && r.to < node.to) {
|
||||
return;
|
||||
if (node.name === 'Text') {
|
||||
// Find the `url` node and then jump into it to find the placeholders
|
||||
for (let i = node.from; i < node.to; i++) {
|
||||
const innerTree = syntaxTree(view.state).resolveInner(i);
|
||||
if (innerTree.node.name === 'url') {
|
||||
innerTree.toTree().iterate({
|
||||
enter(node) {
|
||||
if (node.name !== 'Placeholder') return;
|
||||
if (isSelectionInsideNode(view, node)) return;
|
||||
|
||||
const globalFrom = innerTree.node.from + node.from;
|
||||
const globalTo = innerTree.node.from + node.to;
|
||||
const rawText = view.state.doc.sliceString(globalFrom, globalTo);
|
||||
const onClick = () => onClickPathParameter(rawText);
|
||||
const widget = new PathPlaceholderWidget(rawText, globalFrom, onClick);
|
||||
const deco = Decoration.replace({ widget, inclusive: false });
|
||||
widgets.push(deco.range(globalFrom, globalTo));
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (node.name === 'Tag') {
|
||||
// Don't decorate if the cursor is inside the match
|
||||
if (isSelectionInsideNode(view, node)) return;
|
||||
|
||||
const rawTag = view.state.doc.sliceString(node.from, node.to);
|
||||
|
||||
@@ -114,17 +167,28 @@ function templateTags(
|
||||
export function templateTagsPlugin(
|
||||
options: TwigCompletionOption[],
|
||||
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void,
|
||||
onClickPathParameter: (name: string) => void,
|
||||
) {
|
||||
return ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations: DecorationSet;
|
||||
|
||||
constructor(view: EditorView) {
|
||||
this.decorations = templateTags(view, options, onClickMissingVariable);
|
||||
this.decorations = templateTags(
|
||||
view,
|
||||
options,
|
||||
onClickMissingVariable,
|
||||
onClickPathParameter,
|
||||
);
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
this.decorations = templateTags(update.view, options, onClickMissingVariable);
|
||||
this.decorations = templateTags(
|
||||
update.view,
|
||||
options,
|
||||
onClickMissingVariable,
|
||||
onClickPathParameter,
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -136,13 +200,13 @@ export function templateTagsPlugin(
|
||||
return view.plugin(plugin)?.decorations || Decoration.none;
|
||||
});
|
||||
},
|
||||
eventHandlers: {
|
||||
mousedown(e) {
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.classList.contains('template-tag')) console.log('CLICKED TEMPLATE TAG');
|
||||
// return toggleBoolean(view, view.posAtDOM(target));
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function isSelectionInsideNode(view: EditorView, node: SyntaxNodeRef) {
|
||||
for (const r of view.state.selection.ranges) {
|
||||
if (r.from > node.from && r.to < node.to) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
export const Template = 1,
|
||||
export const
|
||||
Template = 1,
|
||||
Tag = 2,
|
||||
TagOpen = 3,
|
||||
TagContent = 4,
|
||||
Open = 3,
|
||||
Close = 5,
|
||||
Text = 6;
|
||||
TagClose = 5,
|
||||
Text = 6
|
||||
|
||||
@@ -16,4 +16,3 @@ export const parser = LRParser.deserialize({
|
||||
topRules: {"Template":[0,1]},
|
||||
tokenPrec: 0
|
||||
})
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import { styleTags, tags as t } from '@lezer/highlight';
|
||||
|
||||
export const highlight = styleTags({
|
||||
Protocol: t.comment,
|
||||
Placeholder: t.emphasis,
|
||||
// PathSegment: t.tagName,
|
||||
// Port: t.attributeName,
|
||||
// Host: t.variableName,
|
||||
// Path: t.bool,
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
@top url { Protocol? Host Port? Path? Query? }
|
||||
|
||||
Query {
|
||||
"?" queryPair ("&" queryPair)*
|
||||
}
|
||||
Path { ("/" (Placeholder | PathSegment))+ }
|
||||
|
||||
Query { "?" queryPair ("&" queryPair)* }
|
||||
|
||||
@tokens {
|
||||
Protocol { $[a-zA-Z]+ "://" }
|
||||
Path { ("/" $[a-zA-Z0-9\-_.]*)+ }
|
||||
queryPair { ($[a-zA-Z0-9]+ ("=" $[a-zA-Z0-9]*)?) }
|
||||
Port { ":" $[0-9]+ }
|
||||
Host { $[a-zA-Z0-9-_.]+ }
|
||||
Port { ":" $[0-9]+ }
|
||||
Placeholder { ":" ![/?#]+ }
|
||||
PathSegment { ![?#/]+ }
|
||||
queryPair { ($[a-zA-Z0-9]+ ("=" $[a-zA-Z0-9]*)?) }
|
||||
|
||||
// Protocol/host overlaps, so give proto explicit precedence
|
||||
@precedence { Protocol, Host }
|
||||
@precedence { Placeholder, PathSegment }
|
||||
}
|
||||
|
||||
@external propSource highlight from "./highlight"
|
||||
|
||||
@@ -5,4 +5,6 @@ export const
|
||||
Host = 3,
|
||||
Port = 4,
|
||||
Path = 5,
|
||||
Query = 6
|
||||
Placeholder = 6,
|
||||
PathSegment = 7,
|
||||
Query = 8
|
||||
|
||||
@@ -3,16 +3,17 @@ import {LRParser} from "@lezer/lr"
|
||||
import {highlight} from "./highlight"
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: "!jOQOPOOQYOPOOOTOPOOOeOQO'#CbQOOOOOQ`OPOOQ]OPOOOjOPO,58|OrOQO'#CcOwOPO1G.hOOOO,58},58}OOOO-E6a-E6a",
|
||||
stateData: "!S~OQQORPO~OSUOTTOXRO~OYVO~OZWOWUa~OYYO~OZWOWUi~OQR~",
|
||||
goto: "dWPPPPPPX^VSPTUQXVRZX",
|
||||
nodeNames: "⚠ url Protocol Host Port Path Query",
|
||||
maxTerm: 11,
|
||||
states: "#SOQOPOOQYOPOOOTOPOOOeOQO'#CeOmOPO'#CaOxOSO'#CdQOOOOOQ`OPOOQ]OPOOOOOO,59P,59POOOO-E6c-E6cO}OPO,59OO!VOSO'#CfO![OPO1G.jOOOO,59Q,59QOOOO-E6d-E6d",
|
||||
stateData: "!j~OQQORPO~OSWO[RO]TO~OUXOVXO~O[ROZTX]TX~O^ZO~O_[OZWa~O^^O~O_[OZWi~OQRUVU~",
|
||||
goto: "rZPPPPP[PP`elTVPWVUPVWSSPWRYSQ]ZR_]",
|
||||
nodeNames: "⚠ url Protocol Host Port Path Placeholder PathSegment Query",
|
||||
maxTerm: 15,
|
||||
propSources: [highlight],
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 1,
|
||||
tokenData: "%[~RYvwq}!Ov!O!Pv!P!Q!_!Q![!y![!]#u!a!b$T!c!}$Y#R#Sv#T#o$Y~vOZ~P{URP}!Ov!O!Pv!Q![v!c!}v#R#Sv#T#ov~!dVT~}!O!_!O!P!_!P!Q!_!Q![!_!c!}!_#R#S!_#T#o!_R#QVYQRP}!Ov!O!Pv!Q![!y!_!`#g!c!}!y#R#Sv#T#o!yQ#lRYQ!Q![#g!c!}#g#T#o#g~#xP!Q![#{~$QPS~!Q![#{~$YOX~R$aWYQRP}!Ov!O!Pv!Q![!y![!]$y!_!`#g!c!}$Y#R#Sv#T#o$YP$|P!P!Q%PP%SP!P!Q%VP%[OQP",
|
||||
tokenizers: [0, 1],
|
||||
repeatNodeCount: 2,
|
||||
tokenData: "+U~RdOs!atv!avw#Ow}!a}!O#i!O!P#i!P!Q$o!Q![$t![!]&|!]!a!a!a!b(w!b!c!a!c!}(|!}#R!a#R#S#i#S#T!a#T#o(|#o;'S!a;'S;=`!x<%lO!aQ!fUVQOs!at!P!a!Q!a!a!b;'S!a;'S;=`!x<%lO!aQ!{P;=`<%l!aR#VU_PVQOs!at!P!a!Q!a!a!b;'S!a;'S;=`!x<%lO!aR#p_RPVQOs!at}!a}!O#i!O!P#i!Q![#i![!a!a!b!c!a!c!}#i!}#R!a#R#S#i#S#T!a#T#o#i#o;'S!a;'S;=`!x<%lO!a~$tO[~V$}a^SRPVQOs!at}!a}!O#i!O!P#i!Q![$t![!_!a!_!`&S!`!a!a!b!c!a!c!}$t!}#R!a#R#S#i#S#T!a#T#o$t#o;'S!a;'S;=`!x<%lO!aU&ZZ^SVQOs!at!P!a!Q![&S![!a!a!b!c!a!c!}&S!}#T!a#T#o&S#o;'S!a;'S;=`!x<%lO!aR'RVVQOs'ht!P'h!Q![(X![!a'h!b;'S'h;'S;=`(R<%lO'hQ'oUUQVQOs'ht!P'h!Q!a'h!b;'S'h;'S;=`(R<%lO'hQ(UP;=`<%l'hR(bVSPUQVQOs'ht!P'h!Q![(X![!a'h!b;'S'h;'S;=`(R<%lO'h~(|O]~V)Vb^SRPVQOs!at}!a}!O#i!O!P#i!Q![$t![!]*_!]!_!a!_!`&S!`!a!a!b!c!a!c!}(|!}#R!a#R#S#i#S#T!a#T#o(|#o;'S!a;'S;=`!x<%lO!aR*dVVQOs!at!P!a!P!Q*y!Q!a!a!b;'S!a;'S;=`!x<%lO!aP*|P!P!Q+PP+UOQP",
|
||||
tokenizers: [0, 1, 2],
|
||||
topRules: {"url":[0,1]},
|
||||
tokenPrec: 47
|
||||
tokenPrec: 66,
|
||||
termNames: {"0":"⚠","1":"@top","2":"Protocol","3":"Host","4":"Port","5":"Path","6":"Placeholder","7":"PathSegment","8":"Query","9":"(\"/\" (Placeholder | PathSegment))+","10":"(\"&\" queryPair)+","11":"␄","12":"\"/\"","13":"\"?\"","14":"queryPair","15":"\"&\""}
|
||||
})
|
||||
|
||||
@@ -58,9 +58,9 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
|
||||
'!px-0',
|
||||
color === 'custom' && 'text-text-subtle',
|
||||
color === 'default' && 'text-text-subtle',
|
||||
size === 'md' && 'w-9',
|
||||
size === 'sm' && 'w-8',
|
||||
size === 'xs' && 'w-6',
|
||||
size === 'md' && 'w-md',
|
||||
size === 'sm' && 'w-sm',
|
||||
size === 'xs' && 'w-xs',
|
||||
size === '2xs' && 'w-5',
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -6,6 +6,7 @@ export function InlineCode({ className, ...props }: HTMLAttributes<HTMLSpanEleme
|
||||
<code
|
||||
className={classNames(
|
||||
className,
|
||||
'select-text cursor-text',
|
||||
'font-mono text-shrink bg-surface-highlight border border-border-subtle',
|
||||
'px-1.5 py-0.5 rounded text shadow-inner break-words',
|
||||
)}
|
||||
|
||||
@@ -14,7 +14,7 @@ export type InputProps = Omit<
|
||||
> &
|
||||
Pick<
|
||||
EditorProps,
|
||||
| 'contentType'
|
||||
| 'language'
|
||||
| 'useTemplating'
|
||||
| 'autocomplete'
|
||||
| 'forceUpdateKey'
|
||||
@@ -22,6 +22,7 @@ export type InputProps = Omit<
|
||||
| 'autoSelect'
|
||||
| 'autocompleteVariables'
|
||||
| 'onKeyDown'
|
||||
| 'readOnly'
|
||||
> & {
|
||||
name: string;
|
||||
type?: 'text' | 'password';
|
||||
@@ -68,6 +69,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
||||
size = 'md',
|
||||
type = 'text',
|
||||
validate,
|
||||
readOnly,
|
||||
...props
|
||||
}: InputProps,
|
||||
ref,
|
||||
@@ -77,9 +79,10 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
||||
const [focused, setFocused] = useState(false);
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
if (readOnly) return;
|
||||
setFocused(true);
|
||||
onFocus?.();
|
||||
}, [onFocus]);
|
||||
}, [onFocus, readOnly]);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
setFocused(false);
|
||||
@@ -179,6 +182,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
||||
className={editorClassName}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
readOnly={readOnly}
|
||||
{...props}
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import classNames from 'classnames';
|
||||
import type { EditorView } from 'codemirror';
|
||||
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Fragment,
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { XYCoord } from 'react-dnd';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@@ -16,6 +25,10 @@ import type { InputProps } from './Input';
|
||||
import { Input } from './Input';
|
||||
import { RadioDropdown } from './RadioDropdown';
|
||||
|
||||
export interface PairEditorRef {
|
||||
focusValue(index: number): void;
|
||||
}
|
||||
|
||||
export type PairEditorProps = {
|
||||
pairs: Pair[];
|
||||
onChange: (pairs: Pair[]) => void;
|
||||
@@ -41,6 +54,7 @@ export type Pair = {
|
||||
value: string;
|
||||
contentType?: string;
|
||||
isFile?: boolean;
|
||||
readOnlyName?: boolean;
|
||||
};
|
||||
|
||||
type PairContainer = {
|
||||
@@ -48,24 +62,28 @@ type PairContainer = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export function PairEditor({
|
||||
className,
|
||||
forceUpdateKey,
|
||||
nameAutocomplete,
|
||||
nameAutocompleteVariables,
|
||||
namePlaceholder,
|
||||
nameValidate,
|
||||
valueType,
|
||||
onChange,
|
||||
noScroll,
|
||||
pairs: originalPairs,
|
||||
valueAutocomplete,
|
||||
valueAutocompleteVariables,
|
||||
valuePlaceholder,
|
||||
valueValidate,
|
||||
allowFileValues,
|
||||
}: PairEditorProps) {
|
||||
const [forceFocusPairId, setForceFocusPairId] = useState<string | null>(null);
|
||||
export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function PairEditor(
|
||||
{
|
||||
className,
|
||||
forceUpdateKey,
|
||||
nameAutocomplete,
|
||||
nameAutocompleteVariables,
|
||||
namePlaceholder,
|
||||
nameValidate,
|
||||
valueType,
|
||||
onChange,
|
||||
noScroll,
|
||||
pairs: originalPairs,
|
||||
valueAutocomplete,
|
||||
valueAutocompleteVariables,
|
||||
valuePlaceholder,
|
||||
valueValidate,
|
||||
allowFileValues,
|
||||
}: PairEditorProps,
|
||||
ref,
|
||||
) {
|
||||
const [forceFocusNamePairId, setForceFocusNamePairId] = useState<string | null>(null);
|
||||
const [forceFocusValuePairId, setForceFocusValuePairId] = useState<string | null>(null);
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||
const [pairs, setPairs] = useState<PairContainer[]>(() => {
|
||||
// Remove empty headers on initial render
|
||||
@@ -74,6 +92,13 @@ export function PairEditor({
|
||||
return [...pairs, newPairContainer()];
|
||||
});
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
focusValue(index: number) {
|
||||
const id = pairs[index]?.id ?? 'n/a';
|
||||
setForceFocusValuePairId(id);
|
||||
},
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
// Remove empty headers on initial render
|
||||
// TODO: Make this not refresh the entire editor when forceUpdateKey changes, using some
|
||||
@@ -134,17 +159,18 @@ export function PairEditor({
|
||||
if (focusPrevious) {
|
||||
const index = pairs.findIndex((p) => p.id === pair.id);
|
||||
const id = pairs[index - 1]?.id ?? null;
|
||||
setForceFocusPairId(id);
|
||||
setForceFocusNamePairId(id);
|
||||
}
|
||||
return setPairsAndSave((oldPairs) => oldPairs.filter((p) => p.id !== pair.id));
|
||||
},
|
||||
[setPairsAndSave, setForceFocusPairId, pairs],
|
||||
[setPairsAndSave, setForceFocusNamePairId, pairs],
|
||||
);
|
||||
|
||||
const handleFocus = useCallback(
|
||||
(pair: PairContainer) =>
|
||||
setPairs((pairs) => {
|
||||
setForceFocusPairId(null); // Remove focus override when something focused
|
||||
setForceFocusNamePairId(null); // Remove focus override when something focused
|
||||
setForceFocusValuePairId(null); // Remove focus override when something focused
|
||||
const isLast = pair.id === pairs[pairs.length - 1]?.id;
|
||||
return isLast ? [...pairs, newPairContainer()] : pairs;
|
||||
}),
|
||||
@@ -184,7 +210,8 @@ export function PairEditor({
|
||||
nameAutocompleteVariables={nameAutocompleteVariables}
|
||||
valueAutocompleteVariables={valueAutocompleteVariables}
|
||||
valueType={valueType}
|
||||
forceFocusPairId={forceFocusPairId}
|
||||
forceFocusNamePairId={forceFocusNamePairId}
|
||||
forceFocusValuePairId={forceFocusValuePairId}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
nameAutocomplete={nameAutocomplete}
|
||||
valueAutocomplete={valueAutocomplete}
|
||||
@@ -203,7 +230,7 @@ export function PairEditor({
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
enum ItemTypes {
|
||||
ROW = 'pair-row',
|
||||
@@ -212,7 +239,8 @@ enum ItemTypes {
|
||||
type PairEditorRowProps = {
|
||||
className?: string;
|
||||
pairContainer: PairContainer;
|
||||
forceFocusPairId?: string | null;
|
||||
forceFocusNamePairId?: string | null;
|
||||
forceFocusValuePairId?: string | null;
|
||||
onMove: (id: string, side: 'above' | 'below') => void;
|
||||
onEnd: (id: string) => void;
|
||||
onChange: (pair: PairContainer) => void;
|
||||
@@ -238,7 +266,8 @@ type PairEditorRowProps = {
|
||||
function PairEditorRow({
|
||||
allowFileValues,
|
||||
className,
|
||||
forceFocusPairId,
|
||||
forceFocusNamePairId,
|
||||
forceFocusValuePairId,
|
||||
forceUpdateKey,
|
||||
isLast,
|
||||
nameAutocomplete,
|
||||
@@ -254,19 +283,26 @@ function PairEditorRow({
|
||||
valueAutocomplete,
|
||||
valueAutocompleteVariables,
|
||||
valuePlaceholder,
|
||||
valueValidate,
|
||||
valueType,
|
||||
valueValidate,
|
||||
}: PairEditorRowProps) {
|
||||
const { id } = pairContainer;
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const prompt = usePrompt();
|
||||
const nameInputRef = useRef<EditorView>(null);
|
||||
const valueInputRef = useRef<EditorView>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (forceFocusPairId === pairContainer.id) {
|
||||
if (forceFocusNamePairId === pairContainer.id) {
|
||||
nameInputRef.current?.focus();
|
||||
}
|
||||
}, [forceFocusPairId, pairContainer.id]);
|
||||
}, [forceFocusNamePairId, pairContainer.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (forceFocusValuePairId === pairContainer.id) {
|
||||
valueInputRef.current?.focus();
|
||||
}
|
||||
}, [forceFocusValuePairId, pairContainer.id]);
|
||||
|
||||
const handleChangeEnabled = useMemo(
|
||||
() => (enabled: boolean) => onChange({ id, pair: { ...pairContainer.pair, enabled } }),
|
||||
@@ -374,6 +410,7 @@ function PairEditorRow({
|
||||
ref={nameInputRef}
|
||||
hideLabel
|
||||
useTemplating
|
||||
readOnly={pairContainer.pair.readOnlyName}
|
||||
size="sm"
|
||||
require={!isLast && !!pairContainer.pair.enabled && !!pairContainer.pair.value}
|
||||
validate={nameValidate}
|
||||
@@ -398,6 +435,7 @@ function PairEditorRow({
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
ref={valueInputRef}
|
||||
hideLabel
|
||||
useTemplating
|
||||
size="sm"
|
||||
@@ -476,7 +514,14 @@ function PairEditorRow({
|
||||
</RadioDropdown>
|
||||
) : (
|
||||
<Dropdown
|
||||
items={[{ key: 'delete', label: 'Delete', onSelect: handleDelete, variant: 'danger' }]}
|
||||
items={[
|
||||
{
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
onSelect: handleDelete,
|
||||
variant: 'danger',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<IconButton
|
||||
iconSize="sm"
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import classNames from 'classnames';
|
||||
import { forwardRef } from 'react';
|
||||
import { useKeyValue } from '../../hooks/useKeyValue';
|
||||
import { BulkPairEditor } from './BulkPairEditor';
|
||||
import { IconButton } from './IconButton';
|
||||
import type { PairEditorProps } from './PairEditor';
|
||||
import type { PairEditorProps, PairEditorRef } from './PairEditor';
|
||||
import { PairEditor } from './PairEditor';
|
||||
|
||||
interface Props extends PairEditorProps {
|
||||
preferenceName: string;
|
||||
}
|
||||
|
||||
export function PairOrBulkEditor({ preferenceName, ...props }: Props) {
|
||||
export const PairOrBulkEditor = forwardRef<PairEditorRef, Props>(function PairOrBulkEditor(
|
||||
{ preferenceName, ...props }: Props,
|
||||
ref,
|
||||
) {
|
||||
const { value: useBulk, set: setUseBulk } = useKeyValue<boolean>({
|
||||
namespace: 'global',
|
||||
key: ['bulk_edit', preferenceName],
|
||||
@@ -18,7 +22,7 @@ export function PairOrBulkEditor({ preferenceName, ...props }: Props) {
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full group/wrapper">
|
||||
{useBulk ? <BulkPairEditor {...props} /> : <PairEditor {...props} />}
|
||||
{useBulk ? <BulkPairEditor {...props} /> : <PairEditor ref={ref} {...props} />}
|
||||
<div className="absolute right-0 bottom-0">
|
||||
<IconButton
|
||||
size="sm"
|
||||
@@ -34,4 +38,4 @@ export function PairOrBulkEditor({ preferenceName, ...props }: Props) {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { memo, useCallback, useEffect, useRef } from 'react';
|
||||
import { memo, useEffect, useRef } from 'react';
|
||||
import { Icon } from '../Icon';
|
||||
import type { RadioDropdownProps } from '../RadioDropdown';
|
||||
import { RadioDropdown } from '../RadioDropdown';
|
||||
@@ -39,31 +39,23 @@ export function Tabs({
|
||||
}: Props) {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const handleTabChange = useCallback(
|
||||
(value: string) => {
|
||||
const tabs = ref.current?.querySelectorAll<HTMLDivElement>(`[data-tab]`);
|
||||
for (const tab of tabs ?? []) {
|
||||
const v = tab.getAttribute('data-tab');
|
||||
if (v === value) {
|
||||
tab.setAttribute('tabindex', '-1');
|
||||
tab.setAttribute('data-state', 'active');
|
||||
tab.setAttribute('aria-hidden', 'false');
|
||||
tab.style.display = 'block';
|
||||
} else {
|
||||
tab.setAttribute('data-state', 'inactive');
|
||||
tab.setAttribute('aria-hidden', 'true');
|
||||
tab.style.display = 'none';
|
||||
}
|
||||
}
|
||||
onChangeValue(value);
|
||||
},
|
||||
[onChangeValue],
|
||||
);
|
||||
|
||||
// Update tabs when value changes
|
||||
useEffect(() => {
|
||||
if (value === undefined) return;
|
||||
handleTabChange(value);
|
||||
}, [handleTabChange, value]);
|
||||
const tabs = ref.current?.querySelectorAll<HTMLDivElement>(`[data-tab]`);
|
||||
for (const tab of tabs ?? []) {
|
||||
const v = tab.getAttribute('data-tab');
|
||||
if (v === value) {
|
||||
tab.setAttribute('tabindex', '-1');
|
||||
tab.setAttribute('data-state', 'active');
|
||||
tab.setAttribute('aria-hidden', 'false');
|
||||
tab.style.display = 'block';
|
||||
} else {
|
||||
tab.setAttribute('data-state', 'inactive');
|
||||
tab.setAttribute('aria-hidden', 'true');
|
||||
tab.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -103,7 +95,7 @@ export function Tabs({
|
||||
onChange={t.options.onChange}
|
||||
>
|
||||
<button
|
||||
onClick={isActive ? undefined : () => handleTabChange(t.value)}
|
||||
onClick={isActive ? undefined : () => onChangeValue(t.value)}
|
||||
className={btnClassName}
|
||||
>
|
||||
{option && 'shortLabel' in option
|
||||
@@ -121,7 +113,7 @@ export function Tabs({
|
||||
return (
|
||||
<button
|
||||
key={t.value}
|
||||
onClick={() => handleTabChange(t.value)}
|
||||
onClick={() => onChangeValue(t.value)}
|
||||
className={btnClassName}
|
||||
>
|
||||
{t.label}
|
||||
|
||||
37
src-web/components/responseViewers/HTMLOrTextViewer.tsx
Normal file
37
src-web/components/responseViewers/HTMLOrTextViewer.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { HttpResponse } from '@yaakapp/api';
|
||||
import { useContentTypeFromHeaders } from '../../hooks/useContentTypeFromHeaders';
|
||||
import { useResponseBodyText } from '../../hooks/useResponseBodyText';
|
||||
import { isJSON, languageFromContentType } from '../../lib/contentType';
|
||||
import { BinaryViewer } from './BinaryViewer';
|
||||
import { TextViewer } from './TextViewer';
|
||||
import { WebPageViewer } from './WebPageViewer';
|
||||
|
||||
interface Props {
|
||||
response: HttpResponse;
|
||||
pretty: boolean;
|
||||
textViewerClassName?: string;
|
||||
}
|
||||
|
||||
export function HTMLOrTextViewer({ response, pretty, textViewerClassName }: Props) {
|
||||
const rawBody = useResponseBodyText(response);
|
||||
let language = languageFromContentType(useContentTypeFromHeaders(response.headers));
|
||||
|
||||
// A lot of APIs return JSON with `text/html` content type, so interpret as JSON if so
|
||||
if (language === 'html' && isJSON(rawBody.data ?? '')) {
|
||||
language = 'json';
|
||||
}
|
||||
|
||||
if (rawBody.isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (rawBody.data == null) {
|
||||
return <BinaryViewer response={response} />;
|
||||
}
|
||||
|
||||
if (language === 'html' && pretty) {
|
||||
return <WebPageViewer response={response} />;
|
||||
} else {
|
||||
return <TextViewer response={response} pretty={pretty} className={textViewerClassName} />;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { HttpResponse } from '@yaakapp/api';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
@@ -8,8 +9,8 @@ import { useFilterResponse } from '../../hooks/useFilterResponse';
|
||||
import { useResponseBodyText } from '../../hooks/useResponseBodyText';
|
||||
import { useSaveResponse } from '../../hooks/useSaveResponse';
|
||||
import { useToggle } from '../../hooks/useToggle';
|
||||
import { isJSON, languageFromContentType } from '../../lib/contentType';
|
||||
import { tryFormatJson, tryFormatXml } from '../../lib/formatters';
|
||||
import type { HttpResponse } from '@yaakapp/api';
|
||||
import { CopyButton } from '../CopyButton';
|
||||
import { Banner } from '../core/Banner';
|
||||
import { Button } from '../core/Button';
|
||||
@@ -45,9 +46,15 @@ export function TextViewer({ response, pretty, className }: Props) {
|
||||
[setFilterTextMap, response],
|
||||
);
|
||||
|
||||
const saveResponse = useSaveResponse(response);
|
||||
const contentType = useContentTypeFromHeaders(response.headers);
|
||||
const rawBody = useResponseBodyText(response);
|
||||
const saveResponse = useSaveResponse(response);
|
||||
let language = languageFromContentType(useContentTypeFromHeaders(response.headers));
|
||||
|
||||
// A lot of APIs return JSON with `text/html` content type, so interpret as JSON if so
|
||||
if (language === 'html' && isJSON(rawBody.data ?? '')) {
|
||||
language = 'json';
|
||||
}
|
||||
|
||||
const isSearching = filterText != null;
|
||||
|
||||
const filteredResponse = useFilterResponse({
|
||||
@@ -63,9 +70,7 @@ export function TextViewer({ response, pretty, className }: Props) {
|
||||
}
|
||||
}, [isSearching, setFilterText]);
|
||||
|
||||
const isJson = contentType?.includes('json');
|
||||
const isXml = contentType?.includes('xml') || contentType?.includes('html');
|
||||
const canFilter = isJson || isXml;
|
||||
const canFilter = language === 'json' || language === 'xml' || language === 'html';
|
||||
|
||||
const actions = useMemo<ReactNode[]>(() => {
|
||||
const nodes: ReactNode[] = [];
|
||||
@@ -82,7 +87,7 @@ export function TextViewer({ response, pretty, className }: Props) {
|
||||
autoFocus
|
||||
containerClassName="bg-surface"
|
||||
size="sm"
|
||||
placeholder={isJson ? 'JSONPath expression' : 'XPath expression'}
|
||||
placeholder={language === 'json' ? 'JSONPath expression' : 'XPath expression'}
|
||||
label="Filter expression"
|
||||
name="filter"
|
||||
defaultValue={filterText}
|
||||
@@ -109,8 +114,8 @@ export function TextViewer({ response, pretty, className }: Props) {
|
||||
canFilter,
|
||||
filterText,
|
||||
filteredResponse.error,
|
||||
isJson,
|
||||
isSearching,
|
||||
language,
|
||||
response.id,
|
||||
setFilterText,
|
||||
toggleSearch,
|
||||
@@ -153,9 +158,9 @@ export function TextViewer({ response, pretty, className }: Props) {
|
||||
}
|
||||
|
||||
const formattedBody =
|
||||
pretty && contentType?.includes('json')
|
||||
pretty && language === 'json'
|
||||
? tryFormatJson(rawBody.data)
|
||||
: pretty && contentType?.includes('xml')
|
||||
: pretty && (language === 'xml' || language === 'html')
|
||||
? tryFormatXml(rawBody.data)
|
||||
: rawBody.data;
|
||||
|
||||
@@ -176,7 +181,7 @@ export function TextViewer({ response, pretty, className }: Props) {
|
||||
className={className}
|
||||
forceUpdateKey={body}
|
||||
defaultValue={body}
|
||||
contentType={contentType}
|
||||
language={language}
|
||||
actions={actions}
|
||||
extraExtensions={extraExtensions}
|
||||
/>
|
||||
|
||||
@@ -14,9 +14,11 @@ export function useActiveWorkspaceChangedToast() {
|
||||
|
||||
setId(activeWorkspace?.id ?? null);
|
||||
|
||||
// Don't notify on first load
|
||||
// Don't notify on the first load
|
||||
if (id === null) return;
|
||||
|
||||
console.log('HELLO?', activeWorkspace?.id, id, window.location);
|
||||
|
||||
toast.show({
|
||||
id: 'workspace-changed',
|
||||
timeout: 3000,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import type { HttpResponseHeader } from '@yaakapp/api';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export function useContentTypeFromHeaders(headers: HttpResponseHeader[] | null): string | null {
|
||||
return useMemo(
|
||||
|
||||
15
src-web/hooks/useCopyHttpResponse.ts
Normal file
15
src-web/hooks/useCopyHttpResponse.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import type { HttpResponse } from '@yaakapp/api';
|
||||
import { useCopy } from './useCopy';
|
||||
import { getResponseBodyText } from '../lib/responseBody';
|
||||
|
||||
export function useCopyHttpResponse(response: HttpResponse) {
|
||||
const copy = useCopy();
|
||||
return useMutation({
|
||||
mutationKey: ['copy_http_response'],
|
||||
async mutationFn() {
|
||||
const body = await getResponseBodyText(response);
|
||||
copy(body);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export function useCreateDropdownItems({
|
||||
}: {
|
||||
hideFolder?: boolean;
|
||||
hideIcons?: boolean;
|
||||
folderId?: string;
|
||||
folderId?: string | null;
|
||||
} = {}): DropdownItem[] {
|
||||
const createHttpRequest = useCreateHttpRequest();
|
||||
const createGrpcRequest = useCreateGrpcRequest();
|
||||
|
||||
@@ -2,13 +2,11 @@ import { useMutation } from '@tanstack/react-query';
|
||||
import type { Folder } from '@yaakapp/api';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useActiveRequest } from './useActiveRequest';
|
||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
||||
import { usePrompt } from './usePrompt';
|
||||
|
||||
export function useCreateFolder() {
|
||||
const workspace = useActiveWorkspace();
|
||||
const activeRequest = useActiveRequest();
|
||||
const prompt = usePrompt();
|
||||
|
||||
return useMutation<Folder, unknown, Partial<Pick<Folder, 'name' | 'sortPriority' | 'folderId'>>>({
|
||||
@@ -29,7 +27,6 @@ export function useCreateFolder() {
|
||||
placeholder: 'Name',
|
||||
}));
|
||||
patch.sortPriority = patch.sortPriority || -Date.now();
|
||||
patch.folderId = patch.folderId || activeRequest?.folderId;
|
||||
return invokeCmd('cmd_create_folder', { workspaceId: workspace.id, ...patch });
|
||||
},
|
||||
onSettled: () => trackEvent('folder', 'create'),
|
||||
|
||||
13
src-web/hooks/useCreatePlugin.ts
Normal file
13
src-web/hooks/useCreatePlugin.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export function useCreatePlugin() {
|
||||
return useMutation<void, unknown, string>({
|
||||
mutationKey: ['create_plugin'],
|
||||
mutationFn: async (directory: string) => {
|
||||
await invokeCmd('cmd_create_plugin', { directory });
|
||||
},
|
||||
onSettled: () => trackEvent('plugin', 'create'),
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { HttpRequest } from '@yaakapp/api';
|
||||
import type { IntrospectionQuery } from 'graphql';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { buildClientSchema, getIntrospectionQuery } from '../components/core/Editor';
|
||||
import { minPromiseMillis } from '../lib/minPromiseMillis';
|
||||
import { getResponseBodyText } from '../lib/responseBody';
|
||||
@@ -29,8 +29,6 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
|
||||
namespace: 'global',
|
||||
});
|
||||
|
||||
const introspectionInterval = useRef<NodeJS.Timeout>();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchIntrospection = async () => {
|
||||
setIsLoading(true);
|
||||
@@ -63,16 +61,9 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
|
||||
await setIntrospection(data);
|
||||
};
|
||||
|
||||
const runIntrospection = () => {
|
||||
fetchIntrospection()
|
||||
.catch((e) => setError(e.message))
|
||||
.finally(() => setIsLoading(false));
|
||||
};
|
||||
|
||||
// Do it again on an interval
|
||||
clearInterval(introspectionInterval.current);
|
||||
introspectionInterval.current = setInterval(runIntrospection, 1000 * 60);
|
||||
runIntrospection(); // Run immediately
|
||||
fetchIntrospection()
|
||||
.catch((e) => setError(e.message))
|
||||
.finally(() => setIsLoading(false));
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [request.id, request.url, request.method, refetchKey, activeEnvironment?.id]);
|
||||
|
||||
13
src-web/hooks/usePluginInfo.ts
Normal file
13
src-web/hooks/usePluginInfo.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { PluginBootResponse } from '@yaakapp/api';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
export function usePluginInfo(id: string) {
|
||||
return useQuery({
|
||||
queryKey: ['plugin_info', id],
|
||||
queryFn: async () => {
|
||||
const info = (await invokeCmd('cmd_plugin_info', { id })) as PluginBootResponse;
|
||||
return info;
|
||||
},
|
||||
});
|
||||
}
|
||||
23
src-web/hooks/usePlugins.ts
Normal file
23
src-web/hooks/usePlugins.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import type { Plugin } from '@yaakapp/api';
|
||||
import { atom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import { minPromiseMillis } from '../lib/minPromiseMillis';
|
||||
import { listPlugins } from '../lib/store';
|
||||
|
||||
const plugins = await listPlugins();
|
||||
export const pluginsAtom = atom<Plugin[]>(plugins);
|
||||
|
||||
export function usePlugins() {
|
||||
return useAtomValue(pluginsAtom);
|
||||
}
|
||||
|
||||
export function useRefreshPlugins() {
|
||||
const setPlugins = useSetAtom(pluginsAtom);
|
||||
return useMutation({
|
||||
mutationKey: ['refresh_plugins'],
|
||||
mutationFn: async () => {
|
||||
const plugins = await minPromiseMillis(listPlugins());
|
||||
setPlugins(plugins);
|
||||
},
|
||||
});
|
||||
}
|
||||
65
src-web/hooks/useRequestEditor.tsx
Normal file
65
src-web/hooks/useRequestEditor.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import type { DependencyList } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
type EventDataMap = {
|
||||
'request_params.focus_value': string;
|
||||
'request_pane.focus_tab': undefined;
|
||||
};
|
||||
|
||||
export function useRequestEditorEvent<
|
||||
Event extends keyof EventDataMap,
|
||||
Data extends EventDataMap[Event],
|
||||
>(event: Event, fn: (data: Data) => void, deps?: DependencyList) {
|
||||
useEffect(() => {
|
||||
emitter.on(event, fn);
|
||||
return () => {
|
||||
emitter.off(event, fn);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, deps);
|
||||
}
|
||||
|
||||
export function useRequestEditor() {
|
||||
const focusParamsTab = useCallback(() => {
|
||||
emitter.emit('request_pane.focus_tab', undefined);
|
||||
}, []);
|
||||
|
||||
const focusParamValue = useCallback(
|
||||
(name: string) => {
|
||||
focusParamsTab();
|
||||
setTimeout(() => emitter.emit('request_params.focus_value', name), 50);
|
||||
},
|
||||
[focusParamsTab],
|
||||
);
|
||||
|
||||
return {
|
||||
focusParamValue,
|
||||
focusParamsTab,
|
||||
};
|
||||
}
|
||||
|
||||
const emitter = new (class RequestEditorEventEmitter {
|
||||
#emitter: EventEmitter = new EventEmitter();
|
||||
|
||||
emit<Event extends keyof EventDataMap, Data extends EventDataMap[Event]>(
|
||||
event: Event,
|
||||
data: Data,
|
||||
) {
|
||||
this.#emitter.emit(event, data);
|
||||
}
|
||||
|
||||
on<Event extends keyof EventDataMap, Data extends EventDataMap[Event]>(
|
||||
event: Event,
|
||||
fn: (data: Data) => void,
|
||||
) {
|
||||
this.#emitter.on(event, fn);
|
||||
}
|
||||
|
||||
off<Event extends keyof EventDataMap, Data extends EventDataMap[Event]>(
|
||||
event: Event,
|
||||
fn: (data: Data) => void,
|
||||
) {
|
||||
this.#emitter.off(event, fn);
|
||||
}
|
||||
})();
|
||||
@@ -4,7 +4,7 @@ import { getResponseBodyText } from '../lib/responseBody';
|
||||
|
||||
export function useResponseBodyText(response: HttpResponse) {
|
||||
return useQuery<string | null>({
|
||||
queryKey: ['response-body-text', response?.updatedAt],
|
||||
queryKey: ['response-body-text', response.id, response?.updatedAt],
|
||||
queryFn: () => getResponseBodyText(response),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export type TrackResource =
|
||||
| 'http_request'
|
||||
| 'http_response'
|
||||
| 'key_value'
|
||||
| 'plugin'
|
||||
| 'setting'
|
||||
| 'sidebar'
|
||||
| 'theme'
|
||||
|
||||
25
src-web/lib/contentType.ts
Normal file
25
src-web/lib/contentType.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { EditorProps } from '../components/core/Editor';
|
||||
|
||||
export function languageFromContentType(contentType: string | null): EditorProps['language'] {
|
||||
const justContentType = contentType?.split(';')[0] ?? contentType ?? '';
|
||||
if (justContentType.includes('json')) {
|
||||
return 'json';
|
||||
} else if (justContentType.includes('xml')) {
|
||||
return 'xml';
|
||||
} else if (justContentType.includes('html')) {
|
||||
return 'html';
|
||||
} else if (justContentType.includes('javascript')) {
|
||||
return 'javascript';
|
||||
} else {
|
||||
return 'text';
|
||||
}
|
||||
}
|
||||
|
||||
export function isJSON(text: string): boolean {
|
||||
try {
|
||||
JSON.parse(text);
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { sleep } from './sleep';
|
||||
|
||||
/** Ensures a promise takes at least a certain number of milliseconds to resolve */
|
||||
export async function minPromiseMillis<T>(promise: Promise<T>, millis: number) {
|
||||
export async function minPromiseMillis<T>(promise: Promise<T>, millis = 300) {
|
||||
const start = Date.now();
|
||||
let result;
|
||||
let err;
|
||||
|
||||
@@ -46,3 +46,8 @@ export function modelsEq(a: Model, b: Model) {
|
||||
export function getContentTypeHeader(headers: HttpResponseHeader[]): string | null {
|
||||
return headers.find((h) => h.name.toLowerCase() === 'content-type')?.value ?? null;
|
||||
}
|
||||
|
||||
export function getCharsetFromContentType(headers: HttpResponseHeader[]): string | null {
|
||||
const contentType = getContentTypeHeader(headers);
|
||||
return contentType?.match(/charset=([^ ;]+)/)?.[1] ?? null;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import { readFile, readTextFile } from '@tauri-apps/plugin-fs';
|
||||
import { readFile } from '@tauri-apps/plugin-fs';
|
||||
import type { HttpResponse } from '@yaakapp/api';
|
||||
import { getCharsetFromContentType } from './models';
|
||||
|
||||
export async function getResponseBodyText(response: HttpResponse): Promise<string | null> {
|
||||
if (response.bodyPath) {
|
||||
return await readTextFile(response.bodyPath);
|
||||
const bytes = await readFile(response.bodyPath);
|
||||
const charset = getCharsetFromContentType(response.headers);
|
||||
|
||||
try {
|
||||
return new TextDecoder(charset ?? 'utf-8', { fatal: true }).decode(bytes);
|
||||
} catch (_) {
|
||||
// Failed to decode as text, so return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
Folder,
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
Plugin,
|
||||
Settings,
|
||||
Workspace,
|
||||
} from '@yaakapp/api';
|
||||
@@ -63,6 +64,11 @@ export async function listWorkspaces(): Promise<Workspace[]> {
|
||||
return workspaces;
|
||||
}
|
||||
|
||||
export async function listPlugins(): Promise<Plugin[]> {
|
||||
const plugins: Plugin[] = (await invokeCmd('cmd_list_plugins')) ?? [];
|
||||
return plugins;
|
||||
}
|
||||
|
||||
export async function getCookieJar(id: string | null): Promise<CookieJar | null> {
|
||||
if (id === null) return null;
|
||||
const cookieJar: CookieJar = (await invokeCmd('cmd_get_cookie_jar', { id })) ?? null;
|
||||
|
||||
@@ -6,6 +6,7 @@ type TauriCmd =
|
||||
| 'cmd_check_for_updates'
|
||||
| 'cmd_create_cookie_jar'
|
||||
| 'cmd_create_environment'
|
||||
| 'cmd_create_plugin'
|
||||
| 'cmd_template_tokens_to_string'
|
||||
| 'cmd_create_folder'
|
||||
| 'cmd_create_grpc_request'
|
||||
@@ -47,11 +48,13 @@ type TauriCmd =
|
||||
| 'cmd_list_grpc_requests'
|
||||
| 'cmd_list_http_requests'
|
||||
| 'cmd_list_http_responses'
|
||||
| 'cmd_list_plugins'
|
||||
| 'cmd_list_workspaces'
|
||||
| 'cmd_metadata'
|
||||
| 'cmd_new_nested_window'
|
||||
| 'cmd_new_window'
|
||||
| 'cmd_parse_template'
|
||||
| 'cmd_plugin_info'
|
||||
| 'cmd_render_template'
|
||||
| 'cmd_save_response'
|
||||
| 'cmd_send_ephemeral_request'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||
import { attachConsole } from '@tauri-apps/plugin-log';
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
@@ -18,8 +17,6 @@ if (osType !== 'macos') {
|
||||
await getCurrentWebviewWindow().setDecorations(false);
|
||||
}
|
||||
|
||||
await attachConsole();
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
// Hack to not go back in history on backspace. Check for document body
|
||||
// or else it will prevent backspace in input fields.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const plugin = require('tailwindcss/plugin');
|
||||
|
||||
const height = {
|
||||
const sizes = {
|
||||
'2xs': '1.25rem',
|
||||
xs: '1.8rem',
|
||||
sm: '2.0rem',
|
||||
md: '2.5rem',
|
||||
@@ -18,8 +19,10 @@ module.exports = {
|
||||
fontSize: {
|
||||
xs: '0.8rem',
|
||||
},
|
||||
height,
|
||||
minHeight: height,
|
||||
height: sizes,
|
||||
width: sizes,
|
||||
minHeight: sizes,
|
||||
minWidth: sizes,
|
||||
lineHeight: {
|
||||
// HACK: Minus 2 to account for borders inside inputs
|
||||
xs: 'calc(1.75rem - 2px)',
|
||||
|
||||
Reference in New Issue
Block a user