Compare commits

...

16 Commits

Author SHA1 Message Date
Gregory Schier
3ecfb15c89 Fix lint 2024-11-16 15:31:14 -08:00
Gregory Schier
23c026126f Tweak schema menu 2024-11-16 15:17:26 -08:00
Gregory Schier
ff9abab547 More control over GraphQL introspection 2024-11-16 14:27:13 -08:00
Gregory Schier
c9c48c77e4 Update Tauri deps 2024-11-16 13:56:32 -08:00
Hao Xiang
83efc58f29 don't lost request's name and folder when updated by curl (#131) 2024-11-14 14:03:22 -08:00
Gregory Schier
632e1ff091 Update README.md 2024-11-12 19:31:55 -08:00
Gregory Schier
40286756b9 Update README.md 2024-11-11 07:48:39 -08:00
Hao Xiang
1050ac5e3c fix(grpc): proto dep topo order to solve panic (#130) 2024-10-29 14:19:11 -07:00
Gregory Schier
6d2c3712c0 Fix active cookie jar and improve routing 2024-10-28 10:06:43 -07:00
Gregory Schier
4a52095033 Better template function fetching 2024-10-24 08:17:58 -07:00
Gregory Schier
55b12d7329 Try fix for template tags not re-fetching on Windows 2024-10-24 07:47:20 -07:00
Gregory Schier
f4240e5229 Prevent bg flash on context menu in sidebar 2024-10-23 10:07:31 -07:00
Gregory Schier
7759649963 Update local model stores in all mutations (#129) 2024-10-23 09:54:43 -07:00
Gregory Schier
c5e6d6f2cb Some tweaks to request deletion 2024-10-23 06:27:38 -07:00
Gregory Schier
ec850f2cf0 Properly handle charset in content-type 2024-10-23 05:49:14 -07:00
Gregory Schier
ff52ad5345 Handle quotes around charset 2024-10-23 05:44:37 -07:00
60 changed files with 1011 additions and 518 deletions

122
package-lock.json generated
View File

@@ -18,7 +18,7 @@
"src-web"
],
"devDependencies": {
"@tauri-apps/cli": "^2.0.4",
"@tauri-apps/cli": "^2.1.0",
"@typescript-eslint/eslint-plugin": "^8.5.0",
"@typescript-eslint/parser": "^8.5.0",
"eslint": "^8",
@@ -2597,9 +2597,9 @@
}
},
"node_modules/@tanstack/query-core": {
"version": "5.59.6",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.6.tgz",
"integrity": "sha512-g58YTHe4ClRrjJ50GY9fas/0zARJVozY0Hs+hcSBOmwZaeKY+to0/LX8wKnnH/EJiLYcC1sHmE11CAS3ncfZBg==",
"version": "5.59.16",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.16.tgz",
"integrity": "sha512-crHn+G3ltqb5JG0oUv6q+PMz1m1YkjpASrXTU+sYWW9pLk0t2GybUHNRqYPZWhxgjPaVGC4yp92gSFEJgYEsPw==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2618,12 +2618,12 @@
}
},
"node_modules/@tanstack/react-query": {
"version": "5.59.6",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.6.tgz",
"integrity": "sha512-sGg2sNKg8cYf6aS1dzDf4weN+Vt9PfUu+0btwerrbtYysdNBbcGD4rPe9jhPgMtpDDlvi4cbLv+j1Qo814Kf+Q==",
"version": "5.59.16",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.16.tgz",
"integrity": "sha512-MuyWheG47h6ERd4PKQ6V8gDyBu3ThNG22e1fRVwvq6ap3EqsFhyuxCAwhNP/03m/mLg+DAb0upgbPaX6VB+CkQ==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.59.6"
"@tanstack/query-core": "5.59.16"
},
"funding": {
"type": "github",
@@ -2689,9 +2689,9 @@
}
},
"node_modules/@tauri-apps/cli": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.0.4.tgz",
"integrity": "sha512-Hl9eFXz+O366+6su9PfaSzu2EJdFe1p8K8ghkWmi40dz8VmSE7vsMTaOStD0I71ckSOkh2ICDX7FQTBgjlpjWw==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.1.0.tgz",
"integrity": "sha512-K2VhcKqBhAeS5pNOVdnR/xQRU6jwpgmkSL2ejHXcl0m+kaTggT0WRDQnFtPq6NljA7aE03cvwsbCAoFG7vtkJw==",
"dev": true,
"license": "Apache-2.0 OR MIT",
"bin": {
@@ -2705,22 +2705,22 @@
"url": "https://opencollective.com/tauri"
},
"optionalDependencies": {
"@tauri-apps/cli-darwin-arm64": "2.0.4",
"@tauri-apps/cli-darwin-x64": "2.0.4",
"@tauri-apps/cli-linux-arm-gnueabihf": "2.0.4",
"@tauri-apps/cli-linux-arm64-gnu": "2.0.4",
"@tauri-apps/cli-linux-arm64-musl": "2.0.4",
"@tauri-apps/cli-linux-x64-gnu": "2.0.4",
"@tauri-apps/cli-linux-x64-musl": "2.0.4",
"@tauri-apps/cli-win32-arm64-msvc": "2.0.4",
"@tauri-apps/cli-win32-ia32-msvc": "2.0.4",
"@tauri-apps/cli-win32-x64-msvc": "2.0.4"
"@tauri-apps/cli-darwin-arm64": "2.1.0",
"@tauri-apps/cli-darwin-x64": "2.1.0",
"@tauri-apps/cli-linux-arm-gnueabihf": "2.1.0",
"@tauri-apps/cli-linux-arm64-gnu": "2.1.0",
"@tauri-apps/cli-linux-arm64-musl": "2.1.0",
"@tauri-apps/cli-linux-x64-gnu": "2.1.0",
"@tauri-apps/cli-linux-x64-musl": "2.1.0",
"@tauri-apps/cli-win32-arm64-msvc": "2.1.0",
"@tauri-apps/cli-win32-ia32-msvc": "2.1.0",
"@tauri-apps/cli-win32-x64-msvc": "2.1.0"
}
},
"node_modules/@tauri-apps/cli-darwin-arm64": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.4.tgz",
"integrity": "sha512-siH7rOHobb16rPbc11k64p1mxIpiRCkWmzs2qmL5IX21Gx9K5onI3Tk67Oqpf2uNupbYzItrOttaDT4NHFC7tw==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.1.0.tgz",
"integrity": "sha512-ESc6J6CE8hl1yKH2vJ+ALF+thq4Be+DM1mvmTyUCQObvezNCNhzfS6abIUd3ou4x5RGH51ouiANeT3wekU6dCw==",
"cpu": [
"arm64"
],
@@ -2735,9 +2735,9 @@
}
},
"node_modules/@tauri-apps/cli-darwin-x64": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.4.tgz",
"integrity": "sha512-zIccfbCoZMfmUpnk6PFCV0keFyfVj1A9XV3Oiiitj/dkTZ9CQvzjhX3XC0XcK4rsTWegfr2PjSrK06aiPAROAw==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.1.0.tgz",
"integrity": "sha512-TasHS442DFs8cSH2eUQzuDBXUST4ECjCd0yyP+zZzvAruiB0Bg+c8A+I/EnqCvBQ2G2yvWLYG8q/LI7c87A5UA==",
"cpu": [
"x64"
],
@@ -2752,9 +2752,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.4.tgz",
"integrity": "sha512-fgQqJzefOGWCBNg4yrVA82Rg4s1XQr5K0dc2rCxBhJfa696e8dQ1LDrnWq/AiO5r+uHfVaoQTIUvxxpFicYRSA==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.1.0.tgz",
"integrity": "sha512-aP7ZBGNL4ny07Cbb6kKpUOSrmhcIK2KhjviTzYlh+pPhAptxnC78xQGD3zKQkTi2WliJLPmBYbOHWWQa57lQ9w==",
"cpu": [
"arm"
],
@@ -2769,9 +2769,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.4.tgz",
"integrity": "sha512-u8wbt5tPA9pI6j+d7jGrfOz9UVCiTp+IYzKNiIqlrDsAjqAUFaNXYHKqOUboeFWEmI4zoCWj6LgpS2OJTQ5FKg==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.1.0.tgz",
"integrity": "sha512-ZTdgD5gLeMCzndMT2f358EkoYkZ5T+Qy6zPzU+l5vv5M7dHVN9ZmblNAYYXmoOuw7y+BY4X/rZvHV9pcGrcanQ==",
"cpu": [
"arm64"
],
@@ -2786,9 +2786,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.4.tgz",
"integrity": "sha512-hntF1V8e3V1hlrESm93PsghDhf3lA5pbvFrRfYxU1c+fVD/jRXGVw8BH3O1lW8MWwhEg1YdhKk01oAgsuHLuig==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.1.0.tgz",
"integrity": "sha512-NzwqjUCilhnhJzusz3d/0i0F1GFrwCQbkwR6yAHUxItESbsGYkZRJk0yMEWkg3PzFnyK4cWTlQJMEU52TjhEzA==",
"cpu": [
"arm64"
],
@@ -2803,9 +2803,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.4.tgz",
"integrity": "sha512-Iq1GGJb+oT1T0ZV8izrgf0cBtlzPCJaWcNueRbf1ZXquMf+FSTyQv+/Lo8rq5T6buOIJOH7cAOTuEWWqiCZteg==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.1.0.tgz",
"integrity": "sha512-TyiIpMEtZxNOQmuFyfJwaaYbg3movSthpBJLIdPlKxSAB2BW0VWLY3/ZfIxm/G2YGHyREkjJvimzYE0i37PnMA==",
"cpu": [
"x64"
],
@@ -2820,9 +2820,9 @@
}
},
"node_modules/@tauri-apps/cli-linux-x64-musl": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.4.tgz",
"integrity": "sha512-9NTk6Pf0bSwXqCBdAA+PDYts9HeHebZzIo8mbRzRyUbER6QngG5HZb9Ka36Z1QWtJjdRy6uxSb4zb/9NuTeHfA==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.1.0.tgz",
"integrity": "sha512-/dQd0TlaxBdJACrR72DhynWftzHDaX32eBtS5WBrNJ+nnNb+znM3gON6nJ9tSE9jgDa6n1v2BkI/oIDtypfUXw==",
"cpu": [
"x64"
],
@@ -2837,9 +2837,9 @@
}
},
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.4.tgz",
"integrity": "sha512-OF2e9oxiBFR8A8wVMOhUx9QGN/I1ZkquWC7gVQBnA56nx9PabJlDT08QBy5UD8USqZFVznnfNr2ehlheQahb3g==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.1.0.tgz",
"integrity": "sha512-NdQJO7SmdYqOcE+JPU7bwg7+odfZMWO6g8xF9SXYCMdUzvM2Gv/AQfikNXz5yS7ralRhNFuW32i5dcHlxh4pDg==",
"cpu": [
"arm64"
],
@@ -2854,9 +2854,9 @@
}
},
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.4.tgz",
"integrity": "sha512-T+hCKB3rFP6q0saHHtR02hm6wr1ZPJ0Mkii3oRTxjPG6BBXoVzHNCYzvdgEGJPTA2sFuAQtJH764NRtNlDMifw==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.1.0.tgz",
"integrity": "sha512-f5h8gKT/cB8s1ticFRUpNmHqkmaLutT62oFDB7N//2YTXnxst7EpMIn1w+QimxTvTk2gcx6EcW6bEk/y2hZGzg==",
"cpu": [
"ia32"
],
@@ -2871,9 +2871,9 @@
}
},
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.4.tgz",
"integrity": "sha512-GVaiI3KWRFLomjJmApHqihhYlkJ+7FqhumhVfBO6Z2tWzZjQyVQgTdNp0kYEuW2WoAYEj0dKY6qd4YM33xYcUA==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.1.0.tgz",
"integrity": "sha512-P/+LrdSSb5Xbho1LRP4haBjFHdyPdjWvGgeopL96OVtrFpYnfC+RctB45z2V2XxqFk3HweDDxk266btjttfjGw==",
"cpu": [
"x64"
],
@@ -3127,6 +3127,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/whatwg-mimetype": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz",
"integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/yauzl": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
@@ -13242,6 +13249,15 @@
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -13751,7 +13767,7 @@
"@lezer/lr": "^1.3.3",
"@react-hook/resize-observer": "^2.0.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/react-query": "^5.55.4",
"@tanstack/react-query": "^5.59.16",
"@tanstack/react-virtual": "^3.10.8",
"@tauri-apps/api": "^2.0.1",
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",
@@ -13786,6 +13802,7 @@
"react-use": "^17.5.1",
"slugify": "^1.6.6",
"uuid": "^10.0.0",
"whatwg-mimetype": "^4.0.0",
"xml-formatter": "^3.6.3"
},
"devDependencies": {
@@ -13799,6 +13816,7 @@
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@types/uuid": "^10.0.0",
"@types/whatwg-mimetype": "^3.0.2",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"decompress": "^4.2.1",

View File

@@ -31,7 +31,7 @@
"tauri-before-dev": "npm run --workspaces --if-present dev"
},
"devDependencies": {
"@tauri-apps/cli": "^2.0.4",
"@tauri-apps/cli": "^2.1.0",
"@typescript-eslint/eslint-plugin": "^8.5.0",
"@typescript-eslint/parser": "^8.5.0",
"eslint": "^8",

316
src-tauri/Cargo.lock generated
View File

@@ -149,7 +149,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -275,7 +275,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -315,7 +315,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -332,7 +332,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -623,7 +623,7 @@ dependencies = [
"proc-macro-crate 2.0.2",
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
"syn_derive",
]
@@ -742,7 +742,7 @@ dependencies = [
"glib",
"libc",
"once_cell",
"thiserror",
"thiserror 1.0.63",
]
[[package]]
@@ -785,7 +785,7 @@ dependencies = [
"semver",
"serde",
"serde_json",
"thiserror",
"thiserror 1.0.63",
]
[[package]]
@@ -1213,7 +1213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -1223,7 +1223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
dependencies = [
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -1270,7 +1270,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -1292,7 +1292,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core 0.20.10",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -1349,7 +1349,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -1362,7 +1362,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustc_version",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -1412,7 +1412,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -1444,7 +1444,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -1557,7 +1557,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -1740,15 +1740,6 @@ 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"
@@ -1793,7 +1784,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -1913,7 +1904,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -2138,7 +2129,7 @@ dependencies = [
"once_cell",
"pin-project-lite",
"smallvec",
"thiserror",
"thiserror 1.0.63",
]
[[package]]
@@ -2174,7 +2165,7 @@ dependencies = [
"memchr",
"once_cell",
"smallvec",
"thiserror",
"thiserror 1.0.63",
]
[[package]]
@@ -2188,7 +2179,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -2267,7 +2258,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -2776,7 +2767,7 @@ checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -2796,7 +2787,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -2908,7 +2899,7 @@ dependencies = [
"combine",
"jni-sys",
"log",
"thiserror",
"thiserror 1.0.63",
"walkdir",
"windows-sys 0.45.0",
]
@@ -2943,39 +2934,16 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "json-patch"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc"
dependencies = [
"jsonptr 0.4.7",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "json-patch"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08"
dependencies = [
"jsonptr 0.6.3",
"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",
"jsonptr",
"serde",
"serde_json",
"thiserror 1.0.63",
]
[[package]]
@@ -3306,7 +3274,7 @@ dependencies = [
"once_cell",
"png",
"serde",
"thiserror",
"thiserror 1.0.63",
"windows-sys 0.59.0",
]
@@ -3345,7 +3313,7 @@ dependencies = [
"ndk-sys",
"num_enum",
"raw-window-handle",
"thiserror",
"thiserror 1.0.63",
]
[[package]]
@@ -3454,7 +3422,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -3516,7 +3484,7 @@ dependencies = [
"proc-macro-crate 2.0.2",
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -3805,7 +3773,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -4092,7 +4060,7 @@ dependencies = [
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -4139,7 +4107,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -4261,7 +4229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e"
dependencies = [
"proc-macro2",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -4339,7 +4307,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
dependencies = [
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -4379,7 +4347,7 @@ dependencies = [
"prost 0.13.3",
"prost-types 0.13.3",
"regex",
"syn 2.0.72",
"syn 2.0.87",
"tempfile",
]
@@ -4393,7 +4361,7 @@ dependencies = [
"itertools 0.12.1",
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -4406,7 +4374,7 @@ dependencies = [
"itertools 0.13.0",
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -4432,7 +4400,7 @@ checksum = "172da1212c02be2c94901440cb27183cd92bff00ebacca5c323bf7520b8f9c04"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -4535,7 +4503,7 @@ dependencies = [
"rustc-hash",
"rustls 0.23.12",
"socket2",
"thiserror",
"thiserror 1.0.63",
"tokio",
"tracing",
]
@@ -4552,7 +4520,7 @@ dependencies = [
"rustc-hash",
"rustls 0.23.12",
"slab",
"thiserror",
"thiserror 1.0.63",
"tinyvec",
"tracing",
]
@@ -4718,7 +4686,7 @@ dependencies = [
"rand_chacha 0.3.1",
"simd_helpers",
"system-deps",
"thiserror",
"thiserror 1.0.63",
"v_frame",
"wasm-bindgen",
]
@@ -4795,7 +4763,7 @@ checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
dependencies = [
"getrandom 0.2.15",
"libredox",
"thiserror",
"thiserror 1.0.63",
]
[[package]]
@@ -5210,7 +5178,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -5268,8 +5236,8 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.72",
"thiserror",
"syn 2.0.87",
"thiserror 1.0.63",
]
[[package]]
@@ -5342,9 +5310,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.210"
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
dependencies = [
"serde_derive",
]
@@ -5372,13 +5340,13 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.210"
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -5389,14 +5357,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
name = "serde_json"
version = "1.0.125"
version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [
"itoa 1.0.11",
"memchr",
@@ -5412,7 +5380,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -5463,20 +5431,7 @@ dependencies = [
"darling 0.20.10",
"proc-macro2",
"quote",
"syn 2.0.72",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap 2.3.0",
"itoa 1.0.11",
"ryu",
"serde",
"unsafe-libyaml",
"syn 2.0.87",
]
[[package]]
@@ -5742,7 +5697,7 @@ dependencies = [
"sha2",
"smallvec",
"sqlformat",
"thiserror",
"thiserror 1.0.63",
"tokio",
"tokio-stream",
"tracing",
@@ -5760,7 +5715,7 @@ dependencies = [
"quote",
"sqlx-core",
"sqlx-macros-core",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -5783,7 +5738,7 @@ dependencies = [
"sqlx-mysql",
"sqlx-postgres",
"sqlx-sqlite",
"syn 2.0.72",
"syn 2.0.87",
"tempfile",
"tokio",
"url",
@@ -5826,7 +5781,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror",
"thiserror 1.0.63",
"tracing",
"whoami",
]
@@ -5864,7 +5819,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
"thiserror",
"thiserror 1.0.63",
"tracing",
"whoami",
]
@@ -5977,9 +5932,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.72"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
@@ -5995,7 +5950,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -6055,9 +6010,9 @@ dependencies = [
[[package]]
name = "tao"
version = "0.30.2"
version = "0.30.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06e48d7c56b3f7425d061886e8ce3b6acfab1993682ed70bef50fd133d721ee6"
checksum = "6682a07cf5bab0b8a2bd20d0a542917ab928b5edb75ebd4eda6b05cbaab872da"
dependencies = [
"bitflags 2.6.0",
"cocoa 0.26.0",
@@ -6128,9 +6083,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.0.6"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3889b392db6d32a105d3757230ea0220090b8f94c90d3e60b6c5eb91178ab1b"
checksum = "e545de0a2dfe296fa67db208266cd397c5a55ae782da77973ef4c4fac90e9f2c"
dependencies = [
"anyhow",
"bytes",
@@ -6166,7 +6121,7 @@ dependencies = [
"tauri-runtime",
"tauri-runtime-wry",
"tauri-utils",
"thiserror",
"thiserror 2.0.3",
"tokio",
"tray-icon",
"url",
@@ -6179,16 +6134,16 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.0.2"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f96827ccfb1aa40d55d0ded79562d18ba18566657a553f992a982d755148376"
checksum = "7bd2a4bcfaf5fb9f4be72520eefcb61ae565038f8ccba2a497d8c28f463b8c01"
dependencies = [
"anyhow",
"cargo_toml",
"dirs",
"glob",
"heck 0.5.0",
"json-patch 3.0.1",
"json-patch",
"schemars",
"semver",
"serde",
@@ -6201,14 +6156,14 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.0.2"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8947f16f47becd9e9cd39b74ee337fd1981574d78819be18e4384d85e5a0b82f"
checksum = "bf79faeecf301d3e969b1fae977039edb77a4c1f25cc0a961be298b54bff97cf"
dependencies = [
"base64 0.22.1",
"brotli 7.0.0",
"ico",
"json-patch 2.0.0",
"json-patch",
"plist",
"png",
"proc-macro2",
@@ -6217,9 +6172,9 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"syn 2.0.72",
"syn 2.0.87",
"tauri-utils",
"thiserror",
"thiserror 2.0.3",
"time",
"url",
"uuid",
@@ -6228,14 +6183,14 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.0.2"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bd1c8d4a66799d3438747c3a79705cd665a95d6f24cb5f315413ff7a981fe2a"
checksum = "c52027c8c5afb83166dacddc092ee8fff50772f9646d461d8c33ee887e447a03"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
"tauri-codegen",
"tauri-utils",
]
@@ -6270,7 +6225,7 @@ dependencies = [
"serde_json",
"tauri",
"tauri-plugin",
"thiserror",
"thiserror 1.0.63",
]
[[package]]
@@ -6287,7 +6242,7 @@ dependencies = [
"tauri",
"tauri-plugin",
"tauri-plugin-fs",
"thiserror",
"thiserror 1.0.63",
"url",
]
@@ -6307,7 +6262,7 @@ dependencies = [
"serde_repr",
"tauri",
"tauri-plugin",
"thiserror",
"thiserror 1.0.63",
"url",
"uuid",
]
@@ -6330,7 +6285,7 @@ dependencies = [
"swift-rs",
"tauri",
"tauri-plugin",
"thiserror",
"thiserror 1.0.63",
"time",
]
@@ -6349,7 +6304,7 @@ dependencies = [
"sys-locale",
"tauri",
"tauri-plugin",
"thiserror",
"thiserror 1.0.63",
]
[[package]]
@@ -6369,7 +6324,7 @@ dependencies = [
"shared_child",
"tauri",
"tauri-plugin",
"thiserror",
"thiserror 1.0.63",
"tokio",
]
@@ -6395,7 +6350,7 @@ dependencies = [
"tauri",
"tauri-plugin",
"tempfile",
"thiserror",
"thiserror 1.0.63",
"time",
"tokio",
"url",
@@ -6415,14 +6370,14 @@ dependencies = [
"serde_json",
"tauri",
"tauri-plugin",
"thiserror",
"thiserror 1.0.63",
]
[[package]]
name = "tauri-runtime"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1ef7363e7229ac8d04e8a5d405670dbd43dde8fc4bc3bc56105c35452d03784"
checksum = "cce18d43f80d4aba3aa8a0c953bbe835f3d0f2370aca75e8dbb14bd4bab27958"
dependencies = [
"dpi",
"gtk",
@@ -6432,16 +6387,16 @@ dependencies = [
"serde",
"serde_json",
"tauri-utils",
"thiserror",
"thiserror 2.0.3",
"url",
"windows",
]
[[package]]
name = "tauri-runtime-wry"
version = "2.1.2"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62fa2068e8498ad007b54d5773d03d57c3ff6dd96f8c8ce58beff44d0d5e0d30"
checksum = "9f442a38863e10129ffe2cec7bd09c2dcf8a098a3a27801a476a304d5bb991d2"
dependencies = [
"gtk",
"http 1.1.0",
@@ -6465,9 +6420,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.0.2"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc65d6f5c54e56b66258948a6d9e47a82ea41f4b5a7612bfbdd1634c2913ed0"
checksum = "9271a88f99b4adea0dc71d0baca4505475a0bbd139fb135f62958721aaa8fe54"
dependencies = [
"brotli 7.0.0",
"cargo_metadata",
@@ -6475,8 +6430,9 @@ dependencies = [
"dunce",
"glob",
"html5ever",
"http 1.1.0",
"infer",
"json-patch 2.0.0",
"json-patch",
"kuchikiki",
"log",
"memchr",
@@ -6491,7 +6447,7 @@ dependencies = [
"serde_json",
"serde_with",
"swift-rs",
"thiserror",
"thiserror 2.0.3",
"toml 0.8.2",
"url",
"urlpattern",
@@ -6554,7 +6510,16 @@ version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.63",
]
[[package]]
name = "thiserror"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
dependencies = [
"thiserror-impl 2.0.3",
]
[[package]]
@@ -6565,7 +6530,18 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
name = "thiserror-impl"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
@@ -6663,7 +6639,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -6847,7 +6823,7 @@ dependencies = [
"proc-macro2",
"prost-build",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -6915,7 +6891,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -6944,7 +6920,7 @@ dependencies = [
"once_cell",
"png",
"serde",
"thiserror",
"thiserror 1.0.63",
"windows-sys 0.59.0",
]
@@ -6963,7 +6939,7 @@ dependencies = [
"chrono",
"lazy_static",
"serde_json",
"thiserror",
"thiserror 1.0.63",
"ts-rs-macros",
]
@@ -6975,7 +6951,7 @@ checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
"termcolor",
]
@@ -7097,12 +7073,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
@@ -7281,7 +7251,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
"wasm-bindgen-shared",
]
@@ -7315,7 +7285,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -7490,7 +7460,7 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -7499,7 +7469,7 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886"
dependencies = [
"thiserror",
"thiserror 1.0.63",
"windows",
"windows-core 0.58.0",
]
@@ -7604,7 +7574,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -7615,7 +7585,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -7881,12 +7851,13 @@ dependencies = [
[[package]]
name = "wry"
version = "0.46.1"
version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f8c948dc5f7c23bd93ba03b85b7f679852589bb78e150424d993171e4ef7b73"
checksum = "553ca1ce149982123962fac2506aa75b8b76288779a77e72b12fa2fc34938647"
dependencies = [
"base64 0.22.1",
"block2",
"cookie",
"crossbeam-channel",
"dpi",
"dunce",
@@ -7910,7 +7881,8 @@ dependencies = [
"sha2",
"soup3",
"tao-macros",
"thiserror",
"thiserror 1.0.63",
"url",
"webkit2gtk",
"webkit2gtk-sys",
"webview2-com",
@@ -7992,7 +7964,6 @@ dependencies = [
name = "yaak-app"
version = "0.0.0"
dependencies = [
"anyhow",
"base64 0.22.1",
"chrono",
"cocoa 0.26.0",
@@ -8010,7 +7981,6 @@ dependencies = [
"reqwest_cookie_store",
"serde",
"serde_json",
"serde_yaml",
"tauri",
"tauri-build",
"tauri-plugin-clipboard-manager",
@@ -8021,7 +7991,6 @@ dependencies = [
"tauri-plugin-shell",
"tauri-plugin-updater",
"tauri-plugin-window-state",
"thiserror",
"tokio",
"tokio-stream",
"urlencoding",
@@ -8038,6 +8007,7 @@ name = "yaak_grpc"
version = "0.1.0"
dependencies = [
"anyhow",
"async-recursion",
"dunce",
"hyper 0.14.30",
"hyper-rustls 0.24.2",
@@ -8073,7 +8043,7 @@ dependencies = [
"serde_json",
"sqlx",
"tauri",
"thiserror",
"thiserror 1.0.63",
"ts-rs",
]
@@ -8094,7 +8064,7 @@ dependencies = [
"serde_json",
"tauri",
"tauri-plugin-shell",
"thiserror",
"thiserror 1.0.63",
"tokio",
"tonic 0.12.3",
"tonic-build",
@@ -8199,7 +8169,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.72",
"syn 2.0.87",
]
[[package]]
@@ -8220,7 +8190,7 @@ dependencies = [
"displaydoc",
"indexmap 2.3.0",
"memchr",
"thiserror",
"thiserror 1.0.63",
]
[[package]]

View File

@@ -16,7 +16,7 @@ crate-type = ["staticlib", "cdylib", "lib"]
strip = true # Automatically strip symbols from the binary.
[build-dependencies]
tauri-build = { version = "2.0.2", features = [] }
tauri-build = { version = "2.0.3", features = [] }
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2.7"
@@ -29,9 +29,8 @@ openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installa
yaak_grpc = { path = "yaak_grpc" }
yaak_templates = { path = "yaak_templates" }
yaak_plugin_runtime = { workspace = true }
yaak_sse = { workspace = true }
yaak_models = { workspace = true }
yaak_sse = { path = "yaak_sse" }
anyhow = "1.0.86"
base64 = "0.22.0"
chrono = { version = "0.4.31", features = ["serde"] }
datetime = "0.5.2"
@@ -42,10 +41,9 @@ rand = "0.8.5"
regex = "1.10.2"
reqwest = { version = "0.12.4", features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json", "native-tls-alpn"] }
reqwest_cookie_store = "0.8.0"
serde = { version = "1.0.198", features = ["derive"] }
serde_json = { version = "1.0.116", features = ["raw_value"] }
serde_yaml = "0.9.34"
tauri = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["raw_value"] }
tauri = { workspace = true, features = ["devtools", "protocol-asset"] }
tauri-plugin-shell = { workspace = true }
tauri-plugin-clipboard-manager = "2.0.1"
tauri-plugin-dialog = "2.0.3"
@@ -57,13 +55,17 @@ tauri-plugin-window-state = "2.0.1"
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"
eventsource-client = { git = "https://github.com/yaakapp/rust-eventsource-client", version = "0.13.0" }
[workspace.dependencies]
yaak_models = { path = "yaak_models" }
yaak_sse = { path = "yaak_sse" }
yaak_plugin_runtime = { path = "yaak_plugin_runtime" }
serde = "1.0.215"
serde_json = "1.0.132"
tauri-plugin-shell = "2.0.2"
tauri = { version = "2.0.6", features = ["devtools", "protocol-asset"] }
tauri = { version = "2.1.1", features = ["devtools", "protocol-asset"] }
thiserror = "2.0.3"
ts-rs = "10.0.0"

View File

@@ -84,7 +84,7 @@
}
},
"permissions": {
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```",
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
"type": "array",
"items": {
"$ref": "#/definitions/PermissionEntry"

View File

@@ -84,7 +84,7 @@
}
},
"permissions": {
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```",
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
"type": "array",
"items": {
"$ref": "#/definitions/PermissionEntry"

View File

@@ -74,8 +74,8 @@ use yaak_plugin_runtime::events::{
};
use yaak_plugin_runtime::plugin_handle::PluginHandle;
use yaak_sse::sse::ServerSentEvent;
use yaak_templates::{Parser, Tokens};
use yaak_templates::format::format_json;
use yaak_templates::{Parser, Tokens};
mod analytics;
mod export_resources;
@@ -174,7 +174,10 @@ async fn cmd_grpc_reflect<R: Runtime>(
window: WebviewWindow<R>,
grpc_handle: State<'_, Mutex<GrpcHandle>>,
) -> Result<Vec<ServiceDefinition>, String> {
let req = get_grpc_request(&window, request_id).await.map_err(|e| e.to_string())?;
let req = get_grpc_request(&window, request_id)
.await
.map_err(|e| e.to_string())?
.ok_or("Failed to find GRPC request")?;
let uri = safe_uri(&req.url);
@@ -201,7 +204,10 @@ async fn cmd_grpc_go<R: Runtime>(
Some(id) => Some(get_environment(&window, id).await.map_err(|e| e.to_string())?),
None => None,
};
let req = get_grpc_request(&window, request_id).await.map_err(|e| e.to_string())?;
let req = get_grpc_request(&window, request_id)
.await
.map_err(|e| e.to_string())?
.ok_or("Failed to find GRPC request")?;
let workspace = get_workspace(&window, &req.workspace_id).await.map_err(|e| e.to_string())?;
let req = render_grpc_request(
&req,
@@ -1436,12 +1442,12 @@ async fn cmd_get_folder(id: &str, w: WebviewWindow) -> Result<Folder, String> {
}
#[tauri::command]
async fn cmd_get_grpc_request(id: &str, w: WebviewWindow) -> Result<GrpcRequest, String> {
async fn cmd_get_grpc_request(id: &str, w: WebviewWindow) -> Result<Option<GrpcRequest>, String> {
get_grpc_request(&w, id).await.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_get_http_request(id: &str, w: WebviewWindow) -> Result<HttpRequest, String> {
async fn cmd_get_http_request(id: &str, w: WebviewWindow) -> Result<Option<HttpRequest>, String> {
get_http_request(&w, id).await.map_err(|e| e.to_string())
}
@@ -2079,7 +2085,7 @@ async fn handle_plugin_event<R: Runtime>(
}))
}
InternalEventPayload::GetHttpRequestByIdRequest(req) => {
let http_request = get_http_request(app_handle, req.id.as_str()).await.ok();
let http_request = get_http_request(app_handle, req.id.as_str()).await.unwrap();
Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
http_request,
}))

View File

@@ -22,3 +22,4 @@ tauri = { workspace = true }
tauri-plugin-shell = { workspace = true }
md5 = "0.7.0"
dunce = "1.0.4"
async-recursion = "1.1.1"

View File

@@ -4,6 +4,7 @@ use std::path::PathBuf;
use std::str::FromStr;
use anyhow::anyhow;
use async_recursion::async_recursion;
use hyper::client::HttpConnector;
use hyper::Client;
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
@@ -38,9 +39,8 @@ pub async fn fill_pool_from_files(
.expect("failed to resolve protoc include directory");
// HACK: Remove UNC prefix for Windows paths
let global_import_dir = dunce::simplified(global_import_dir.as_path())
.to_string_lossy()
.to_string();
let global_import_dir =
dunce::simplified(global_import_dir.as_path()).to_string_lossy().to_string();
let desc_path = dunce::simplified(desc_path.as_path());
let mut args = vec![
@@ -89,12 +89,9 @@ pub async fn fill_pool_from_files(
let bytes = fs::read(desc_path).await.map_err(|e| e.to_string())?;
let fdp = FileDescriptorSet::decode(bytes.deref()).map_err(|e| e.to_string())?;
pool.add_file_descriptor_set(fdp)
.map_err(|e| e.to_string())?;
pool.add_file_descriptor_set(fdp).map_err(|e| e.to_string())?;
fs::remove_file(desc_path)
.await
.map_err(|e| e.to_string())?;
fs::remove_file(desc_path).await.map_err(|e| e.to_string())?;
Ok(pool)
}
@@ -107,6 +104,10 @@ pub async fn fill_pool_from_reflection(uri: &Uri) -> Result<DescriptorPool, Stri
if service == "grpc.reflection.v1alpha.ServerReflection" {
continue;
}
if service == "grpc.reflection.v1.ServerReflection"{
// TODO: update reflection client to use v1
continue;
}
file_descriptor_set_from_service_name(&service, &mut pool, &mut client).await;
}
@@ -120,10 +121,7 @@ pub fn get_transport() -> Client<HttpsConnector<HttpConnector>, BoxBody> {
http_connector.enforce_http(false);
http_connector
});
Client::builder()
.pool_max_idle_per_host(0)
.http2_only(true)
.build(connector)
Client::builder().pool_max_idle_per_host(0).http2_only(true).build(connector)
}
async fn list_services(
@@ -137,11 +135,7 @@ async fn list_services(
_ => panic!("Expected a ListServicesResponse variant"),
};
Ok(list_services_response
.service
.iter()
.map(|s| s.name.clone())
.collect::<Vec<_>>())
Ok(list_services_response.service.iter().map(|s| s.name.clone()).collect::<Vec<_>>())
}
async fn file_descriptor_set_from_service_name(
@@ -153,14 +147,11 @@ async fn file_descriptor_set_from_service_name(
client,
MessageRequest::FileContainingSymbol(service_name.into()),
)
.await
.await
{
Ok(resp) => resp,
Err(e) => {
warn!(
"Error fetching file descriptor for service {}: {}",
service_name, e
);
warn!("Error fetching file descriptor for service {}: {}", service_name, e);
return;
}
};
@@ -170,16 +161,37 @@ async fn file_descriptor_set_from_service_name(
_ => panic!("Expected a FileDescriptorResponse variant"),
};
for fd in file_descriptor_response.file_descriptor_proto {
add_file_descriptors_to_pool(file_descriptor_response.file_descriptor_proto, pool, client)
.await;
}
#[async_recursion]
async fn add_file_descriptors_to_pool(
fds: Vec<Vec<u8>>,
pool: &mut DescriptorPool,
client: &mut ServerReflectionClient<Client<HttpsConnector<HttpConnector>, BoxBody>>,
) {
let mut topo_sort = topology::SimpleTopoSort::new();
let mut fd_mapping = std::collections::HashMap::with_capacity(fds.len());
for fd in fds {
let fdp = FileDescriptorProto::decode(fd.deref()).unwrap();
// Add deps first or else we'll get an error
for dep_name in fdp.clone().dependency {
file_descriptor_set_by_filename(&dep_name, pool, client).await;
}
topo_sort.insert(fdp.name().to_string(), fdp.dependency.clone());
fd_mapping.insert(fdp.name().to_string(), fdp);
}
pool.add_file_descriptor_proto(fdp)
.expect("add file descriptor proto");
for node in topo_sort {
match node {
Ok(node) => {
if let Some(fdp) = fd_mapping.remove(&node) {
pool.add_file_descriptor_proto(fdp).expect("add file descriptor proto");
} else {
file_descriptor_set_by_filename(node.as_str(), pool, client).await;
}
}
Err(_) => panic!("proto file got cycle!"),
}
}
}
@@ -206,11 +218,8 @@ async fn file_descriptor_set_by_filename(
}
};
for fd in file_descriptor_response.file_descriptor_proto {
let fdp = FileDescriptorProto::decode(fd.deref()).unwrap();
pool.add_file_descriptor_proto(fdp)
.expect("add file descriptor proto");
}
add_file_descriptors_to_pool(file_descriptor_response.file_descriptor_proto, pool, client)
.await;
}
async fn send_reflection_request(
@@ -249,4 +258,137 @@ pub fn method_desc_to_path(md: &MethodDescriptor) -> PathAndQuery {
.ok_or_else(|| anyhow!("invalid method path"))
.expect("invalid method path");
PathAndQuery::from_str(&format!("/{}/{}", namespace, method_name)).expect("invalid method path")
}
}
mod topology {
use std::collections::{HashMap, HashSet};
pub struct SimpleTopoSort<T> {
out_graph: HashMap<T, HashSet<T>>,
in_graph: HashMap<T, HashSet<T>>,
}
impl<T> SimpleTopoSort<T>
where
T: Eq + std::hash::Hash + Clone,
{
pub fn new() -> Self {
SimpleTopoSort {
out_graph: HashMap::new(),
in_graph: HashMap::new(),
}
}
pub fn insert<I: IntoIterator<Item = T>>(&mut self, node: T, deps: I) {
self.out_graph.entry(node.clone()).or_insert(HashSet::new());
for dep in deps {
self.out_graph.entry(node.clone()).or_insert(HashSet::new()).insert(dep.clone());
self.in_graph.entry(dep.clone()).or_insert(HashSet::new()).insert(node.clone());
}
}
}
impl<T> IntoIterator for SimpleTopoSort<T>
where
T: Eq + std::hash::Hash + Clone,
{
type IntoIter = SimpleTopoSortIter<T>;
type Item = <SimpleTopoSortIter<T> as Iterator>::Item;
fn into_iter(self) -> Self::IntoIter {
SimpleTopoSortIter::new(self)
}
}
pub struct SimpleTopoSortIter<T> {
data: SimpleTopoSort<T>,
zero_indegree: Vec<T>,
}
impl<T> SimpleTopoSortIter<T>
where
T: Eq + std::hash::Hash + Clone,
{
pub fn new(data: SimpleTopoSort<T>) -> Self {
let mut zero_indegree = Vec::new();
for (node, _) in data.in_graph.iter() {
if !data.out_graph.contains_key(node) {
zero_indegree.push(node.clone());
}
}
for (node, deps) in data.out_graph.iter(){
if deps.is_empty(){
zero_indegree.push(node.clone());
}
}
SimpleTopoSortIter {
data,
zero_indegree,
}
}
}
impl<T> Iterator for SimpleTopoSortIter<T>
where
T: Eq + std::hash::Hash + Clone,
{
type Item = Result<T, &'static str>;
fn next(&mut self) -> Option<Self::Item> {
if self.zero_indegree.is_empty() {
if self.data.out_graph.is_empty() {
return None;
}
return Some(Err("Cycle detected"));
}
let node = self.zero_indegree.pop().unwrap();
if let Some(parents) = self.data.in_graph.get(&node){
for parent in parents.iter(){
let deps = self.data.out_graph.get_mut(parent).unwrap();
deps.remove(&node);
if deps.is_empty() {
self.zero_indegree.push(parent.clone());
}
}
}
self.data.out_graph.remove(&node);
Some(Ok(node))
}
}
#[test]
fn test_sort(){
{
let mut topo_sort = SimpleTopoSort::new();
topo_sort.insert("a", []);
for node in topo_sort {
match node {
Ok(n) => assert_eq!(n, "a"),
Err(e) => panic!("err {}", e),
}
}
}
{
let mut topo_sort = SimpleTopoSort::new();
topo_sort.insert("a", ["b"]);
topo_sort.insert("b", []);
let mut iter = topo_sort.into_iter();
match iter.next() {
Some(Ok(n)) => assert_eq!(n, "b"),
_ => panic!("err"),
}
match iter.next() {
Some(Ok(n)) => assert_eq!(n, "a"),
_ => panic!("err"),
}
assert_eq!(iter.next(), None);
}
}
}

View File

@@ -6,8 +6,10 @@ pub enum Error {
SqlError(#[from] rusqlite::Error),
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("Model not found {0}")]
ModelNotFound(String),
#[error("unknown error")]
Unknown,
}
pub type Result<T> = std::result::Result<T, Error>;
pub type Result<T> = std::result::Result<T, Error>;

View File

@@ -1,5 +1,6 @@
use std::fs;
use crate::error::Error::ModelNotFound;
use crate::error::Result;
use crate::models::{
CookieJar, CookieJarIden, Environment, EnvironmentIden, Folder, FolderIden, GrpcConnection,
@@ -299,7 +300,12 @@ pub async fn duplicate_grpc_request<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<GrpcRequest> {
let mut request = get_grpc_request(window, id).await?.clone();
let mut request = match get_grpc_request(window, id).await? {
Some(r) => r,
None => {
return Err(ModelNotFound(id.to_string()));
}
};
request.id = "".to_string();
upsert_grpc_request(window, &request).await
}
@@ -308,7 +314,12 @@ pub async fn delete_grpc_request<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<GrpcRequest> {
let req = get_grpc_request(window, id).await?;
let req = match get_grpc_request(window, id).await? {
Some(r) => r,
None => {
return Err(ModelNotFound(id.to_string()));
}
};
let dbm = &*window.app_handle().state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -393,7 +404,10 @@ pub async fn upsert_grpc_request<R: Runtime>(
Ok(emit_upserted_model(window, m))
}
pub async fn get_grpc_request<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<GrpcRequest> {
pub async fn get_grpc_request<R: Runtime>(
mgr: &impl Manager<R>,
id: &str,
) -> Result<Option<GrpcRequest>> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -403,7 +417,7 @@ pub async fn get_grpc_request<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Re
.cond_where(Expr::col(GrpcRequestIden::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())?)
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into()).optional()?)
}
pub async fn list_grpc_requests<R: Runtime>(
@@ -1083,7 +1097,10 @@ pub async fn duplicate_http_request<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<HttpRequest> {
let mut request = get_http_request(window, id).await?.clone();
let mut request = match get_http_request(window, id).await? {
None => return Err(ModelNotFound(id.to_string())),
Some(r) => r,
};
request.id = "".to_string();
upsert_http_request(window, request).await
}
@@ -1181,7 +1198,10 @@ pub async fn list_http_requests<R: Runtime>(
Ok(items.map(|v| v.unwrap()).collect())
}
pub async fn get_http_request<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Result<HttpRequest> {
pub async fn get_http_request<R: Runtime>(
mgr: &impl Manager<R>,
id: &str,
) -> Result<Option<HttpRequest>> {
let dbm = &*mgr.state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();
@@ -1191,14 +1211,17 @@ pub async fn get_http_request<R: Runtime>(mgr: &impl Manager<R>, id: &str) -> Re
.cond_where(Expr::col(HttpRequestIden::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())?)
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into()).optional()?)
}
pub async fn delete_http_request<R: Runtime>(
window: &WebviewWindow<R>,
id: &str,
) -> Result<HttpRequest> {
let req = get_http_request(window, id).await?;
let req = match get_http_request(window, id).await? {
None => return Err(ModelNotFound(id.to_string())),
Some(r) => r,
};
// DB deletes will cascade but this will delete the files
delete_all_http_responses_for_request(window, id).await?;
@@ -1258,7 +1281,10 @@ pub async fn create_http_response<R: Runtime>(
delete_http_response(window, response.id.as_str()).await?;
}
let req = get_http_request(window, request_id).await?;
let req = match get_http_request(window, request_id).await? {
None => return Err(ModelNotFound(request_id.to_string())),
Some(r) => r,
};
let id = generate_model_id(ModelType::TypeHttpResponse);
let dbm = &*window.app_handle().state::<SqliteConnection>();
let db = dbm.0.lock().await.get().unwrap();

View File

@@ -1,6 +1,6 @@
import { lazy } from 'react';
import { createBrowserRouter, Navigate, RouterProvider, useParams } from 'react-router-dom';
import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
import { paths, useAppRoutes } from '../hooks/useAppRoutes';
import { DefaultLayout } from './DefaultLayout';
import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace';
import RouteError from './RouteError';
@@ -19,19 +19,23 @@ const router = createBrowserRouter([
element: <RedirectToLatestWorkspace />,
},
{
path: routePaths.workspaces(),
path: paths.workspaces(),
element: <RedirectToLatestWorkspace />,
},
{
path: routePaths.workspace({
path: paths.workspace({
workspaceId: ':workspaceId',
environmentId: null,
cookieJarId: null,
}),
element: <LazyWorkspace />,
},
{
path: routePaths.request({
path: paths.request({
workspaceId: ':workspaceId',
requestId: ':requestId',
environmentId: null,
cookieJarId: null,
}),
element: <LazyWorkspace />,
},
@@ -40,8 +44,10 @@ const router = createBrowserRouter([
element: <RedirectLegacyEnvironmentURLs />,
},
{
path: routePaths.workspaceSettings({
path: paths.workspaceSettings({
workspaceId: ':workspaceId',
environmentId: null,
cookieJarId: null,
}),
element: <LazySettings />,
},
@@ -64,13 +70,13 @@ function RedirectLegacyEnvironmentURLs() {
workspaceId?: string;
environmentId?: string;
}>();
const environmentId = rawEnvironmentId === '__default__' ? undefined : rawEnvironmentId;
const environmentId = (rawEnvironmentId === '__default__' ? undefined : rawEnvironmentId) ?? null;
let to;
if (workspaceId != null && requestId != null) {
to = routes.paths.request({ workspaceId, environmentId, requestId });
to = routes.paths.request({ workspaceId, environmentId, requestId, cookieJarId: null });
} else if (workspaceId != null) {
to = routes.paths.workspace({ workspaceId, environmentId });
to = routes.paths.workspace({ workspaceId, environmentId, cookieJarId: null });
} else {
to = routes.paths.workspaces();
}

View File

@@ -271,7 +271,8 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
return routes.navigate('request', {
workspaceId: r.workspaceId,
requestId: r.id,
environmentId: activeEnvironment?.id,
environmentId: activeEnvironment?.id ?? null,
cookieJarId: activeCookieJar?.id ?? null,
});
},
});
@@ -313,7 +314,8 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
workspaceCommands,
sortedRequests,
routes,
activeEnvironment,
activeEnvironment?.id,
activeCookieJar?.id,
sortedEnvironments,
setActiveEnvironmentId,
sortedWorkspaces,

View File

@@ -45,6 +45,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
const handleCreateEnvironment = async () => {
const e = await createEnvironment.mutateAsync();
if (e == null) return;
setSelectedEnvironmentId(e.id);
};

View File

@@ -2,12 +2,15 @@ import type { HttpRequest } from '@yaakapp-internal/models';
import { updateSchema } from 'cm6-graphql';
import type { EditorView } from 'codemirror';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useLocalStorage } from 'react-use';
import { useIntrospectGraphQL } from '../hooks/useIntrospectGraphQL';
import { tryFormatJson } from '../lib/formatters';
import { Button } from './core/Button';
import { Dropdown } from './core/Dropdown';
import type { EditorProps } from './core/Editor';
import { Editor, formatGraphQL } from './core/Editor';
import { FormattedError } from './core/FormattedError';
import { Icon } from './core/Icon';
import { Separator } from './core/Separator';
import { useDialog } from './DialogContext';
@@ -19,18 +22,25 @@ type Props = Pick<EditorProps, 'heightMode' | 'className' | 'forceUpdateKey'> &
export function GraphQLEditor({ body, onChange, baseRequest, ...extraEditorProps }: Props) {
const editorViewRef = useRef<EditorView>(null);
const { schema, isLoading, error, refetch } = useIntrospectGraphQL(baseRequest);
const [currentBody, setCurrentBody] = useState<{ query: string; variables: string | undefined }>(() => {
// Migrate text bodies to GraphQL format
// NOTE: This is how GraphQL used to be stored
if ('text' in body) {
const b = tryParseJson(body.text, {});
const variables = JSON.stringify(b.variables || undefined, null, 2);
return { query: b.query ?? '', variables };
}
return { query: body.query ?? '', variables: body.variables ?? '' };
const [autoIntrospectDisabled, setAutoIntrospectDisabled] = useLocalStorage<
Record<string, boolean>
>('graphQLAutoIntrospectDisabled', {});
const { schema, isLoading, error, refetch, clear } = useIntrospectGraphQL(baseRequest, {
disabled: autoIntrospectDisabled?.[baseRequest.id],
});
const [currentBody, setCurrentBody] = useState<{ query: string; variables: string | undefined }>(
() => {
// Migrate text bodies to GraphQL format
// NOTE: This is how GraphQL used to be stored
if ('text' in body) {
const b = tryParseJson(body.text, {});
const variables = JSON.stringify(b.variables || undefined, null, 2);
return { query: b.query ?? '', variables };
}
return { query: body.query ?? '', variables: body.variables ?? '' };
},
);
const handleChangeQuery = (query: string) => {
const newBody = { query, variables: currentBody.variables || undefined };
@@ -52,52 +62,106 @@ export function GraphQLEditor({ body, onChange, baseRequest, ...extraEditorProps
const dialog = useDialog();
const actions = useMemo<EditorProps['actions']>(() => {
const isValid = error || isLoading;
if (!isValid) {
return [];
}
const actions: EditorProps['actions'] = [
const actions = useMemo<EditorProps['actions']>(
() => [
<div key="introspection" className="!opacity-100">
<Button
key="introspection"
size="xs"
color={error ? 'danger' : 'secondary'}
isLoading={isLoading}
onClick={() => {
dialog.show({
title: 'Introspection Failed',
size: 'dynamic',
id: 'introspection-failed',
render: () => (
<>
<FormattedError>{error ?? 'unknown'}</FormattedError>
<div className="w-full my-4">
<Button
onClick={() => {
dialog.hide('introspection-failed');
refetch();
}}
className="ml-auto"
color="primary"
size="sm"
>
Try Again
</Button>
</div>
</>
),
});
}}
>
{error ? 'Introspection Failed' : 'Introspecting'}
</Button>
{schema === undefined ? null /* Initializing */ : !error ? (
<Dropdown
items={[
{
key: 'refresh',
label: 'Refetch',
leftSlot: <Icon icon="refresh" />,
onSelect: refetch,
},
{
key: 'clear',
label: 'Clear',
onSelect: clear,
hidden: !schema,
variant: 'danger',
leftSlot: <Icon icon="trash" />,
},
{ type: 'separator', label: 'Setting' },
{
key: 'auto_fetch',
label: 'Automatic Introspection',
onSelect: () => {
setAutoIntrospectDisabled({
...autoIntrospectDisabled,
[baseRequest.id]: !autoIntrospectDisabled?.[baseRequest.id],
});
},
leftSlot: (
<Icon
icon={
autoIntrospectDisabled?.[baseRequest.id]
? 'check_square_unchecked'
: 'check_square_checked'
}
/>
),
},
]}
>
<Button
size="sm"
variant="border"
title="Refetch Schema"
isLoading={isLoading}
color={isLoading || schema ? 'default' : 'warning'}
>
{isLoading ? 'Introspecting' : schema ? 'Schema' : 'No Schema'}
</Button>
</Dropdown>
) : (
<Button
size="sm"
color="danger"
isLoading={isLoading}
onClick={() => {
dialog.show({
title: 'Introspection Failed',
size: 'dynamic',
id: 'introspection-failed',
render: ({ hide }) => (
<>
<FormattedError>{error ?? 'unknown'}</FormattedError>
<div className="w-full my-4">
<Button
onClick={async () => {
hide();
await refetch();
}}
className="ml-auto"
color="primary"
size="sm"
>
Try Again
</Button>
</div>
</>
),
});
}}
>
Introspection Failed
</Button>
)}
</div>,
];
return actions;
}, [dialog, error, isLoading, refetch]);
],
[
isLoading,
refetch,
error,
autoIntrospectDisabled,
baseRequest.id,
clear,
schema,
setAutoIntrospectDisabled,
dialog,
],
);
return (
<div className="h-full w-full grid grid-cols-1 grid-rows-[minmax(0,100%)_auto]">

View File

@@ -71,7 +71,11 @@ export function MoveToWorkspaceDialog({ onDone, request, activeWorkspaceId }: Pr
className="mr-auto min-w-[5rem]"
onClick={() => {
toast.hide('workspace-moved');
routes.navigate('workspace', { workspaceId: selectedWorkspaceId });
routes.navigate('workspace', {
workspaceId: selectedWorkspaceId,
cookieJarId: null,
environmentId: null,
});
}}
>
Switch to Workspace

View File

@@ -1,6 +1,7 @@
import classNames from 'classnames';
import { useMemo, useRef } from 'react';
import { useKeyPressEvent } from 'react-use';
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
@@ -20,6 +21,7 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
const activeRequest = useActiveRequest();
const activeWorkspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const [activeCookieJar] = useActiveCookieJar();
const routes = useAppRoutes();
const allRecentRequestIds = useRecentRequests();
const recentRequestIds = useMemo(() => allRecentRequestIds.slice(1), [allRecentRequestIds]);
@@ -57,8 +59,9 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
onSelect: () => {
routes.navigate('request', {
requestId: request.id,
environmentId: activeEnvironment?.id,
workspaceId: activeWorkspace.id,
environmentId: activeEnvironment?.id ?? null,
cookieJarId: activeCookieJar?.id ?? null,
});
},
});
@@ -76,7 +79,7 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
}
return recentRequestItems.slice(0, 20);
}, [activeWorkspace, activeEnvironment?.id, recentRequestIds, requests, routes]);
}, [activeWorkspace, recentRequestIds, requests, routes, activeEnvironment?.id, activeCookieJar?.id]);
return (
<Dropdown ref={dropdownRef} items={items}>

View File

@@ -41,7 +41,7 @@ export const RecentResponsesDropdown = function ResponsePane({
},
{
key: 'copy',
label: 'Copy to Clipboard',
label: 'Copy Body',
onSelect: copyResponse.mutate,
leftSlot: <Icon icon="copy" />,
hidden: responses.length === 0,

View File

@@ -1,6 +1,7 @@
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { getRecentCookieJars } from '../hooks/useRecentCookieJars';
import { getRecentEnvironments } from '../hooks/useRecentEnvironments';
import { getRecentRequests } from '../hooks/useRecentRequests';
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
@@ -20,13 +21,14 @@ export function RedirectToLatestWorkspace() {
(async function () {
const workspaceId = recentWorkspaces[0] ?? workspaces[0]?.id ?? 'n/a';
const environmentId = (await getRecentEnvironments(workspaceId))[0];
const requestId = (await getRecentRequests(workspaceId))[0];
const environmentId = (await getRecentEnvironments(workspaceId))[0] ?? null;
const cookieJarId = (await getRecentCookieJars(workspaceId))[0] ?? null;
const requestId = (await getRecentRequests(workspaceId))[0] ?? null;
if (workspaceId != null && requestId != null) {
navigate(routes.paths.request({ workspaceId, environmentId, requestId }));
navigate(routes.paths.request({ workspaceId, environmentId, requestId, cookieJarId }));
} else {
navigate(routes.paths.workspace({ workspaceId, environmentId }));
navigate(routes.paths.workspace({ workspaceId, environmentId, cookieJarId }));
}
})();
}, [navigate, recentWorkspaces, routes.paths, workspaces, workspaces.length]);

View File

@@ -13,6 +13,7 @@ import React, { Fragment, useCallback, useMemo, useRef, useState } from 'react';
import type { XYCoord } from 'react-dnd';
import { useDrag, useDrop } from 'react-dnd';
import { useKey, useKeyPressEvent } from 'react-use';
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
import { useActiveRequest } from '../hooks/useActiveRequest';
@@ -73,6 +74,7 @@ export function Sidebar({ className }: Props) {
const sidebarRef = useRef<HTMLLIElement>(null);
const activeRequest = useActiveRequest();
const [activeEnvironment] = useActiveEnvironment();
const [activeCookieJar] = useActiveCookieJar();
const folders = useFolders();
const requests = useRequests();
const activeWorkspace = useActiveWorkspace();
@@ -221,14 +223,22 @@ export function Sidebar({ className }: Props) {
routes.navigate('request', {
requestId: id,
workspaceId: item.workspaceId,
environmentId: activeEnvironment?.id,
environmentId: activeEnvironment?.id ?? null,
cookieJarId: activeCookieJar?.id ?? null,
});
setSelectedId(id);
setSelectedTree(tree);
if (!opts.noFocus) focusActiveRequest({ forced: { id, tree } });
}
},
[treeParentMap, collapsed, routes, activeEnvironment, focusActiveRequest],
[
treeParentMap,
collapsed,
routes,
activeEnvironment?.id,
activeCookieJar?.id,
focusActiveRequest,
],
);
const handleClearSelected = useCallback(() => {
@@ -273,8 +283,9 @@ export function Sidebar({ className }: Props) {
e.preventDefault();
routes.navigate('request', {
requestId: selected.id,
workspaceId: activeWorkspace?.id,
environmentId: activeEnvironment?.id,
workspaceId: activeWorkspace?.id ?? null,
environmentId: activeEnvironment?.id ?? null,
cookieJarId: activeCookieJar?.id ?? null,
});
});
@@ -704,9 +715,15 @@ function SidebarItem({
const handleSubmitNameEdit = useCallback(
async (el: HTMLInputElement) => {
if (itemModel === 'http_request') {
await updateHttpRequest.mutateAsync({ id: itemId, update: (r) => ({ ...r, name: el.value }) });
await updateHttpRequest.mutateAsync({
id: itemId,
update: (r) => ({ ...r, name: el.value }),
});
} else if (itemModel === 'grpc_request') {
await updateGrpcRequest.mutateAsync({ id: itemId, update: (r) => ({ ...r, name: el.value }) });
await updateGrpcRequest.mutateAsync({
id: itemId,
update: (r) => ({ ...r, name: el.value }),
});
}
setEditing(false);
},
@@ -724,7 +741,7 @@ function SidebarItem({
switch (e.key) {
case 'Enter':
e.preventDefault();
handleSubmitNameEdit(e.currentTarget);
await handleSubmitNameEdit(e.currentTarget);
break;
case 'Escape':
e.preventDefault();
@@ -741,8 +758,8 @@ function SidebarItem({
}, [setEditing, itemModel]);
const handleBlur = useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
handleSubmitNameEdit(e.currentTarget);
async (e: React.FocusEvent<HTMLInputElement>) => {
await handleSubmitNameEdit(e.currentTarget);
},
[handleSubmitNameEdit],
);
@@ -903,8 +920,9 @@ function SidebarItem({
className={classNames(
'w-full flex gap-1.5 items-center h-xs px-1.5 rounded-md focus-visible:ring focus-visible:ring-border-focus outline-0',
editing && 'ring-1 focus-within:ring-focus',
isActive && 'bg-surface-highlight text',
!isActive && 'text-text-subtle group-hover/item:text-text active:bg-surface-highlight',
isActive && 'bg-surface-highlight text-text',
!isActive && 'text-text-subtle group-hover/item:text-text',
showContextMenu && '!text-text', // Show as "active" when context menu is open
selected && useProminentStyles && '!bg-surface-active',
)}
>

View File

@@ -22,6 +22,8 @@ const icons = {
cake: lucide.CakeIcon,
chat: lucide.MessageSquare,
check: lucide.CheckIcon,
check_square_checked: lucide.SquareCheckIcon,
check_square_unchecked: lucide.SquareIcon,
check_circle: lucide.CheckCircleIcon,
chevron_down: lucide.ChevronDownIcon,
chevron_right: lucide.ChevronRightIcon,
@@ -56,6 +58,7 @@ const icons = {
left_panel_visible: lucide.PanelLeftCloseIcon,
magic_wand: lucide.Wand2Icon,
minus: lucide.MinusIcon,
minus_circle: lucide.MinusCircleIcon,
moon: lucide.MoonIcon,
more_vertical: lucide.MoreVerticalIcon,
paste: lucide.ClipboardPasteIcon,

View File

@@ -18,6 +18,8 @@ export function useActiveCookieJar() {
export function useEnsureActiveCookieJar() {
const cookieJars = useCookieJars();
const [activeCookieJarId, setActiveCookieJarId] = useActiveCookieJarId();
// Set the active cookie jar to the first one, if none set
useEffect(() => {
if (cookieJars == null) return; // Hasn't loaded yet
if (cookieJars.find((j) => j.id === activeCookieJarId)) {

View File

@@ -1,7 +1,6 @@
import { useParams } from 'react-router-dom';
import type { RouteParamsRequest } from './useAppRoutes';
export function useActiveRequestId(): string | null {
const { requestId } = useParams<RouteParamsRequest>();
const { requestId } = useParams();
return requestId ?? null;
}

View File

@@ -1,7 +1,6 @@
import type { Workspace } from '@yaakapp-internal/models';
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
import type { RouteParamsWorkspace } from './useAppRoutes';
import { useWorkspaces } from './useWorkspaces';
export function useActiveWorkspace(): Workspace | null {
@@ -15,6 +14,6 @@ export function useActiveWorkspace(): Workspace | null {
}
function useActiveWorkspaceId(): string | null {
const { workspaceId } = useParams<RouteParamsWorkspace>();
const { workspaceId } = useParams();
return workspaceId ?? null;
}

View File

@@ -1,22 +1,19 @@
import type { Environment } from '@yaakapp-internal/models';
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { QUERY_COOKIE_JAR_ID } from './useActiveCookieJar';
import { QUERY_ENVIRONMENT_ID } from './useActiveEnvironment';
import { useActiveRequestId } from './useActiveRequestId';
import { useActiveWorkspace } from './useActiveWorkspace';
export type RouteParamsWorkspace = {
workspaceId: string;
environmentId?: string;
cookieJarId?: string;
environmentId: string | null;
cookieJarId: string | null;
};
export type RouteParamsRequest = RouteParamsWorkspace & {
requestId: string;
};
export const routePaths = {
export const paths = {
workspaces() {
return '/workspaces';
},
@@ -52,44 +49,21 @@ export const routePaths = {
};
export function useAppRoutes() {
const activeWorkspace = useActiveWorkspace();
const requestId = useActiveRequestId();
const nav = useNavigate();
const navigate = useCallback(
<T extends keyof typeof routePaths>(path: T, ...params: Parameters<(typeof routePaths)[T]>) => {
<T extends keyof typeof paths>(path: T, ...params: Parameters<(typeof paths)[T]>) => {
// Not sure how to make TS work here, but it's good from the
// outside caller perspective.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const resolvedPath = routePaths[path](...(params as any));
const resolvedPath = paths[path](...(params as any));
nav(resolvedPath);
},
[nav],
);
const setEnvironment = useCallback(
(environment: Environment | null) => {
if (activeWorkspace == null) {
navigate('workspaces');
} else if (requestId == null) {
navigate('workspace', {
workspaceId: activeWorkspace.id,
environmentId: environment == null ? undefined : environment.id,
});
} else {
navigate('request', {
workspaceId: activeWorkspace.id,
environmentId: environment == null ? undefined : environment.id,
requestId,
});
}
},
[navigate, activeWorkspace, requestId],
);
return {
paths: routePaths,
paths,
navigate,
setEnvironment,
};
}

View File

@@ -1,15 +1,19 @@
import { useMutation } from '@tanstack/react-query';
import type { CookieJar } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai";
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
import {cookieJarsAtom} from "./useCookieJars";
import { usePrompt } from './usePrompt';
import {updateModelList} from "./useSyncModelStores";
export function useCreateCookieJar() {
const workspace = useActiveWorkspace();
const prompt = usePrompt();
const setCookieJars = useSetAtom(cookieJarsAtom);
return useMutation<CookieJar>({
return useMutation<CookieJar | null>({
mutationKey: ['create_cookie_jar'],
mutationFn: async () => {
if (workspace === null) {
@@ -23,8 +27,16 @@ export function useCreateCookieJar() {
label: 'Name',
defaultValue: 'My Jar',
});
if (name == null) return null;
return invokeCmd('cmd_create_cookie_jar', { workspaceId: workspace.id, name });
},
onSuccess: (cookieJar) => {
if (cookieJar == null) return;
// Optimistic update
setCookieJars(updateModelList(cookieJar));
},
onSettled: () => trackEvent('cookie_jar', 'create'),
});
}

View File

@@ -1,17 +1,21 @@
import { useMutation } from '@tanstack/react-query';
import type { Environment } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai";
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveWorkspace } from './useActiveWorkspace';
import {environmentsAtom} from "./useEnvironments";
import { usePrompt } from './usePrompt';
import {updateModelList} from "./useSyncModelStores";
export function useCreateEnvironment() {
const [, setActiveEnvironmentId] = useActiveEnvironment();
const prompt = usePrompt();
const workspace = useActiveWorkspace();
const setEnvironments = useSetAtom(environmentsAtom);
return useMutation<Environment, unknown, void>({
return useMutation<Environment | null, unknown, void>({
mutationKey: ['create_environment'],
mutationFn: async () => {
const name = await prompt({
@@ -23,6 +27,8 @@ export function useCreateEnvironment() {
defaultValue: 'My Environment',
confirmText: 'Create',
});
if (name == null) return null;
return invokeCmd('cmd_create_environment', {
name,
variables: [],
@@ -31,7 +37,11 @@ export function useCreateEnvironment() {
},
onSettled: () => trackEvent('environment', 'create'),
onSuccess: async (environment) => {
if (workspace == null) return;
if (environment == null) return;
// Optimistic update
setEnvironments(updateModelList(environment));
setActiveEnvironmentId(environment.id);
},
});

View File

@@ -1,15 +1,23 @@
import { useMutation } from '@tanstack/react-query';
import type { Folder } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
import { foldersAtom } from './useFolders';
import { usePrompt } from './usePrompt';
import { updateModelList } from './useSyncModelStores';
export function useCreateFolder() {
const workspace = useActiveWorkspace();
const prompt = usePrompt();
const setFolders = useSetAtom(foldersAtom);
return useMutation<void, unknown, Partial<Pick<Folder, 'name' | 'sortPriority' | 'folderId'>>>({
return useMutation<
Folder | null,
unknown,
Partial<Pick<Folder, 'name' | 'sortPriority' | 'folderId'>>
>({
mutationKey: ['create_folder'],
mutationFn: async (patch) => {
if (workspace === null) {
@@ -25,14 +33,19 @@ export function useCreateFolder() {
confirmText: 'Create',
placeholder: 'Name',
});
if (name == null) {
return;
}
if (name == null) return null;
patch.name = name;
}
patch.sortPriority = patch.sortPriority || -Date.now();
await invokeCmd('cmd_create_folder', { workspaceId: workspace.id, ...patch });
return await invokeCmd('cmd_create_folder', { workspaceId: workspace.id, ...patch });
},
onSuccess: (folder) => {
if (folder == null) return;
// Optimistic update
setFolders(updateModelList(folder));
},
onSettled: () => trackEvent('folder', 'create'),
});

View File

@@ -1,17 +1,23 @@
import { useMutation } from '@tanstack/react-query';
import type { GrpcRequest } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai";
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import {useActiveCookieJar} from "./useActiveCookieJar";
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
import {grpcRequestsAtom} from "./useGrpcRequests";
import {updateModelList} from "./useSyncModelStores";
export function useCreateGrpcRequest() {
const workspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const [activeCookieJar] = useActiveCookieJar();
const activeRequest = useActiveRequest();
const routes = useAppRoutes();
const setGrpcRequests = useSetAtom(grpcRequestsAtom);
return useMutation<
GrpcRequest,
@@ -33,23 +39,22 @@ export function useCreateGrpcRequest() {
}
}
patch.folderId = patch.folderId || activeRequest?.folderId;
const request = await invokeCmd<GrpcRequest>('cmd_create_grpc_request', {
return invokeCmd<GrpcRequest>('cmd_create_grpc_request', {
workspaceId: workspace.id,
name: '',
...patch,
});
// Give some time for the workspace to sync to the local store
await new Promise((resolve) => setTimeout(resolve, 100));
return request;
},
onSettled: () => trackEvent('grpc_request', 'create'),
onSuccess: async (request) => {
// Optimistic update
setGrpcRequests(updateModelList(request));
routes.navigate('request', {
workspaceId: request.workspaceId,
requestId: request.id,
environmentId: activeEnvironment?.id,
environmentId: activeEnvironment?.id ?? null,
cookieJarId: activeCookieJar?.id ?? null,
});
},
});

View File

@@ -1,17 +1,23 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpRequest } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai/index';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveCookieJar } from './useActiveCookieJar';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
import { httpRequestsAtom } from './useHttpRequests';
import { updateModelList } from './useSyncModelStores';
export function useCreateHttpRequest() {
const workspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const [activeCookieJar] = useActiveCookieJar();
const activeRequest = useActiveRequest();
const routes = useAppRoutes();
const setHttpRequests = useSetAtom(httpRequestsAtom);
return useMutation<HttpRequest, unknown, Partial<HttpRequest>>({
mutationKey: ['create_http_request'],
@@ -21,7 +27,7 @@ export function useCreateHttpRequest() {
}
if (patch.sortPriority === undefined) {
if (activeRequest != null) {
// Place above currently-active request
// Place above currently active request
patch.sortPriority = activeRequest.sortPriority - 0.0001;
} else {
// Place at the very top
@@ -29,21 +35,20 @@ export function useCreateHttpRequest() {
}
}
patch.folderId = patch.folderId || activeRequest?.folderId;
const request = await invokeCmd<HttpRequest>('cmd_create_http_request', {
return invokeCmd<HttpRequest>('cmd_create_http_request', {
request: { workspaceId: workspace.id, ...patch },
});
// Give some time for the workspace to sync to the local store
await new Promise((resolve) => setTimeout(resolve, 100));
return request;
},
onSettled: () => trackEvent('http_request', 'create'),
onSuccess: async (request) => {
// Optimistic update
setHttpRequests(updateModelList(request));
routes.navigate('request', {
workspaceId: request.workspaceId,
requestId: request.id,
environmentId: activeEnvironment?.id,
environmentId: activeEnvironment?.id ?? null,
cookieJarId: activeCookieJar?.id ?? null,
});
},
});

View File

@@ -1,13 +1,18 @@
import { useMutation } from '@tanstack/react-query';
import type { Workspace } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai/index';
import { invokeCmd } from '../lib/tauri';
import { useAppRoutes } from './useAppRoutes';
import { usePrompt } from './usePrompt';
import { updateModelList } from './useSyncModelStores';
import { workspacesAtom } from './useWorkspaces';
export function useCreateWorkspace() {
const routes = useAppRoutes();
const prompt = usePrompt();
return useMutation<Workspace, void, void>({
const setWorkspaces = useSetAtom(workspacesAtom);
return useMutation<Workspace | null, void, void>({
mutationKey: ['create_workspace'],
mutationFn: async () => {
const name = await prompt({
@@ -18,15 +23,22 @@ export function useCreateWorkspace() {
placeholder: 'My Workspace',
confirmText: 'Create',
});
const workspace = await invokeCmd<Workspace>('cmd_create_workspace', { name });
// Give some time for the workspace to sync to the local store
await new Promise((resolve) => setTimeout(resolve, 100));
return workspace;
if (name == null) {
return null;
}
return invokeCmd<Workspace>('cmd_create_workspace', { name });
},
onSuccess: async (workspace) => {
routes.navigate('workspace', { workspaceId: workspace.id });
if (workspace == null) return;
// Optimistic update
setWorkspaces(updateModelList(workspace));
routes.navigate('workspace', {
workspaceId: workspace.id,
environmentId: null,
cookieJarId: null,
});
},
});
}

View File

@@ -1,14 +1,18 @@
import { useMutation } from '@tanstack/react-query';
import type { GrpcRequest } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai";
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { getGrpcRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import {grpcRequestsAtom} from "./useGrpcRequests";
import {removeModelById} from "./useSyncModelStores";
export function useDeleteAnyGrpcRequest() {
const confirm = useConfirm();
const setGrpcRequests = useSetAtom(grpcRequestsAtom);
return useMutation<GrpcRequest | null, string, string>({
mutationKey: ['delete_any_grpc_request'],
@@ -29,6 +33,12 @@ export function useDeleteAnyGrpcRequest() {
if (!confirmed) return null;
return invokeCmd('cmd_delete_grpc_request', { requestId: id });
},
onSuccess: (request) => {
if (request == null) return;
// Optimistic update
setGrpcRequests(removeModelById(request));
},
onSettled: () => trackEvent('grpc_request', 'delete'),
});
}

View File

@@ -1,14 +1,18 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpRequest } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import { httpRequestsAtom } from './useHttpRequests';
import { removeModelById } from './useSyncModelStores';
export function useDeleteAnyHttpRequest() {
const confirm = useConfirm();
const setHttpRequests = useSetAtom(httpRequestsAtom);
return useMutation<HttpRequest | null, string, string>({
mutationKey: ['delete_any_http_request'],
@@ -27,7 +31,13 @@ export function useDeleteAnyHttpRequest() {
),
});
if (!confirmed) return null;
return invokeCmd('cmd_delete_http_request', { requestId: id });
return invokeCmd<HttpRequest>('cmd_delete_http_request', { requestId: id });
},
onSuccess: (request) => {
if (request == null) return;
// Optimistic update
setHttpRequests(removeModelById(request));
},
onSettled: () => trackEvent('http_request', 'delete'),
});

View File

@@ -1,12 +1,16 @@
import { useMutation } from '@tanstack/react-query';
import type { CookieJar } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai";
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import {cookieJarsAtom} from "./useCookieJars";
import {removeModelById} from "./useSyncModelStores";
export function useDeleteCookieJar(cookieJar: CookieJar | null) {
const confirm = useConfirm();
const setCookieJars = useSetAtom(cookieJarsAtom);
return useMutation<CookieJar | null, string>({
mutationKey: ['delete_cookie_jar', cookieJar?.id],
@@ -25,5 +29,10 @@ export function useDeleteCookieJar(cookieJar: CookieJar | null) {
return invokeCmd('cmd_delete_cookie_jar', { cookieJarId: cookieJar?.id });
},
onSettled: () => trackEvent('cookie_jar', 'delete'),
onSuccess: (cookieJar) => {
if (cookieJar == null) return;
setCookieJars(removeModelById(cookieJar));
}
});
}

View File

@@ -1,12 +1,16 @@
import { useMutation } from '@tanstack/react-query';
import type { Environment } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai";
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import {environmentsAtom} from "./useEnvironments";
import {removeModelById} from "./useSyncModelStores";
export function useDeleteEnvironment(environment: Environment | null) {
const confirm = useConfirm();
const setEnvironments = useSetAtom(environmentsAtom);
return useMutation<Environment | null, string>({
mutationKey: ['delete_environment', environment?.id],
@@ -25,5 +29,10 @@ export function useDeleteEnvironment(environment: Environment | null) {
return invokeCmd('cmd_delete_environment', { environmentId: environment?.id });
},
onSettled: () => trackEvent('environment', 'delete'),
onSuccess: (environment) => {
if (environment == null) return;
setEnvironments(removeModelById(environment));
}
});
}

View File

@@ -1,13 +1,17 @@
import { useMutation } from '@tanstack/react-query';
import type { Folder } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai';
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { getFolder } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { useConfirm } from './useConfirm';
import { foldersAtom } from './useFolders';
import { removeModelById } from './useSyncModelStores';
export function useDeleteFolder(id: string | null) {
const confirm = useConfirm();
const setFolders = useSetAtom(foldersAtom);
return useMutation<Folder | null, string>({
mutationKey: ['delete_folder', id],
@@ -27,5 +31,10 @@ export function useDeleteFolder(id: string | null) {
return invokeCmd('cmd_delete_folder', { folderId: id });
},
onSettled: () => trackEvent('folder', 'delete'),
onSuccess: (folder) => {
if (folder == null) return;
setFolders(removeModelById(folder));
},
});
}

View File

@@ -1,14 +1,23 @@
import { useMutation } from '@tanstack/react-query';
import type { GrpcConnection } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai";
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import {grpcConnectionsAtom} from "./useGrpcConnections";
import {removeModelById} from "./useSyncModelStores";
export function useDeleteGrpcConnection(id: string | null) {
const setGrpcConnections = useSetAtom(grpcConnectionsAtom);
return useMutation<GrpcConnection>({
mutationKey: ['delete_grpc_connection', id],
mutationFn: async () => {
return await invokeCmd('cmd_delete_grpc_connection', { id: id });
},
onSettled: () => trackEvent('grpc_connection', 'delete'),
onSuccess: (connection) => {
if (connection == null) return;
setGrpcConnections(removeModelById(connection));
}
});
}

View File

@@ -1,8 +1,11 @@
import { useMutation } from '@tanstack/react-query';
import { useSetAtom } from 'jotai';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { grpcConnectionsAtom } from './useGrpcConnections';
export function useDeleteGrpcConnections(requestId?: string) {
const setGrpcConnections = useSetAtom(grpcConnectionsAtom);
return useMutation({
mutationKey: ['delete_grpc_connections', requestId],
mutationFn: async () => {
@@ -10,5 +13,8 @@ export function useDeleteGrpcConnections(requestId?: string) {
await invokeCmd('cmd_delete_all_grpc_connections', { requestId });
},
onSettled: () => trackEvent('grpc_connection', 'delete_many'),
onSuccess: () => {
setGrpcConnections((all) => all.filter((r) => r.requestId !== requestId));
},
});
}

View File

@@ -1,14 +1,21 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpResponse } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai";
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import {httpResponsesAtom} from "./useHttpResponses";
import {removeModelById} from "./useSyncModelStores";
export function useDeleteHttpResponse(id: string | null) {
const setHttpResponses = useSetAtom(httpResponsesAtom);
return useMutation<HttpResponse>({
mutationKey: ['delete_http_response', id],
mutationFn: async () => {
return await invokeCmd('cmd_delete_http_response', { id: id });
},
onSettled: () => trackEvent('http_response', 'delete'),
onSuccess: (response) => {
setHttpResponses(removeModelById(response));
}
});
}

View File

@@ -1,14 +1,20 @@
import { useMutation } from '@tanstack/react-query';
import { useSetAtom } from 'jotai';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { httpResponsesAtom } from './useHttpResponses';
export function useDeleteHttpResponses(requestId?: string) {
const setHttpResponses = useSetAtom(httpResponsesAtom);
return useMutation({
mutationKey: ['delete_http_responses', requestId],
mutationFn: async () => {
if (requestId === undefined) return;
await invokeCmd('cmd_delete_all_http_responses', { requestId });
},
onSuccess: () => {
setHttpResponses((all) => all.filter((r) => r.requestId !== requestId));
},
onSettled: () => trackEvent('http_response', 'delete_many'),
});
}

View File

@@ -1,15 +1,17 @@
import { useMutation } from '@tanstack/react-query';
import { useSetAtom } from 'jotai/index';
import { count } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAlert } from './useAlert';
import { useConfirm } from './useConfirm';
import { useGrpcConnections } from './useGrpcConnections';
import { useHttpResponses } from './useHttpResponses';
import { httpResponsesAtom, useHttpResponses } from './useHttpResponses';
export function useDeleteSendHistory() {
const confirm = useConfirm();
const alert = useAlert();
const setHttpResponses = useSetAtom(httpResponsesAtom);
const activeWorkspace = useActiveWorkspace();
const httpResponses = useHttpResponses();
const grpcConnections = useGrpcConnections();
@@ -36,8 +38,15 @@ export function useDeleteSendHistory() {
variant: 'delete',
description: <>Delete {labels.join(' and ')}?</>,
});
if (!confirmed) return;
if (!confirmed) return false;
await invokeCmd('cmd_delete_send_history', { workspaceId: activeWorkspace?.id ?? 'n/a' });
return true;
},
onSuccess: async (confirmed) => {
if (!confirmed) return;
setHttpResponses((all) => all.filter((r) => r.workspaceId !== activeWorkspace?.id));
},
});
}

View File

@@ -1,16 +1,20 @@
import { useMutation } from '@tanstack/react-query';
import type { Workspace } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai";
import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
import { useConfirm } from './useConfirm';
import {removeModelById} from "./useSyncModelStores";
import {workspacesAtom} from "./useWorkspaces";
export function useDeleteWorkspace(workspace: Workspace | null) {
const activeWorkspace = useActiveWorkspace();
const routes = useAppRoutes();
const confirm = useConfirm();
const setWorkspaces = useSetAtom(workspacesAtom);
return useMutation<Workspace | null, string>({
mutationKey: ['delete_workspace', workspace?.id],
@@ -32,6 +36,9 @@ export function useDeleteWorkspace(workspace: Workspace | null) {
onSuccess: async (workspace) => {
if (workspace === null) return;
// Optimistic update
setWorkspaces(removeModelById(workspace));
const { id: workspaceId } = workspace;
if (workspaceId === activeWorkspace?.id) {
routes.navigate('workspaces');

View File

@@ -2,6 +2,7 @@ import { useMutation } from '@tanstack/react-query';
import type { GrpcRequest } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import {useActiveCookieJar} from "./useActiveCookieJar";
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
@@ -16,6 +17,7 @@ export function useDuplicateGrpcRequest({
}) {
const activeWorkspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const [activeCookieJar] = useActiveCookieJar();
const routes = useAppRoutes();
return useMutation<GrpcRequest, string>({
@@ -36,7 +38,8 @@ export function useDuplicateGrpcRequest({
routes.navigate('request', {
workspaceId: activeWorkspace.id,
requestId: request.id,
environmentId: activeEnvironment?.id,
environmentId: activeEnvironment?.id ?? null,
cookieJarId: activeCookieJar?.id ?? null,
});
}
},

View File

@@ -2,6 +2,7 @@ import { useMutation } from '@tanstack/react-query';
import type { HttpRequest } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri';
import {useActiveCookieJar} from "./useActiveCookieJar";
import { useActiveEnvironment } from './useActiveEnvironment';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
@@ -15,6 +16,7 @@ export function useDuplicateHttpRequest({
}) {
const activeWorkspace = useActiveWorkspace();
const [activeEnvironment] = useActiveEnvironment();
const [activeCookieJar] = useActiveCookieJar();
const routes = useAppRoutes();
return useMutation<HttpRequest, string>({
mutationKey: ['duplicate_http_request', id],
@@ -28,7 +30,8 @@ export function useDuplicateHttpRequest({
routes.navigate('request', {
workspaceId: activeWorkspace.id,
requestId: request.id,
environmentId: activeEnvironment?.id,
environmentId: activeEnvironment?.id ?? null,
cookieJarId: activeCookieJar?.id ?? null,
});
}
},

View File

@@ -1,4 +1,5 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpRequest } from '@yaakapp-internal/models';
import { useToast } from '../components/ToastContext';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
@@ -22,11 +23,10 @@ export function useImportCurl() {
overwriteRequestId?: string;
command: string;
}) => {
const request: Record<string, unknown> = await invokeCmd('cmd_curl_to_request', {
const request: HttpRequest = await invokeCmd('cmd_curl_to_request', {
command,
workspaceId: workspace?.id,
});
delete request.id;
let verb;
if (overwriteRequestId == null) {
@@ -34,7 +34,19 @@ export function useImportCurl() {
await createRequest.mutateAsync(request);
} else {
verb = 'Updated';
await updateRequest.mutateAsync({ id: overwriteRequestId, update: request });
await updateRequest.mutateAsync({
id: overwriteRequestId,
update: (r: HttpRequest) => ({
...request,
id: r.id,
createdAt: r.createdAt,
workspaceId: r.workspaceId,
folderId: r.folderId,
name: r.name,
sortPriority: r.sortPriority,
}),
});
setTimeout(() => wasUpdatedExternally(overwriteRequestId), 100);
}

View File

@@ -66,7 +66,8 @@ export function useImportData() {
if (importedWorkspace != null) {
routes.navigate('workspace', {
workspaceId: importedWorkspace.id,
environmentId: imported.environments[0]?.id,
environmentId: imported.environments[0]?.id ?? null,
cookieJarId: null,
});
}

View File

@@ -14,12 +14,14 @@ const introspectionRequestBody = JSON.stringify({
operationName: 'IntrospectionQuery',
});
export function useIntrospectGraphQL(baseRequest: HttpRequest) {
export function useIntrospectGraphQL(
baseRequest: HttpRequest,
options: { disabled?: boolean } = {},
) {
// Debounce the request because it can change rapidly and we don't
// want to send so too many requests.
const request = useDebouncedValue(baseRequest);
const [activeEnvironment] = useActiveEnvironment();
const [refetchKey, setRefetchKey] = useState<number>(0);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string>();
@@ -29,10 +31,11 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
namespace: 'global',
});
useEffect(() => {
const fetchIntrospection = async () => {
const refetch = useCallback(async () => {
try {
setIsLoading(true);
setError(undefined);
const args = {
...baseRequest,
bodyType: 'application/json',
@@ -44,42 +47,54 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
);
if (response.error) {
throw new Error(response.error);
return setError(response.error);
}
const bodyText = await getResponseBodyText(response);
if (response.status < 200 || response.status >= 300) {
throw new Error(`Request failed with status ${response.status}.\n\n${bodyText}`);
return setError(`Request failed with status ${response.status}.\n\n${bodyText}`);
}
if (bodyText === null) {
throw new Error('Empty body returned in response');
return setError('Empty body returned in response');
}
const { data } = JSON.parse(bodyText);
console.log(`Got introspection response for ${baseRequest.url}`, data);
await setIntrospection(data);
};
} catch (err) {
setError(String(err));
} finally {
setIsLoading(false);
}
}, [activeEnvironment?.id, baseRequest, setIntrospection]);
fetchIntrospection()
.catch((e) => setError(e.message))
.finally(() => setIsLoading(false));
useEffect(() => {
// Skip introspection if automatic is disabled and we already have one
if (options.disabled) {
return;
}
refetch().catch(console.error);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [request.id, request.url, request.method, refetchKey, activeEnvironment?.id]);
}, [request.id, request.url, request.method, activeEnvironment?.id]);
const refetch = useCallback(() => {
setRefetchKey((k) => k + 1);
}, []);
const clear = useCallback(async () => {
await setIntrospection(null);
}, [setIntrospection]);
const schema = useMemo(() => {
if (introspection == null) {
return introspection;
}
try {
return introspection ? buildClientSchema(introspection) : undefined;
return buildClientSchema(introspection);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
setError('message' in e ? e.message : String(e));
}
}, [introspection]);
return { schema, isLoading, error, refetch };
return { schema, isLoading, error, refetch, clear };
}

View File

@@ -12,7 +12,11 @@ export function useOpenSettings() {
if (workspace == null) return;
await invokeCmd('cmd_new_child_window', {
url: routes.paths.workspaceSettings({ workspaceId: workspace.id }),
url: routes.paths.workspaceSettings({
workspaceId: workspace.id,
cookieJarId: null,
environmentId: null,
}),
label: 'settings',
title: 'Yaak Settings',
innerSize: [600, 550],

View File

@@ -17,9 +17,9 @@ export function useOpenWorkspace() {
workspaceId: string;
inNewWindow: boolean;
}) => {
const environmentId = (await getRecentEnvironments(workspaceId))[0];
const requestId = (await getRecentRequests(workspaceId))[0];
const cookieJarId = (await getRecentCookieJars(workspaceId))[0];
const environmentId = (await getRecentEnvironments(workspaceId))[0] ?? null;
const requestId = (await getRecentRequests(workspaceId))[0] ?? null;
const cookieJarId = (await getRecentCookieJars(workspaceId))[0] ?? null;
const baseArgs = { workspaceId, environmentId, cookieJarId } as const;
if (inNewWindow) {
const path =

View File

@@ -4,7 +4,7 @@ import type { AnyModel } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai/index';
import { extractKeyValue } from '../lib/keyValueStore';
import { modelsEq } from '../lib/model_util';
import {useActiveWorkspace} from "./useActiveWorkspace";
import { useActiveWorkspace } from './useActiveWorkspace';
import { cookieJarsAtom } from './useCookieJars';
import { environmentsAtom } from './useEnvironments';
import { foldersAtom } from './useFolders';
@@ -60,31 +60,26 @@ export function useSyncModelStores() {
return;
}
// Mark these models as DESC instead of ASC
const pushToFront = (['http_response', 'grpc_connection'] as AnyModel['model'][]).includes(
model.model,
);
if (shouldIgnoreModel(model, windowLabel)) return;
if (model.model === 'workspace') {
setWorkspaces(updateModelList(model, pushToFront));
setWorkspaces(updateModelList(model));
} else if (model.model === 'plugin') {
setPlugins(updateModelList(model, pushToFront));
setPlugins(updateModelList(model));
} else if (model.model === 'http_request') {
setHttpRequests(updateModelList(model, pushToFront));
setHttpRequests(updateModelList(model));
} else if (model.model === 'folder') {
setFolders(updateModelList(model, pushToFront));
setFolders(updateModelList(model));
} else if (model.model === 'http_response') {
setHttpResponses(updateModelList(model, pushToFront));
setHttpResponses(updateModelList(model));
} else if (model.model === 'grpc_request') {
setGrpcRequests(updateModelList(model, pushToFront));
setGrpcRequests(updateModelList(model));
} else if (model.model === 'grpc_connection') {
setGrpcConnections(updateModelList(model, pushToFront));
setGrpcConnections(updateModelList(model));
} else if (model.model === 'environment') {
setEnvironments(updateModelList(model, pushToFront));
setEnvironments(updateModelList(model));
} else if (model.model === 'cookie_jar') {
setCookieJars(updateModelList(model, pushToFront));
setCookieJars(updateModelList(model));
} else if (model.model === 'settings') {
setSettings(model);
} else if (queryKey != null) {
@@ -96,7 +91,7 @@ export function useSyncModelStores() {
}
if (Array.isArray(current)) {
return updateModelList(model, pushToFront)(current);
return updateModelList(model)(current);
}
});
}
@@ -106,35 +101,38 @@ export function useSyncModelStores() {
const { model, windowLabel } = payload;
if (shouldIgnoreModel(model, windowLabel)) return;
console.log('Delete model', payload.model);
console.log('Delete model', payload);
if (model.model === 'workspace') {
setWorkspaces(removeById(model));
setWorkspaces(removeModelById(model));
} else if (model.model === 'plugin') {
setPlugins(removeById(model));
setPlugins(removeModelById(model));
} else if (model.model === 'http_request') {
setHttpRequests(removeById(model));
setHttpRequests(removeModelById(model));
} else if (model.model === 'http_response') {
setHttpResponses(removeById(model));
setHttpResponses(removeModelById(model));
} else if (model.model === 'folder') {
setFolders(removeById(model));
setFolders(removeModelById(model));
} else if (model.model === 'environment') {
setEnvironments(removeById(model));
setEnvironments(removeModelById(model));
} else if (model.model === 'grpc_request') {
setGrpcRequests(removeById(model));
setGrpcRequests(removeModelById(model));
} else if (model.model === 'grpc_connection') {
setGrpcConnections(removeById(model));
setGrpcConnections(removeModelById(model));
} else if (model.model === 'grpc_event') {
queryClient.setQueryData(grpcEventsQueryKey(model), removeById(model));
queryClient.setQueryData(grpcEventsQueryKey(model), removeModelById(model));
} else if (model.model === 'key_value') {
queryClient.setQueryData(keyValueQueryKey(model), undefined);
} else if (model.model === 'cookie_jar') {
setCookieJars(removeById(model));
setCookieJars(removeModelById(model));
}
});
}
function updateModelList<T extends AnyModel>(model: T, pushToFront: boolean) {
export function updateModelList<T extends AnyModel>(model: T) {
// Mark these models as DESC instead of ASC
const pushToFront = model.model === 'http_response' || model.model === 'grpc_connection';
return (current: T[] | undefined): T[] => {
const index = current?.findIndex((v) => modelsEq(v, model)) ?? -1;
if (index >= 0) {
@@ -145,7 +143,7 @@ function updateModelList<T extends AnyModel>(model: T, pushToFront: boolean) {
};
}
function removeById<T extends { id: string }>(model: T) {
export function removeModelById<T extends { id: string }>(model: T) {
return (entries: T[] | undefined) => entries?.filter((e) => e.id !== model.id) ?? [];
}

View File

@@ -1,18 +1,25 @@
import { useQuery } from '@tanstack/react-query';
import type { GetTemplateFunctionsResponse } from '@yaakapp-internal/plugin';
import { useState } from 'react';
import { invokeCmd } from '../lib/tauri';
import { usePluginsKey } from './usePlugins';
export function useTemplateFunctions() {
const pluginsKey = usePluginsKey();
const [numFns, setNumFns] = useState<number>(0);
const result = useQuery({
queryKey: ['template_functions', pluginsKey],
// Fetch periodically until functions are returned
// NOTE: visibilitychange (refetchOnWindowFocus) does not work on Windows, so we'll rely on this logic
// to refetch things until that's working again
// TODO: Update plugin system to wait for plugins to initialize before sending the first event to them
refetchInterval: numFns > 0 ? Infinity : 500,
refetchOnMount: true,
queryFn: async () => {
const responses = (await invokeCmd(
'cmd_template_functions',
)) as GetTemplateFunctionsResponse[];
return responses;
const result = await invokeCmd<GetTemplateFunctionsResponse[]>('cmd_template_functions');
setNumFns(result.length);
return result;
},
});

View File

@@ -1,10 +1,14 @@
import { useMutation } from '@tanstack/react-query';
import type { Folder } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai/index";
import { getFolder } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import {foldersAtom} from "./useFolders";
import {updateModelList} from "./useSyncModelStores";
export function useUpdateAnyFolder() {
return useMutation<void, unknown, { id: string; update: (r: Folder) => Folder }>({
const setFolders = useSetAtom(foldersAtom);
return useMutation<Folder, unknown, { id: string; update: (r: Folder) => Folder }>({
mutationKey: ['update_any_folder'],
mutationFn: async ({ id, update }) => {
const folder = await getFolder(id);
@@ -12,7 +16,10 @@ export function useUpdateAnyFolder() {
throw new Error("Can't update a null folder");
}
await invokeCmd('cmd_update_folder', { folder: update(folder) });
return invokeCmd<Folder>('cmd_update_folder', { folder: update(folder) });
},
onSuccess: async (folder) => {
setFolders(updateModelList(folder));
}
});
}

View File

@@ -1,11 +1,15 @@
import { useMutation } from '@tanstack/react-query';
import type { GrpcRequest } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai/index';
import { getGrpcRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { grpcRequestsAtom } from './useGrpcRequests';
import { updateModelList } from './useSyncModelStores';
export function useUpdateAnyGrpcRequest() {
const setGrpcRequests = useSetAtom(grpcRequestsAtom);
return useMutation<
void,
GrpcRequest,
unknown,
{ id: string; update: Partial<GrpcRequest> | ((r: GrpcRequest) => GrpcRequest) }
>({
@@ -18,7 +22,10 @@ export function useUpdateAnyGrpcRequest() {
const patchedRequest =
typeof update === 'function' ? update(request) : { ...request, ...update };
await invokeCmd('cmd_update_grpc_request', { request: patchedRequest });
return invokeCmd<GrpcRequest>('cmd_update_grpc_request', { request: patchedRequest });
},
onSuccess: (request) => {
setGrpcRequests(updateModelList(request));
},
});
}

View File

@@ -1,11 +1,15 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpRequest } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai/index";
import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import {httpRequestsAtom} from "./useHttpRequests";
import {updateModelList} from "./useSyncModelStores";
export function useUpdateAnyHttpRequest() {
const setHttpRequests = useSetAtom(httpRequestsAtom);
return useMutation<
void,
HttpRequest,
unknown,
{ id: string; update: Partial<HttpRequest> | ((r: HttpRequest) => HttpRequest) }
>({
@@ -18,7 +22,10 @@ export function useUpdateAnyHttpRequest() {
const patchedRequest =
typeof update === 'function' ? update(request) : { ...request, ...update };
await invokeCmd('cmd_update_http_request', { request: patchedRequest });
return invokeCmd<HttpRequest>('cmd_update_http_request', { request: patchedRequest });
},
onSuccess: async (request) => {
setHttpRequests(updateModelList(request));
}
});
}

View File

@@ -1,10 +1,14 @@
import { useMutation } from '@tanstack/react-query';
import type { CookieJar } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai/index';
import { getCookieJar } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { cookieJarsAtom } from './useCookieJars';
import { updateModelList } from './useSyncModelStores';
export function useUpdateCookieJar(id: string | null) {
return useMutation<void, unknown, Partial<CookieJar> | ((j: CookieJar) => CookieJar)>({
const setCookieJars = useSetAtom(cookieJarsAtom);
return useMutation<CookieJar, unknown, Partial<CookieJar> | ((j: CookieJar) => CookieJar)>({
mutationKey: ['update_cookie_jar', id],
mutationFn: async (v) => {
const cookieJar = await getCookieJar(id);
@@ -13,7 +17,10 @@ export function useUpdateCookieJar(id: string | null) {
}
const newCookieJar = typeof v === 'function' ? v(cookieJar) : { ...cookieJar, ...v };
await invokeCmd('cmd_update_cookie_jar', { cookieJar: newCookieJar });
return invokeCmd<CookieJar>('cmd_update_cookie_jar', { cookieJar: newCookieJar });
},
onSuccess: (cookieJar) => {
setCookieJars(updateModelList(cookieJar));
},
});
}

View File

@@ -1,10 +1,18 @@
import { useMutation } from '@tanstack/react-query';
import type { Environment } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai/index';
import { getEnvironment } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { environmentsAtom } from './useEnvironments';
import {updateModelList} from "./useSyncModelStores";
export function useUpdateEnvironment(id: string | null) {
return useMutation<void, unknown, Partial<Environment> | ((r: Environment) => Environment)>({
const setEnvironments = useSetAtom(environmentsAtom);
return useMutation<
Environment,
unknown,
Partial<Environment> | ((r: Environment) => Environment)
>({
mutationKey: ['update_environment', id],
mutationFn: async (v) => {
const environment = await getEnvironment(id);
@@ -13,7 +21,10 @@ export function useUpdateEnvironment(id: string | null) {
}
const newEnvironment = typeof v === 'function' ? v(environment) : { ...environment, ...v };
await invokeCmd('cmd_update_environment', { environment: newEnvironment });
return invokeCmd<Environment>('cmd_update_environment', { environment: newEnvironment });
},
onSuccess: async (environment) => {
setEnvironments(updateModelList(environment));
},
});
}

View File

@@ -1,15 +1,21 @@
import { useMutation } from '@tanstack/react-query';
import type { Settings } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai';
import { getSettings } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import { settingsAtom } from './useSettings';
export function useUpdateSettings() {
return useMutation<void, unknown, Partial<Settings>>({
const setSettings = useSetAtom(settingsAtom);
return useMutation<Settings, unknown, Partial<Settings>>({
mutationKey: ['update_settings'],
mutationFn: async (patch) => {
const settings = await getSettings();
const newSettings: Settings = { ...settings, ...patch };
await invokeCmd('cmd_update_settings', { settings: newSettings });
return invokeCmd<Settings>('cmd_update_settings', { settings: newSettings });
},
onSuccess: (settings) => {
setSettings(settings);
},
});
}

View File

@@ -1,10 +1,14 @@
import { useMutation } from '@tanstack/react-query';
import type { Workspace } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai/index";
import { getWorkspace } from '../lib/store';
import { invokeCmd } from '../lib/tauri';
import {updateModelList} from "./useSyncModelStores";
import {workspacesAtom} from "./useWorkspaces";
export function useUpdateWorkspace(id: string | null) {
return useMutation<void, unknown, Partial<Workspace> | ((w: Workspace) => Workspace)>({
const setWorkspaces = useSetAtom(workspacesAtom);
return useMutation<Workspace, unknown, Partial<Workspace> | ((w: Workspace) => Workspace)>({
mutationKey: ['update_workspace', id],
mutationFn: async (v) => {
const workspace = await getWorkspace(id);
@@ -13,7 +17,10 @@ export function useUpdateWorkspace(id: string | null) {
}
const newWorkspace = typeof v === 'function' ? v(workspace) : { ...workspace, ...v };
await invokeCmd('cmd_update_workspace', { workspace: newWorkspace });
return invokeCmd('cmd_update_workspace', { workspace: newWorkspace });
},
onSuccess: async (workspace) => {
setWorkspaces(updateModelList(workspace));
},
});
}

View File

@@ -5,6 +5,7 @@ import type {
HttpResponse,
HttpResponseHeader,
} from '@yaakapp-internal/models';
import MimeType from 'whatwg-mimetype';
export const BODY_TYPE_NONE = null;
export const BODY_TYPE_GRAPHQL = 'graphql';
@@ -58,5 +59,8 @@ export function getContentTypeHeader(headers: HttpResponseHeader[]): string | nu
export function getCharsetFromContentType(headers: HttpResponseHeader[]): string | null {
const contentType = getContentTypeHeader(headers);
return contentType?.match(/charset=([^ ;]+)/)?.[1] ?? null;
if (contentType == null) return null;
const mimeType = new MimeType(contentType);
return mimeType.parameters.get('charset') ?? null;
}

View File

@@ -19,7 +19,7 @@
"@lezer/lr": "^1.3.3",
"@react-hook/resize-observer": "^2.0.2",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/react-query": "^5.55.4",
"@tanstack/react-query": "^5.59.16",
"@tanstack/react-virtual": "^3.10.8",
"@tauri-apps/api": "^2.0.1",
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",
@@ -54,6 +54,7 @@
"react-use": "^17.5.1",
"slugify": "^1.6.6",
"uuid": "^10.0.0",
"whatwg-mimetype": "^4.0.0",
"xml-formatter": "^3.6.3"
},
"devDependencies": {
@@ -67,6 +68,7 @@
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@types/uuid": "^10.0.0",
"@types/whatwg-mimetype": "^3.0.2",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"decompress": "^4.2.1",