mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-19 00:57:48 +01:00
Compare commits
15 Commits
v2024.11.0
...
v2024.11.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f50f35519 | ||
|
|
4e775b2b49 | ||
|
|
e77a9e5d44 | ||
|
|
a381e44d8c | ||
|
|
4acf0969e8 | ||
|
|
30c4178269 | ||
|
|
dffe6e0a16 | ||
|
|
8090e67b9e | ||
|
|
f1beabcb6f | ||
|
|
647b8e2313 | ||
|
|
f5b4697608 | ||
|
|
f201857d51 | ||
|
|
0d982057a5 | ||
|
|
6fb94384b9 | ||
|
|
d754e7233d |
180
package-lock.json
generated
180
package-lock.json
generated
@@ -14,10 +14,11 @@
|
||||
"src-tauri/yaak_plugin_runtime",
|
||||
"src-tauri/yaak_sync",
|
||||
"src-tauri/yaak_templates",
|
||||
"src-tauri/yaak_sse",
|
||||
"src-web"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.0.2",
|
||||
"@tauri-apps/cli": "^2.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.5.0",
|
||||
"@typescript-eslint/parser": "^8.5.0",
|
||||
"eslint": "^8",
|
||||
@@ -2650,6 +2651,33 @@
|
||||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-virtual": {
|
||||
"version": "3.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.8.tgz",
|
||||
"integrity": "sha512-VbzbVGSsZlQktyLrP5nxE+vE1ZR+U0NFAWPbJLoG2+DKPwd2D7dVICTVIIaYlJqX1ZCEnYDbaOpmMwbsyhBoIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/virtual-core": "3.10.8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/virtual-core": {
|
||||
"version": "3.10.8",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.8.tgz",
|
||||
"integrity": "sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/api": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.0.2.tgz",
|
||||
@@ -2661,9 +2689,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.0.2.tgz",
|
||||
"integrity": "sha512-R4ontHZvXORArERAHIidp5zRfZEshZczTiK+poslBv7AGKpQZoMw+E49zns7mOmP64i2Cq9Ci0pJvi4Rm8Okzw==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.0.3.tgz",
|
||||
"integrity": "sha512-JwEyhc5BAVpn4E8kxzY/h7+bVOiXQdudR1r3ODMfyyumZBfgIWqpD/WuTcPq6Yjchju1BSS+80jAE/oYwI/RKg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"bin": {
|
||||
@@ -2677,22 +2705,22 @@
|
||||
"url": "https://opencollective.com/tauri"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tauri-apps/cli-darwin-arm64": "2.0.2",
|
||||
"@tauri-apps/cli-darwin-x64": "2.0.2",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.0.2",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.0.2",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.0.2",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.0.2",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.0.2",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.0.2",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.0.2",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.0.2"
|
||||
"@tauri-apps/cli-darwin-arm64": "2.0.3",
|
||||
"@tauri-apps/cli-darwin-x64": "2.0.3",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.0.3",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.0.3",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.0.3",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.0.3",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.0.3",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.0.3",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.0.3",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.2.tgz",
|
||||
"integrity": "sha512-B+/a8Q6wAqmB4A4HVeK0oQP5TdQGKW60ZLOI9O2ktH2HPr9ETr3XkwXPuJ2uAOuGEgtRZHBgFOIgG000vMnKlg==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.0.3.tgz",
|
||||
"integrity": "sha512-jIsbxGWS+As1ZN7umo90nkql/ZAbrDK0GBT6UsgHSz5zSwwArICsZFFwE1pLZip5yoiV5mn3TGG2c1+v+0puzQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2707,9 +2735,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.2.tgz",
|
||||
"integrity": "sha512-kaurhn6XT4gAVCPAQSSHl/CHFxTS0ljc47N7iGTSlYJ03sCWPRZeNuVa/bn6rolz9MA2JfnRnFqB1pUL6jzp9Q==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.0.3.tgz",
|
||||
"integrity": "sha512-ROITHtLTA1muyrwgyuwyasmaLCGtT4as/Kd1kerXaSDtFcYrnxiM984ZD0+FDUEDl5BgXtYa/sKKkKQFjgmM0A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2724,9 +2752,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.2.tgz",
|
||||
"integrity": "sha512-bVrofjlacMxmGMcqK18iBW05tsZXOd19/MnqruFFcHSVjvkGGIXHMtUbMXnZNXBPkHDsnfytNtkY9SZGfCFaBA==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.0.3.tgz",
|
||||
"integrity": "sha512-bQ3EZwCFfrLg/ZQ2I8sLuifSxESz4TP56SleTkKsPtTIZgNnKpM88PRDz4neiRroHVOq8NK0X276qi9LjGcXPw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2741,9 +2769,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.2.tgz",
|
||||
"integrity": "sha512-7XCBn0TTBVQGnV42dXcbHPLg/9W8kJoVzuliIozvNGyRWxfXqDbQYzpI48HUQG3LgHMabcw8+pVZAfGhevLrCA==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.0.3.tgz",
|
||||
"integrity": "sha512-aLfAA8P9OTErVUk3sATxtXqpAtlfDPMPp4fGjDysEELG/MyekGhmh2k/kG/i32OdPeCfO+Nr37wJksARJKubGw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2758,9 +2786,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.2.tgz",
|
||||
"integrity": "sha512-1xi2SreGVlpAL68MCsDUY63rdItUdPZreXIAcOVqvUehcJRYOa1XGSBhrV0YXRgZeh0AtKC19z6PRzcv4rosZA==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.0.3.tgz",
|
||||
"integrity": "sha512-I4MVD7nf6lLLRmNQPpe5beEIFM6q7Zkmh77ROA5BNu/+vHNL5kiTMD+bmd10ZL2r753A6pO7AvqkIxcBuIl0tg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2775,9 +2803,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.2.tgz",
|
||||
"integrity": "sha512-WVjwYzPWFqZVg1fx6KSU5w47Q0VbMyaCp34qs5EcS8EIU0/RnofdzqUoOYqvgGVgNgoz7Pj5dXK2SkS8BHXMmA==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.0.3.tgz",
|
||||
"integrity": "sha512-C6Jkx2zZGKkoi+sg5FK9GoH/0EvAaOgrZfF5azV5EALGba46g7VpWcZgp9zFUd7K2IzTi+0OOY8TQ2OVfKZgew==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2792,9 +2820,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.2.tgz",
|
||||
"integrity": "sha512-h5miE2mctgaQNn/BbG9o1pnJcrx+VGBi2A6JFqGu934lFgSV5+s28M8Gc8AF2JgFH4hQV4IuMkeSw8Chu5Dodg==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.0.3.tgz",
|
||||
"integrity": "sha512-qi4ghmTfSAl+EEUDwmwI9AJUiOLNSmU1RgiGgcPRE+7A/W+Am9UnxYySAiRbB/gJgTl9sj/pqH5Y9duP1/sqHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2809,9 +2837,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.2.tgz",
|
||||
"integrity": "sha512-2b8oO0+dYonahG5PfA/zoq0zlafLclfmXgqoWDZ++UiPtQHJNpNeEQ8GWbSFKGHQ494Jo6jHvazOojGRE1kqAg==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.0.3.tgz",
|
||||
"integrity": "sha512-UXxHkYmFesC97qVmZre4vY7oDxRDtC2OeKNv0bH+iSnuUp/ROxzJYGyaelnv9Ybvgl4YVqDCnxgB28qMM938TA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2826,9 +2854,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.2.tgz",
|
||||
"integrity": "sha512-axgICLunFi0To3EibdCBgbST5RocsSmtM4c04+CbcX8WQQosJ9ziWlCSrrOTRr+gJERAMSvEyVUS98f6bWMw9A==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.0.3.tgz",
|
||||
"integrity": "sha512-D+xoaa35RGlkXDpnL5uDTpj29untuC5Wp6bN9snfgFDagD0wnFfC8+2ZQGu16bD0IteWqDI0OSoIXhNvy+F+wg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -2843,9 +2871,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.2.tgz",
|
||||
"integrity": "sha512-JR17cM6+DyExZRgpXr2/DdqvcFYi/EKvQt8dI5R1/uQoesWd8jeNnrU7c1FG1Zmw9+pTzDztsNqEKsrNq2sNIg==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.0.3.tgz",
|
||||
"integrity": "sha512-eWV9XWb4dSYHXl13OtYWLjX1JHphUEkHkkGwJrhr8qFBm7RbxXxQvrsUEprSi51ug/dwJenjJgM4zR8By4htfw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3345,6 +3373,10 @@
|
||||
"resolved": "plugin-runtime",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaakapp-internal/sse": {
|
||||
"resolved": "src-tauri/yaak_sse",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaakapp-internal/template": {
|
||||
"resolved": "src-tauri/yaak_templates",
|
||||
"link": true
|
||||
@@ -6464,15 +6496,6 @@
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-fuzzy": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-fuzzy/-/fast-fuzzy-1.12.0.tgz",
|
||||
"integrity": "sha512-sXxGgHS+ubYpsdLnvOvJ9w5GYYZrtL9mkosG3nfuD446ahvoWEsSKBP7ieGmWIKVLnaxRDgUJkZMdxRgA2Ni+Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"graphemesplit": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
@@ -6892,6 +6915,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/fuzzbunny": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fuzzbunny/-/fuzzbunny-1.0.1.tgz",
|
||||
"integrity": "sha512-afCIda+Ox6xw3I+b4nhbdXBRZJQQhJAH2kKlxVcybuJTFe1LUn2V7jD0+AGB/ssgyDSOrs8y+CIycv+PsTfVkA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/gauge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
|
||||
@@ -7181,16 +7210,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/graphemesplit": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/graphemesplit/-/graphemesplit-2.4.4.tgz",
|
||||
"integrity": "sha512-lKrpp1mk1NH26USxC/Asw4OHbhSQf5XfrWZ+CDv/dFVvd1j17kFgMotdJvOesmHkbFX9P9sBfpH8VogxOWLg8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-base64": "^3.6.0",
|
||||
"unicode-trie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/graphql": {
|
||||
"version": "16.9.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz",
|
||||
@@ -8211,12 +8230,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/js-base64": {
|
||||
"version": "3.7.7",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz",
|
||||
"integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/js-cookie": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
|
||||
@@ -9770,12 +9783,6 @@
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
|
||||
"integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/papaparse": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz",
|
||||
@@ -12514,12 +12521,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-inflate": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
|
||||
"integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
@@ -12838,16 +12839,6 @@
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unicode-trie": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
|
||||
"integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "^0.2.5",
|
||||
"tiny-inflate": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unique-string": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz",
|
||||
@@ -13738,6 +13729,10 @@
|
||||
"name": "@yaakapp-internal/plugin",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"src-tauri/yaak_sse": {
|
||||
"name": "@yaakapp-internal/sse",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"src-tauri/yaak_templates": {
|
||||
"name": "@yaakapp-internal/template",
|
||||
"version": "1.0.0"
|
||||
@@ -13757,6 +13752,7 @@
|
||||
"@react-hook/resize-observer": "^2.0.2",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tanstack/react-query": "^5.55.4",
|
||||
"@tanstack/react-virtual": "^3.10.8",
|
||||
"@tauri-apps/api": "^2.0.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0",
|
||||
@@ -13771,10 +13767,10 @@
|
||||
"codemirror-json-schema": "^0.6.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"fast-fuzzy": "^1.12.0",
|
||||
"focus-trap-react": "^10.2.3",
|
||||
"format-graphql": "^1.5.0",
|
||||
"framer-motion": "^11.5.4",
|
||||
"fuzzbunny": "^1.0.1",
|
||||
"jotai": "^2.9.3",
|
||||
"lucide-react": "^0.439.0",
|
||||
"mime": "^4.0.4",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"src-tauri/yaak_plugin_runtime",
|
||||
"src-tauri/yaak_sync",
|
||||
"src-tauri/yaak_templates",
|
||||
"src-tauri/yaak_sse",
|
||||
"src-web"
|
||||
],
|
||||
"scripts": {
|
||||
@@ -30,7 +31,7 @@
|
||||
"tauri-before-dev": "npm run --workspaces --if-present dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.0.2",
|
||||
"@tauri-apps/cli": "^2.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.5.0",
|
||||
"@typescript-eslint/parser": "^8.5.0",
|
||||
"eslint": "^8",
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
edition = "2018"
|
||||
|
||||
# Widths
|
||||
chain_width = 100
|
||||
max_width = 100
|
||||
single_line_if_else_max_width = 100
|
||||
fn_call_width = 100
|
||||
|
||||
254
src-tauri/Cargo.lock
generated
254
src-tauri/Cargo.lock
generated
@@ -1410,7 +1410,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
"libloading 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1623,6 +1623,21 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eventsource-client"
|
||||
version = "0.13.0"
|
||||
source = "git+https://github.com/yaakapp/rust-eventsource-client#e9e1e52421f11f0409179389b997aa49275a8461"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"hyper 0.14.30",
|
||||
"hyper-rustls 0.24.2",
|
||||
"hyper-timeout 0.4.1",
|
||||
"log",
|
||||
"pin-project",
|
||||
"rand 0.8.5",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exr"
|
||||
version = "1.72.0"
|
||||
@@ -1807,6 +1822,21 @@ dependencies = [
|
||||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.30"
|
||||
@@ -1893,6 +1923,7 @@ version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
@@ -2521,9 +2552,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.2"
|
||||
version = "0.27.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
|
||||
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
@@ -2580,9 +2611,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.7"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
|
||||
checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
@@ -2593,7 +2624,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
@@ -2984,7 +3014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf"
|
||||
dependencies = [
|
||||
"gtk-sys",
|
||||
"libloading",
|
||||
"libloading 0.7.4",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
@@ -3015,6 +3045,16 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
@@ -3462,7 +3502,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||
dependencies = [
|
||||
"malloc_buf",
|
||||
"objc_exception",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3470,6 +3509,9 @@ name = "objc-sys"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
@@ -3497,6 +3539,30 @@ dependencies = [
|
||||
"objc2-quartz-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-cloud-kit"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-contacts"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-data"
|
||||
version = "0.2.2"
|
||||
@@ -3521,6 +3587,18 @@ dependencies = [
|
||||
"objc2-metal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-location"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-contacts",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-encode"
|
||||
version = "4.0.3"
|
||||
@@ -3540,6 +3618,18 @@ dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-link-presentation"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-metal"
|
||||
version = "0.2.2"
|
||||
@@ -3566,21 +3656,71 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_exception"
|
||||
version = "0.1.2"
|
||||
name = "objc2-symbols"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
|
||||
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_id"
|
||||
version = "0.1.1"
|
||||
name = "objc2-ui-kit"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
|
||||
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
||||
dependencies = [
|
||||
"objc",
|
||||
"bitflags 2.6.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-image",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
"objc2-link-presentation",
|
||||
"objc2-quartz-core",
|
||||
"objc2-symbols",
|
||||
"objc2-uniform-type-identifiers",
|
||||
"objc2-user-notifications",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-uniform-type-identifiers"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-user-notifications"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-web-kit"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4181,12 +4321,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.13.1"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc"
|
||||
checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive 0.13.1",
|
||||
"prost-derive 0.13.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4203,8 +4343,8 @@ dependencies = [
|
||||
"once_cell",
|
||||
"petgraph",
|
||||
"prettyplease",
|
||||
"prost 0.13.1",
|
||||
"prost-types 0.13.1",
|
||||
"prost 0.13.3",
|
||||
"prost-types 0.13.3",
|
||||
"regex",
|
||||
"syn 2.0.72",
|
||||
"tempfile",
|
||||
@@ -4225,9 +4365,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.13.1"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca"
|
||||
checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
@@ -4273,11 +4413,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.13.1"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2"
|
||||
checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670"
|
||||
dependencies = [
|
||||
"prost 0.13.1",
|
||||
"prost 0.13.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4682,7 +4822,7 @@ dependencies = [
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.4.1",
|
||||
"hyper-rustls 0.27.2",
|
||||
"hyper-rustls 0.27.3",
|
||||
"hyper-tls",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
@@ -5169,9 +5309,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.208"
|
||||
version = "1.0.210"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -5199,9 +5339,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.208"
|
||||
version = "1.0.210"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5955,9 +6095,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.0.2"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5920aad0804ea5e86808d4b6e8753d3bcbae7efc8f4e41a4da00b45427559868"
|
||||
checksum = "44438500b50708bfc1e6083844e135d1b516325aae58710dcd8fb67e050ae87c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -6247,9 +6387,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.0.1"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af12ad1af974b274ef1d32a94e6eba27a312b429ef28fcb98abc710df7f9151d"
|
||||
checksum = "c8f437293d6f5e5dce829250f4dbdce4e0b52905e297a6689cc2963eb53ac728"
|
||||
dependencies = [
|
||||
"dpi",
|
||||
"gtk",
|
||||
@@ -6266,9 +6406,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "2.0.1"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e45e88aa0b11b302d836e6ea3e507a6359044c4a8bc86b865ba99868c695753d"
|
||||
checksum = "1431602bcc71f2f840ad623915c9842ecc32999b867c4a787d975a17a9625cc6"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http 1.1.0",
|
||||
@@ -6526,9 +6666,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.15"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
|
||||
checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
@@ -6636,9 +6776,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tonic"
|
||||
version = "0.12.1"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38659f4a91aba8598d27821589f5db7dddd94601e7a01b1e485a50e5484c7401"
|
||||
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
@@ -6654,7 +6794,7 @@ dependencies = [
|
||||
"hyper-util",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost 0.13.1",
|
||||
"prost 0.13.3",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -7708,14 +7848,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.44.1"
|
||||
version = "0.46.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "440600584cfbd8b0d28eace95c1f2c253db05dae43780b79380aa1e868f04c73"
|
||||
checksum = "2f8c948dc5f7c23bd93ba03b85b7f679852589bb78e150424d993171e4ef7b73"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block",
|
||||
"cocoa 0.26.0",
|
||||
"core-graphics 0.24.0",
|
||||
"block2",
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
"dunce",
|
||||
@@ -7728,8 +7866,11 @@ dependencies = [
|
||||
"kuchikiki",
|
||||
"libc",
|
||||
"ndk",
|
||||
"objc",
|
||||
"objc_id",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-ui-kit",
|
||||
"objc2-web-kit",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"raw-window-handle",
|
||||
@@ -7823,6 +7964,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"cocoa 0.26.0",
|
||||
"datetime",
|
||||
"eventsource-client",
|
||||
"hex_color",
|
||||
"http 1.1.0",
|
||||
"log",
|
||||
@@ -7854,6 +7996,7 @@ dependencies = [
|
||||
"yaak_grpc",
|
||||
"yaak_models",
|
||||
"yaak_plugin_runtime",
|
||||
"yaak_sse",
|
||||
"yaak_templates",
|
||||
]
|
||||
|
||||
@@ -7910,7 +8053,7 @@ dependencies = [
|
||||
"dunce",
|
||||
"log",
|
||||
"path-slash",
|
||||
"prost 0.13.1",
|
||||
"prost 0.13.3",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"reqwest",
|
||||
@@ -7920,12 +8063,21 @@ dependencies = [
|
||||
"tauri-plugin-shell",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tonic 0.12.1",
|
||||
"tonic 0.12.3",
|
||||
"tonic-build",
|
||||
"ts-rs",
|
||||
"yaak_models",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaak_sse"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaak_templates"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["yaak_grpc", "yaak_templates", "yaak_plugin_runtime", "yaak_models"]
|
||||
members = ["yaak_grpc", "yaak_templates", "yaak_plugin_runtime", "yaak_models", "yaak_sse"]
|
||||
|
||||
[package]
|
||||
name = "yaak-app"
|
||||
@@ -30,6 +30,7 @@ yaak_grpc = { path = "yaak_grpc" }
|
||||
yaak_templates = { path = "yaak_templates" }
|
||||
yaak_plugin_runtime = { 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"] }
|
||||
@@ -59,9 +60,10 @@ 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_plugin_runtime = { path = "yaak_plugin_runtime" }
|
||||
tauri-plugin-shell = "2.0.1"
|
||||
tauri = { version = "2.0.2", features = ["devtools", "protocol-asset"] }
|
||||
tauri = { version = "2.0.4", features = ["devtools", "protocol-asset"] }
|
||||
|
||||
2
src-tauri/gen/schemas/capabilities.json
generated
2
src-tauri/gen/schemas/capabilities.json
generated
@@ -1 +1 @@
|
||||
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"shell:allow-open","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-is-fullscreen","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-show","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-toggle-maximize","core:window:allow-internal-toggle-maximize","core:window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text"]}}
|
||||
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"shell:allow-open","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-internal-toggle-maximize","core:window:allow-is-fullscreen","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-show","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-toggle-maximize","core:window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text"]}}
|
||||
1
src-tauri/migrations/20241012181547_proxy-setting.sql
Normal file
1
src-tauri/migrations/20241012181547_proxy-setting.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE settings ADD COLUMN proxy TEXT;
|
||||
@@ -14,39 +14,45 @@ use http::{HeaderMap, HeaderName, HeaderValue};
|
||||
use log::{debug, error, warn};
|
||||
use mime_guess::Mime;
|
||||
use reqwest::redirect::Policy;
|
||||
use reqwest::{multipart, Url};
|
||||
use reqwest::{multipart, Proxy, Url};
|
||||
use reqwest::{Method, Response};
|
||||
use serde_json::Value;
|
||||
use tauri::{Manager, Runtime, WebviewWindow};
|
||||
use tokio::fs;
|
||||
use tokio::fs::{create_dir_all, File};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::sync::watch::Receiver;
|
||||
use tokio::sync::{oneshot, Mutex};
|
||||
use yaak_models::models::{
|
||||
Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader,
|
||||
HttpResponseState,
|
||||
HttpResponseState, ProxySetting, ProxySettingAuth,
|
||||
};
|
||||
use yaak_models::queries::{
|
||||
get_http_response, get_or_create_settings, get_workspace, update_response_if_id,
|
||||
upsert_cookie_jar,
|
||||
};
|
||||
use yaak_models::queries::{get_workspace, update_response_if_id, upsert_cookie_jar};
|
||||
use yaak_plugin_runtime::events::{RenderPurpose, WindowContext};
|
||||
|
||||
pub async fn send_http_request<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
request: &HttpRequest,
|
||||
response: &HttpResponse,
|
||||
og_response: &HttpResponse,
|
||||
environment: Option<Environment>,
|
||||
cookie_jar: Option<CookieJar>,
|
||||
cancelled_rx: &mut Receiver<bool>,
|
||||
) -> Result<HttpResponse, String> {
|
||||
let workspace = get_workspace(window, &request.workspace_id)
|
||||
.await
|
||||
.expect("Failed to get Workspace");
|
||||
let workspace =
|
||||
get_workspace(window, &request.workspace_id).await.expect("Failed to get Workspace");
|
||||
let settings = get_or_create_settings(window).await;
|
||||
let cb = PluginTemplateCallback::new(
|
||||
window.app_handle(),
|
||||
&WindowContext::from_window(window),
|
||||
RenderPurpose::Send,
|
||||
);
|
||||
|
||||
let response_id = og_response.id.clone();
|
||||
let response = Arc::new(Mutex::new(og_response.clone()));
|
||||
|
||||
let rendered_request =
|
||||
render_http_request(&request, &workspace, environment.as_ref(), &cb).await;
|
||||
|
||||
@@ -56,6 +62,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
|
||||
url_string = format!("http://{}", url_string);
|
||||
}
|
||||
debug!("Sending request to {url_string}");
|
||||
|
||||
let mut client_builder = reqwest::Client::builder()
|
||||
.redirect(match workspace.setting_follow_redirects {
|
||||
@@ -70,6 +77,31 @@ pub async fn send_http_request<R: Runtime>(
|
||||
.danger_accept_invalid_certs(!workspace.setting_validate_certificates)
|
||||
.tls_info(true);
|
||||
|
||||
match settings.proxy {
|
||||
Some(ProxySetting::Disabled) => client_builder = client_builder.no_proxy(),
|
||||
Some(ProxySetting::Enabled { http, https, auth }) => {
|
||||
debug!("Using proxy http={http} https={https}");
|
||||
let mut proxy = Proxy::custom(move |url| {
|
||||
let http = if http.is_empty() { None } else { Some(http.to_owned()) };
|
||||
let https = if https.is_empty() { None } else { Some(https.to_owned()) };
|
||||
let proxy_url = match (url.scheme(), http, https) {
|
||||
("http", Some(proxy_url), _) => Some(proxy_url),
|
||||
("https", _, Some(proxy_url)) => Some(proxy_url),
|
||||
_ => None,
|
||||
};
|
||||
proxy_url
|
||||
});
|
||||
|
||||
if let Some(ProxySettingAuth { user, password }) = auth {
|
||||
debug!("Using proxy auth");
|
||||
proxy = proxy.basic_auth(user.as_str(), password.as_str());
|
||||
}
|
||||
|
||||
client_builder = client_builder.proxy(proxy);
|
||||
}
|
||||
None => {} // Nothing to do for this one, as it is the default
|
||||
}
|
||||
|
||||
// Add cookie store if specified
|
||||
let maybe_cookie_manager = match cookie_jar.clone() {
|
||||
Some(cj) => {
|
||||
@@ -116,7 +148,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
Ok(u) => u,
|
||||
Err(e) => {
|
||||
return Ok(response_err(
|
||||
response,
|
||||
&*response.lock().await,
|
||||
format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()),
|
||||
window,
|
||||
)
|
||||
@@ -128,7 +160,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
Ok(u) => u,
|
||||
Err(e) => {
|
||||
return Ok(response_err(
|
||||
response,
|
||||
&*response.lock().await,
|
||||
format!("Failed to parse URL \"{}\": {}", url_string, e.to_string()),
|
||||
window,
|
||||
)
|
||||
@@ -191,16 +223,8 @@ pub async fn send_http_request<R: Runtime>(
|
||||
let a = rendered_request.authentication;
|
||||
|
||||
if b == "basic" {
|
||||
let username = a
|
||||
.get("username")
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
let password = a
|
||||
.get("password")
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
let username = a.get("username").unwrap_or(empty_value).as_str().unwrap_or_default();
|
||||
let password = a.get("password").unwrap_or(empty_value).as_str().unwrap_or_default();
|
||||
|
||||
let auth = format!("{username}:{password}");
|
||||
let encoded = BASE64_STANDARD.encode(auth);
|
||||
@@ -209,11 +233,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
HeaderValue::from_str(&format!("Basic {}", encoded)).unwrap(),
|
||||
);
|
||||
} else if b == "bearer" {
|
||||
let token = a
|
||||
.get("token")
|
||||
.unwrap_or(empty_value)
|
||||
.as_str()
|
||||
.unwrap_or_default();
|
||||
let token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or_default();
|
||||
headers.insert(
|
||||
"Authorization",
|
||||
HeaderValue::from_str(&format!("Bearer {token}")).unwrap(),
|
||||
@@ -227,10 +247,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
let query = get_str_h(&request_body, "query");
|
||||
let variables = get_str_h(&request_body, "variables");
|
||||
let body = if variables.trim().is_empty() {
|
||||
format!(
|
||||
r#"{{"query":{}}}"#,
|
||||
serde_json::to_string(query).unwrap_or_default()
|
||||
)
|
||||
format!(r#"{{"query":{}}}"#, serde_json::to_string(query).unwrap_or_default())
|
||||
} else {
|
||||
format!(
|
||||
r#"{{"query":{},"variables":{variables}}}"#,
|
||||
@@ -275,7 +292,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
request_builder = request_builder.body(f);
|
||||
}
|
||||
Err(e) => {
|
||||
return Ok(response_err(response, e, window).await);
|
||||
return Ok(response_err(&*response.lock().await, e, window).await);
|
||||
}
|
||||
}
|
||||
} else if body_type == "multipart/form-data" && request_body.contains_key("form") {
|
||||
@@ -301,9 +318,12 @@ pub async fn send_http_request<R: Runtime>(
|
||||
match fs::read(file_path.clone()).await {
|
||||
Ok(f) => multipart::Part::bytes(f),
|
||||
Err(e) => {
|
||||
return Ok(
|
||||
response_err(response, e.to_string(), window).await
|
||||
);
|
||||
return Ok(response_err(
|
||||
&*response.lock().await,
|
||||
e.to_string(),
|
||||
window,
|
||||
)
|
||||
.await);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -318,9 +338,8 @@ pub async fn send_http_request<R: Runtime>(
|
||||
Mime::from_str("application/octet-stream").unwrap();
|
||||
let mime =
|
||||
mime_guess::from_path(file_path.clone()).first_or(default_mime);
|
||||
part = part
|
||||
.mime_str(mime.essence_str())
|
||||
.map_err(|e| e.to_string())?;
|
||||
part =
|
||||
part.mime_str(mime.essence_str()).map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
// Set file path if not empty
|
||||
@@ -351,7 +370,8 @@ pub async fn send_http_request<R: Runtime>(
|
||||
let sendable_req = match request_builder.build() {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
return Ok(response_err(response, e.to_string(), window).await);
|
||||
warn!("Failed to build request builder {e:?}");
|
||||
return Ok(response_err(&*response.lock().await, e.to_string(), window).await);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -368,62 +388,58 @@ pub async fn send_http_request<R: Runtime>(
|
||||
Ok(r) = resp_rx => r,
|
||||
_ = cancelled_rx.changed() => {
|
||||
debug!("Request cancelled");
|
||||
return Ok(response_err(response, "Request was cancelled".to_string(), window).await);
|
||||
return Ok(response_err(&*response.lock().await, "Request was cancelled".to_string(), window).await);
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
let window = window.clone();
|
||||
let response = response.clone();
|
||||
let cancelled_rx = cancelled_rx.clone();
|
||||
let response_id = response_id.clone();
|
||||
let response = response.clone();
|
||||
tokio::spawn(async move {
|
||||
let result = match raw_response {
|
||||
match raw_response {
|
||||
Ok(mut v) => {
|
||||
let mut response = response.clone();
|
||||
let content_length = v.content_length();
|
||||
let response_headers = v.headers().clone();
|
||||
response.elapsed_headers = start.elapsed().as_millis() as i32;
|
||||
response.status = v.status().as_u16() as i32;
|
||||
response.status_reason = v.status().canonical_reason().map(|s| s.to_string());
|
||||
response.headers = response_headers
|
||||
.iter()
|
||||
.map(|(k, v)| HttpResponseHeader {
|
||||
name: k.as_str().to_string(),
|
||||
value: v.to_str().unwrap_or_default().to_string(),
|
||||
})
|
||||
.collect();
|
||||
response.url = v.url().to_string();
|
||||
response.remote_addr = v.remote_addr().map(|a| a.to_string());
|
||||
response.version = match v.version() {
|
||||
reqwest::Version::HTTP_09 => Some("HTTP/0.9".to_string()),
|
||||
reqwest::Version::HTTP_10 => Some("HTTP/1.0".to_string()),
|
||||
reqwest::Version::HTTP_11 => Some("HTTP/1.1".to_string()),
|
||||
reqwest::Version::HTTP_2 => Some("HTTP/2".to_string()),
|
||||
reqwest::Version::HTTP_3 => Some("HTTP/3".to_string()),
|
||||
_ => None,
|
||||
};
|
||||
let dir = window.app_handle().path().app_data_dir().unwrap();
|
||||
let base_dir = dir.join("responses");
|
||||
create_dir_all(base_dir.clone())
|
||||
.await
|
||||
.expect("Failed to create responses dir");
|
||||
let body_path = if response.id.is_empty() {
|
||||
base_dir.join(response.id.clone())
|
||||
create_dir_all(base_dir.clone()).await.expect("Failed to create responses dir");
|
||||
let body_path = if response_id.is_empty() {
|
||||
base_dir.join(response_id.clone())
|
||||
} else {
|
||||
base_dir.join(uuid::Uuid::new_v4().to_string())
|
||||
};
|
||||
|
||||
response.body_path = Some(
|
||||
body_path
|
||||
.to_str()
|
||||
.expect("Failed to get body path")
|
||||
.to_string(),
|
||||
);
|
||||
{
|
||||
let mut r = response.lock().await;
|
||||
r.body_path = Some(body_path.to_str().unwrap().to_string());
|
||||
r.elapsed_headers = start.elapsed().as_millis() as i32;
|
||||
r.status = v.status().as_u16() as i32;
|
||||
r.status_reason = v.status().canonical_reason().map(|s| s.to_string());
|
||||
r.headers = response_headers
|
||||
.iter()
|
||||
.map(|(k, v)| HttpResponseHeader {
|
||||
name: k.as_str().to_string(),
|
||||
value: v.to_str().unwrap_or_default().to_string(),
|
||||
})
|
||||
.collect();
|
||||
r.url = v.url().to_string();
|
||||
r.remote_addr = v.remote_addr().map(|a| a.to_string());
|
||||
r.version = match v.version() {
|
||||
reqwest::Version::HTTP_09 => Some("HTTP/0.9".to_string()),
|
||||
reqwest::Version::HTTP_10 => Some("HTTP/1.0".to_string()),
|
||||
reqwest::Version::HTTP_11 => Some("HTTP/1.1".to_string()),
|
||||
reqwest::Version::HTTP_2 => Some("HTTP/2".to_string()),
|
||||
reqwest::Version::HTTP_3 => Some("HTTP/3".to_string()),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let content_length = v.content_length();
|
||||
response.state = HttpResponseState::Connected;
|
||||
response = update_response_if_id(&window, &response)
|
||||
.await
|
||||
.expect("Failed to update response after connected");
|
||||
r.state = HttpResponseState::Connected;
|
||||
update_response_if_id(&window, &r)
|
||||
.await
|
||||
.expect("Failed to update response after connected");
|
||||
}
|
||||
|
||||
// Write body to FS
|
||||
let mut f = File::options()
|
||||
@@ -446,9 +462,10 @@ pub async fn send_http_request<R: Runtime>(
|
||||
f.write_all(&bytes).await.expect("Failed to write to file");
|
||||
f.flush().await.expect("Failed to flush file");
|
||||
written_bytes += bytes.len();
|
||||
response.elapsed = start.elapsed().as_millis() as i32;
|
||||
response.content_length = Some(written_bytes as i32);
|
||||
response = update_response_if_id(&window, &response)
|
||||
let mut r = response.lock().await;
|
||||
r.elapsed = start.elapsed().as_millis() as i32;
|
||||
r.content_length = Some(written_bytes as i32);
|
||||
update_response_if_id(&window, &r)
|
||||
.await
|
||||
.expect("Failed to update response");
|
||||
}
|
||||
@@ -456,21 +473,24 @@ pub async fn send_http_request<R: Runtime>(
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
response = response_err(&response, e.to_string(), &window).await;
|
||||
response_err(&*response.lock().await, e.to_string(), &window).await;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set final content length
|
||||
response.content_length = match content_length {
|
||||
Some(l) => Some(l as i32),
|
||||
None => Some(written_bytes as i32),
|
||||
{
|
||||
let mut r = response.lock().await;
|
||||
r.content_length = match content_length {
|
||||
Some(l) => Some(l as i32),
|
||||
None => Some(written_bytes as i32),
|
||||
};
|
||||
r.state = HttpResponseState::Closed;
|
||||
update_response_if_id(&window, &r)
|
||||
.await
|
||||
.expect("Failed to update response");
|
||||
};
|
||||
response.state = HttpResponseState::Closed;
|
||||
response = update_response_if_id(&window, &response)
|
||||
.await
|
||||
.expect("Failed to update response");
|
||||
|
||||
// Add cookie store if specified
|
||||
if let Some((cookie_store, mut cookie_jar)) = maybe_cookie_manager {
|
||||
@@ -497,19 +517,30 @@ pub async fn send_http_request<R: Runtime>(
|
||||
error!("Failed to update cookie jar: {}", e);
|
||||
};
|
||||
}
|
||||
response
|
||||
}
|
||||
Err(e) => response_err(&response, e.to_string(), &window).await,
|
||||
Err(e) => {
|
||||
warn!("Failed to execute request {e}");
|
||||
response_err(&*response.lock().await, format!("{e} → {e:?}"), &window).await;
|
||||
}
|
||||
};
|
||||
|
||||
done_tx.send(result.clone()).unwrap();
|
||||
let r = response.lock().await.clone();
|
||||
done_tx.send(r).unwrap();
|
||||
});
|
||||
};
|
||||
|
||||
Ok(tokio::select! {
|
||||
Ok(r) = done_rx => r,
|
||||
_ = cancelled_rx.changed() => {
|
||||
response_err(&response, "Request was cancelled".to_string(), &window).await
|
||||
match get_http_response(window, response_id.as_str()).await {
|
||||
Ok(mut r) => {
|
||||
r.state = HttpResponseState::Closed;
|
||||
update_response_if_id(&window, &r).await.expect("Failed to update response")
|
||||
},
|
||||
_ => {
|
||||
response_err(&*response.lock().await, "Ephemeral request was cancelled".to_string(), &window).await
|
||||
}.clone(),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ extern crate core;
|
||||
extern crate objc;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{create_dir_all, read_to_string, File};
|
||||
use std::fs::{create_dir_all, File};
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::str::FromStr;
|
||||
@@ -13,6 +13,7 @@ use std::{fs, panic};
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine;
|
||||
use chrono::Utc;
|
||||
use eventsource_client::{EventParser, SSE};
|
||||
use fern::colors::ColoredLevelConfig;
|
||||
use log::{debug, error, info, warn};
|
||||
use rand::random;
|
||||
@@ -27,6 +28,7 @@ use tauri::{Manager, WindowEvent};
|
||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||
use tauri_plugin_log::{fern, Target, TargetKind};
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
use tokio::fs::read_to_string;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use yaak_grpc::manager::{DynamicMessage, GrpcHandle};
|
||||
@@ -69,6 +71,7 @@ use yaak_plugin_runtime::events::{
|
||||
WindowContext,
|
||||
};
|
||||
use yaak_plugin_runtime::plugin_handle::PluginHandle;
|
||||
use yaak_sse::sse::ServerSentEvent;
|
||||
use yaak_templates::{Parser, Tokens};
|
||||
|
||||
mod analytics;
|
||||
@@ -337,7 +340,7 @@ async fn cmd_grpc_go<R: Runtime>(
|
||||
&GrpcConnection {
|
||||
elapsed: start.elapsed().as_millis() as i32,
|
||||
error: Some(err.clone()),
|
||||
state: GrpcConnectionState::Initialized,
|
||||
state: GrpcConnectionState::Closed,
|
||||
..conn.clone()
|
||||
},
|
||||
)
|
||||
@@ -593,6 +596,7 @@ async fn cmd_grpc_go<R: Runtime>(
|
||||
stream.into_inner()
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
warn!("GRPC stream error {e:?}");
|
||||
upsert_grpc_event(
|
||||
&w,
|
||||
&(match e.status {
|
||||
@@ -645,7 +649,7 @@ async fn cmd_grpc_go<R: Runtime>(
|
||||
&w,
|
||||
&GrpcEvent {
|
||||
content: "Connection complete".to_string(),
|
||||
status: Some(Code::Unavailable as i32),
|
||||
status: Some(Code::Ok as i32),
|
||||
metadata: metadata_to_map(trailers),
|
||||
event_type: GrpcEventType::ConnectionEnd,
|
||||
..base_event.clone()
|
||||
@@ -797,7 +801,7 @@ async fn cmd_filter_response<R: Runtime>(
|
||||
}
|
||||
}
|
||||
|
||||
let body = read_to_string(response.body_path.unwrap()).unwrap();
|
||||
let body = read_to_string(response.body_path.unwrap()).await.unwrap();
|
||||
|
||||
// TODO: Have plugins register their own content type (regex?)
|
||||
plugin_manager
|
||||
@@ -806,14 +810,36 @@ async fn cmd_filter_response<R: Runtime>(
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_get_sse_events(file_path: &str) -> Result<Vec<ServerSentEvent>, String> {
|
||||
let body = fs::read(file_path).map_err(|e| e.to_string())?;
|
||||
let mut p = EventParser::new();
|
||||
p.process_bytes(body.into()).map_err(|e| e.to_string())?;
|
||||
|
||||
let mut events = Vec::new();
|
||||
while let Some(e) = p.get_event() {
|
||||
if let SSE::Event(e) = e {
|
||||
events.push(ServerSentEvent {
|
||||
event_type: e.event_type,
|
||||
data: e.data,
|
||||
id: e.id,
|
||||
retry: e.retry,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_import_data<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
file_path: &str,
|
||||
) -> Result<WorkspaceExportResources, String> {
|
||||
let file =
|
||||
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
|
||||
let file = read_to_string(file_path)
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
|
||||
let file_contents = file.as_str();
|
||||
let (import_result, plugin_name) = plugin_manager
|
||||
.import_data(&window, file_contents)
|
||||
@@ -1139,7 +1165,7 @@ async fn response_err<R: Runtime>(
|
||||
error: String,
|
||||
w: &WebviewWindow<R>,
|
||||
) -> HttpResponse {
|
||||
warn!("Failed to send request: {}", error);
|
||||
warn!("Failed to send request: {error:?}");
|
||||
let mut response = response.clone();
|
||||
response.state = HttpResponseState::Closed;
|
||||
response.error = Some(error.clone());
|
||||
@@ -1801,6 +1827,7 @@ pub fn run() {
|
||||
])
|
||||
.level_for("plugin_runtime", log::LevelFilter::Info)
|
||||
.level_for("cookie_store", log::LevelFilter::Info)
|
||||
.level_for("eventsource_client::event_parser", log::LevelFilter::Info)
|
||||
.level_for("h2", log::LevelFilter::Info)
|
||||
.level_for("hyper", log::LevelFilter::Info)
|
||||
.level_for("hyper_util", log::LevelFilter::Info)
|
||||
@@ -1901,6 +1928,7 @@ pub fn run() {
|
||||
cmd_get_folder,
|
||||
cmd_get_grpc_request,
|
||||
cmd_get_http_request,
|
||||
cmd_get_sse_events,
|
||||
cmd_get_key_value,
|
||||
cmd_get_settings,
|
||||
cmd_get_workspace,
|
||||
|
||||
@@ -288,7 +288,8 @@ var SUPPORTED_ARGS = [
|
||||
// Request method
|
||||
DATA_FLAGS
|
||||
].flatMap((v) => v);
|
||||
function pluginHookImport(ctx, rawData) {
|
||||
var BOOL_FLAGS = ["G", "get", "digest"];
|
||||
function pluginHookImport(_ctx, rawData) {
|
||||
if (!rawData.match(/^\s*curl /)) {
|
||||
return null;
|
||||
}
|
||||
@@ -359,10 +360,11 @@ function importCommand(parseEntries, workspaceId) {
|
||||
}
|
||||
let value;
|
||||
const nextEntry = parseEntries[i + 1];
|
||||
const hasValue = !BOOL_FLAGS.includes(name);
|
||||
if (isSingleDash && name.length > 1) {
|
||||
value = name.slice(1);
|
||||
name = name.slice(0, 1);
|
||||
} else if (typeof nextEntry === "string" && !nextEntry.startsWith("-")) {
|
||||
} else if (typeof nextEntry === "string" && hasValue && !nextEntry.startsWith("-")) {
|
||||
value = nextEntry;
|
||||
i++;
|
||||
} else {
|
||||
|
||||
55
src-tauri/vendored/plugins/template-function-fs/build/index.js
generated
Normal file
55
src-tauri/vendored/plugins/template-function-fs/build/index.js
generated
Normal file
@@ -0,0 +1,55 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/index.ts
|
||||
var src_exports = {};
|
||||
__export(src_exports, {
|
||||
plugin: () => plugin
|
||||
});
|
||||
module.exports = __toCommonJS(src_exports);
|
||||
var import_node_fs = __toESM(require("node:fs"));
|
||||
var plugin = {
|
||||
templateFunctions: [{
|
||||
name: "fs.readFile",
|
||||
description: "Read the contents of a file as utf-8",
|
||||
args: [{ title: "Select File", type: "file", name: "path", label: "File" }],
|
||||
async onRender(_ctx, args) {
|
||||
if (!args.values.path) return null;
|
||||
try {
|
||||
return import_node_fs.default.promises.readFile(args.values.path, "utf-8");
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
plugin
|
||||
});
|
||||
9
src-tauri/vendored/plugins/template-function-fs/package.json
generated
Normal file
9
src-tauri/vendored/plugins/template-function-fs/package.json
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@yaakapp/template-function-fs",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"build": "yaakcli build ./src/index.ts",
|
||||
"dev": "yaakcli dev ./src/index.js"
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ var algorithms = ["md5", "sha1", "sha256", "sha512"];
|
||||
var plugin = {
|
||||
templateFunctions: algorithms.map((algorithm) => ({
|
||||
name: `hash.${algorithm}`,
|
||||
description: "Hash a value to its hexidecimal representation",
|
||||
args: [
|
||||
{
|
||||
name: "input",
|
||||
|
||||
@@ -26,24 +26,21 @@ module.exports = __toCommonJS(src_exports);
|
||||
var plugin = {
|
||||
templateFunctions: [{
|
||||
name: "prompt.text",
|
||||
description: "Prompt the user for input when sending a request",
|
||||
args: [
|
||||
{ type: "text", name: "title", label: "Title" },
|
||||
{ type: "text", name: "label", label: "Label", optional: true },
|
||||
{ type: "text", name: "defaultValue", label: "Default Value", optional: true },
|
||||
{ type: "text", name: "placeholder", label: "Placeholder", optional: true }
|
||||
],
|
||||
async onRender(ctx, args) {
|
||||
console.log("PROMPT", args);
|
||||
if (args.purpose !== "send") return null;
|
||||
const value = await ctx.prompt.text({
|
||||
return await ctx.prompt.text({
|
||||
id: `prompt-${args.values.label}`,
|
||||
label: args.values.label ?? "",
|
||||
label: args.values.title ?? "",
|
||||
title: args.values.title ?? "",
|
||||
defaultValue: args.values.defaultValue,
|
||||
placeholder: args.values.placeholder
|
||||
});
|
||||
console.log("VALUE", value);
|
||||
return value;
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
@@ -8837,6 +8837,7 @@ var plugin = {
|
||||
templateFunctions: [
|
||||
{
|
||||
name: "response.header",
|
||||
description: "Read the value of a response header, by name",
|
||||
args: [
|
||||
requestArg,
|
||||
{
|
||||
@@ -8863,6 +8864,7 @@ var plugin = {
|
||||
},
|
||||
{
|
||||
name: "response.body.path",
|
||||
description: "Access a field of the response body using JsonPath or XPath",
|
||||
aliases: ["response"],
|
||||
args: [
|
||||
requestArg,
|
||||
@@ -8901,6 +8903,34 @@ var plugin = {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "response.body.raw",
|
||||
description: "Access the entire response body, as text",
|
||||
aliases: ["response"],
|
||||
args: [
|
||||
requestArg,
|
||||
behaviorArg
|
||||
],
|
||||
async onRender(ctx, args) {
|
||||
if (!args.values.request) return null;
|
||||
const response = await getResponse(ctx, {
|
||||
requestId: args.values.request,
|
||||
purpose: args.purpose,
|
||||
behavior: args.values.behavior ?? null
|
||||
});
|
||||
if (response == null) return null;
|
||||
if (response.bodyPath == null) {
|
||||
return null;
|
||||
}
|
||||
let body;
|
||||
try {
|
||||
body = (0, import_node_fs.readFileSync)(response.bodyPath, "utf-8");
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ pub struct GrpcConnection {
|
||||
pub uri: Uri,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct StreamError {
|
||||
pub message: String,
|
||||
pub status: Option<Status>,
|
||||
@@ -234,7 +234,7 @@ impl GrpcHandle {
|
||||
&pool,
|
||||
input_message,
|
||||
))
|
||||
.unwrap(),
|
||||
.unwrap(),
|
||||
})
|
||||
}
|
||||
def
|
||||
@@ -301,4 +301,4 @@ fn make_pool_key(id: &str, uri: &str, proto_files: &Vec<PathBuf>) -> String {
|
||||
);
|
||||
|
||||
format!("{:x}", md5::compute(pool_key))
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,7 @@ async fn file_descriptor_set_from_service_name(
|
||||
client,
|
||||
MessageRequest::FileContainingSymbol(service_name.into()),
|
||||
)
|
||||
.await
|
||||
.await
|
||||
{
|
||||
Ok(resp) => resp,
|
||||
Err(e) => {
|
||||
@@ -249,4 +249,4 @@ 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")
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,10 @@ export type KeyValue = { model: "key_value", createdAt: string, updatedAt: strin
|
||||
|
||||
export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt: string, checkedAt: string | null, directory: string, enabled: boolean, url: string | null, };
|
||||
|
||||
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, telemetry: boolean, theme: string, themeDark: string, themeLight: string, updateChannel: string, };
|
||||
export type ProxySetting = { "type": "enabled", http: string, https: string, auth: ProxySettingAuth | null, } | { "type": "disabled" };
|
||||
|
||||
export type ProxySettingAuth = { user: string, password: string, };
|
||||
|
||||
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, telemetry: boolean, theme: string, themeDark: string, themeLight: string, updateChannel: string, proxy: ProxySetting | null, };
|
||||
|
||||
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, variables: Array<EnvironmentVariable>, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };
|
||||
|
||||
@@ -6,6 +6,26 @@ use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
pub enum ProxySetting {
|
||||
Enabled {
|
||||
http: String,
|
||||
https: String,
|
||||
auth: Option<ProxySettingAuth>,
|
||||
},
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
pub struct ProxySettingAuth {
|
||||
pub user: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
@@ -27,6 +47,7 @@ pub struct Settings {
|
||||
pub theme_dark: String,
|
||||
pub theme_light: String,
|
||||
pub update_channel: String,
|
||||
pub proxy: Option<ProxySetting>,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
@@ -44,6 +65,7 @@ pub enum SettingsIden {
|
||||
InterfaceFontSize,
|
||||
InterfaceScale,
|
||||
OpenWorkspaceNewWindow,
|
||||
Proxy,
|
||||
Telemetry,
|
||||
Theme,
|
||||
ThemeDark,
|
||||
@@ -55,22 +77,24 @@ impl<'s> TryFrom<&Row<'s>> for Settings {
|
||||
type Error = rusqlite::Error;
|
||||
|
||||
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
|
||||
let proxy: Option<String> = r.get("proxy")?;
|
||||
Ok(Settings {
|
||||
id: r.get("id")?,
|
||||
model: r.get("model")?,
|
||||
created_at: r.get("created_at")?,
|
||||
updated_at: r.get("updated_at")?,
|
||||
theme: r.get("theme")?,
|
||||
appearance: r.get("appearance")?,
|
||||
editor_font_size: r.get("editor_font_size")?,
|
||||
editor_soft_wrap: r.get("editor_soft_wrap")?,
|
||||
interface_font_size: r.get("interface_font_size")?,
|
||||
interface_scale: r.get("interface_scale")?,
|
||||
open_workspace_new_window: r.get("open_workspace_new_window")?,
|
||||
proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }),
|
||||
telemetry: r.get("telemetry")?,
|
||||
theme: r.get("theme")?,
|
||||
theme_dark: r.get("theme_dark")?,
|
||||
theme_light: r.get("theme_light")?,
|
||||
update_channel: r.get("update_channel")?,
|
||||
interface_font_size: r.get("interface_font_size")?,
|
||||
interface_scale: r.get("interface_scale")?,
|
||||
editor_font_size: r.get("editor_font_size")?,
|
||||
editor_soft_wrap: r.get("editor_soft_wrap")?,
|
||||
telemetry: r.get("telemetry")?,
|
||||
open_workspace_new_window: r.get("open_workspace_new_window")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -431,7 +455,7 @@ pub struct HttpResponseHeader {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde( rename_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
pub enum HttpResponseState {
|
||||
Initialized,
|
||||
@@ -618,7 +642,7 @@ impl<'s> TryFrom<&Row<'s>> for GrpcRequest {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde( rename_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
pub enum GrpcConnectionState {
|
||||
Initialized,
|
||||
@@ -626,7 +650,7 @@ pub enum GrpcConnectionState {
|
||||
Closed,
|
||||
}
|
||||
|
||||
impl Default for GrpcConnectionState{
|
||||
impl Default for GrpcConnectionState {
|
||||
fn default() -> Self {
|
||||
Self::Initialized
|
||||
}
|
||||
@@ -911,7 +935,7 @@ impl ModelType {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
#[ts(export, export_to="models.ts")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
pub enum AnyModel {
|
||||
CookieJar(CookieJar),
|
||||
Environment(Environment),
|
||||
|
||||
@@ -11,6 +11,7 @@ use crate::models::{
|
||||
use crate::plugin::SqliteConnection;
|
||||
use log::{debug, error};
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
use rusqlite::OptionalExtension;
|
||||
use sea_query::ColumnRef::Asterisk;
|
||||
use sea_query::Keyword::CurrentTimestamp;
|
||||
use sea_query::{Cond, Expr, OnConflict, Order, Query, SqliteQueryBuilder};
|
||||
@@ -117,9 +118,7 @@ pub async fn set_key_value_raw<R: Runtime>(
|
||||
.returning_all()
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
|
||||
let mut stmt = db
|
||||
.prepare(sql.as_str())
|
||||
.expect("Failed to prepare KeyValue upsert");
|
||||
let mut stmt = db.prepare(sql.as_str()).expect("Failed to prepare KeyValue upsert");
|
||||
let kv = stmt
|
||||
.query_row(&*params.as_params(), |row| row.try_into())
|
||||
.expect("Failed to upsert KeyValue");
|
||||
@@ -143,8 +142,7 @@ pub async fn get_key_value_raw<R: Runtime>(
|
||||
)
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
|
||||
db.query_row(sql.as_str(), &*params.as_params(), |row| row.try_into())
|
||||
.ok()
|
||||
db.query_row(sql.as_str(), &*params.as_params(), |row| row.try_into()).ok()
|
||||
}
|
||||
|
||||
pub async fn list_workspaces<R: Runtime>(mgr: &impl Manager<R>) -> Result<Vec<Workspace>> {
|
||||
@@ -365,11 +363,7 @@ pub async fn upsert_grpc_request<R: Runtime>(
|
||||
request.service.as_ref().map(|s| s.as_str()).into(),
|
||||
request.method.as_ref().map(|s| s.as_str()).into(),
|
||||
request.message.as_str().into(),
|
||||
request
|
||||
.authentication_type
|
||||
.as_ref()
|
||||
.map(|s| s.as_str())
|
||||
.into(),
|
||||
request.authentication_type.as_ref().map(|s| s.as_str()).into(),
|
||||
serde_json::to_string(&request.authentication)?.into(),
|
||||
serde_json::to_string(&request.metadata)?.into(),
|
||||
])
|
||||
@@ -434,10 +428,7 @@ pub async fn upsert_grpc_connection<R: Runtime>(
|
||||
) -> Result<GrpcConnection> {
|
||||
let connections =
|
||||
list_http_responses_for_request(window, connection.request_id.as_str(), None).await?;
|
||||
for c in connections
|
||||
.iter()
|
||||
.skip(MAX_GRPC_CONNECTIONS_PER_REQUEST - 1)
|
||||
{
|
||||
for c in connections.iter().skip(MAX_GRPC_CONNECTIONS_PER_REQUEST - 1) {
|
||||
debug!("Deleting old grpc connection {}", c.id);
|
||||
delete_grpc_connection(window, c.id.as_str()).await?;
|
||||
}
|
||||
@@ -488,6 +479,7 @@ pub async fn upsert_grpc_connection<R: Runtime>(
|
||||
GrpcConnectionIden::Method,
|
||||
GrpcConnectionIden::Elapsed,
|
||||
GrpcConnectionIden::Status,
|
||||
GrpcConnectionIden::State,
|
||||
GrpcConnectionIden::Error,
|
||||
GrpcConnectionIden::Trailers,
|
||||
GrpcConnectionIden::Url,
|
||||
@@ -663,7 +655,7 @@ pub async fn list_grpc_events<R: Runtime>(
|
||||
.from(GrpcEventIden::Table)
|
||||
.cond_where(Expr::col(GrpcEventIden::ConnectionId).eq(connection_id))
|
||||
.column(Asterisk)
|
||||
.order_by(GrpcEventIden::CreatedAt, Order::Desc)
|
||||
.order_by(GrpcEventIden::CreatedAt, Order::Asc)
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
let mut stmt = db.prepare(sql.as_str())?;
|
||||
let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?;
|
||||
@@ -756,7 +748,7 @@ pub async fn delete_environment<R: Runtime>(
|
||||
|
||||
const SETTINGS_ID: &str = "default";
|
||||
|
||||
async fn get_settings<R: Runtime>(mgr: &impl Manager<R>) -> Result<Settings> {
|
||||
async fn get_settings<R: Runtime>(mgr: &impl Manager<R>) -> Result<Option<Settings>> {
|
||||
let dbm = &*mgr.state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
|
||||
@@ -766,13 +758,15 @@ async fn get_settings<R: Runtime>(mgr: &impl Manager<R>) -> Result<Settings> {
|
||||
.cond_where(Expr::col(SettingsIden::Id).eq(SETTINGS_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 get_or_create_settings<R: Runtime>(mgr: &impl Manager<R>) -> Settings {
|
||||
if let Ok(settings) = get_settings(mgr).await {
|
||||
return settings;
|
||||
}
|
||||
match get_settings(mgr).await {
|
||||
Ok(Some(settings)) => return settings,
|
||||
Ok(None) => (),
|
||||
Err(e) => panic!("Failed to get settings {e:?}"),
|
||||
};
|
||||
|
||||
let dbm = &*mgr.state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
@@ -784,11 +778,8 @@ pub async fn get_or_create_settings<R: Runtime>(mgr: &impl Manager<R>) -> Settin
|
||||
.returning_all()
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
|
||||
let mut stmt = db
|
||||
.prepare(sql.as_str())
|
||||
.expect("Failed to prepare Settings insert");
|
||||
stmt.query_row(&*params.as_params(), |row| row.try_into())
|
||||
.expect("Failed to insert Settings")
|
||||
let mut stmt = db.prepare(sql.as_str()).expect("Failed to prepare Settings insert");
|
||||
stmt.query_row(&*params.as_params(), |row| row.try_into()).expect("Failed to insert Settings")
|
||||
}
|
||||
|
||||
pub async fn update_settings<R: Runtime>(
|
||||
@@ -804,39 +795,23 @@ pub async fn update_settings<R: Runtime>(
|
||||
.values([
|
||||
(SettingsIden::Id, "default".into()),
|
||||
(SettingsIden::CreatedAt, CurrentTimestamp.into()),
|
||||
(
|
||||
SettingsIden::Appearance,
|
||||
settings.appearance.as_str().into(),
|
||||
),
|
||||
(SettingsIden::Appearance, settings.appearance.as_str().into()),
|
||||
(SettingsIden::ThemeDark, settings.theme_dark.as_str().into()),
|
||||
(
|
||||
SettingsIden::ThemeLight,
|
||||
settings.theme_light.as_str().into(),
|
||||
),
|
||||
(
|
||||
SettingsIden::UpdateChannel,
|
||||
settings.update_channel.as_str().into(),
|
||||
),
|
||||
(
|
||||
SettingsIden::InterfaceFontSize,
|
||||
settings.interface_font_size.into(),
|
||||
),
|
||||
(
|
||||
SettingsIden::InterfaceScale,
|
||||
settings.interface_scale.into(),
|
||||
),
|
||||
(
|
||||
SettingsIden::EditorFontSize,
|
||||
settings.editor_font_size.into(),
|
||||
),
|
||||
(
|
||||
SettingsIden::EditorSoftWrap,
|
||||
settings.editor_soft_wrap.into(),
|
||||
),
|
||||
(SettingsIden::ThemeLight, settings.theme_light.as_str().into()),
|
||||
(SettingsIden::UpdateChannel, settings.update_channel.into()),
|
||||
(SettingsIden::InterfaceFontSize, settings.interface_font_size.into()),
|
||||
(SettingsIden::InterfaceScale, settings.interface_scale.into()),
|
||||
(SettingsIden::EditorFontSize, settings.editor_font_size.into()),
|
||||
(SettingsIden::EditorSoftWrap, settings.editor_soft_wrap.into()),
|
||||
(SettingsIden::Telemetry, settings.telemetry.into()),
|
||||
(SettingsIden::OpenWorkspaceNewWindow, settings.open_workspace_new_window.into()),
|
||||
(
|
||||
SettingsIden::OpenWorkspaceNewWindow,
|
||||
settings.open_workspace_new_window.into(),
|
||||
SettingsIden::Proxy,
|
||||
(match settings.proxy {
|
||||
None => None,
|
||||
Some(p) => Some(serde_json::to_string(&p)?),
|
||||
})
|
||||
.into(),
|
||||
),
|
||||
])
|
||||
.returning_all()
|
||||
@@ -1307,10 +1282,7 @@ pub async fn create_http_response<R: Runtime>(
|
||||
elapsed.into(),
|
||||
elapsed_headers.into(),
|
||||
url.into(),
|
||||
serde_json::to_value(state)?
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.into(),
|
||||
serde_json::to_value(state)?.as_str().unwrap_or_default().into(),
|
||||
status.into(),
|
||||
status_reason.into(),
|
||||
content_length.into(),
|
||||
@@ -1390,32 +1362,15 @@ pub async fn update_http_response<R: Runtime>(
|
||||
HttpResponseIden::StatusReason,
|
||||
response.status_reason.as_ref().map(|s| s.as_str()).into(),
|
||||
),
|
||||
(
|
||||
HttpResponseIden::ContentLength,
|
||||
response.content_length.into(),
|
||||
),
|
||||
(
|
||||
HttpResponseIden::BodyPath,
|
||||
response.body_path.as_ref().map(|s| s.as_str()).into(),
|
||||
),
|
||||
(
|
||||
HttpResponseIden::Error,
|
||||
response.error.as_ref().map(|s| s.as_str()).into(),
|
||||
),
|
||||
(HttpResponseIden::ContentLength, response.content_length.into()),
|
||||
(HttpResponseIden::BodyPath, response.body_path.as_ref().map(|s| s.as_str()).into()),
|
||||
(HttpResponseIden::Error, response.error.as_ref().map(|s| s.as_str()).into()),
|
||||
(
|
||||
HttpResponseIden::Headers,
|
||||
serde_json::to_string(&response.headers)
|
||||
.unwrap_or_default()
|
||||
.into(),
|
||||
),
|
||||
(
|
||||
HttpResponseIden::Version,
|
||||
response.version.as_ref().map(|s| s.as_str()).into(),
|
||||
),
|
||||
(
|
||||
HttpResponseIden::State,
|
||||
serde_json::to_value(&response.state)?.as_str().into(),
|
||||
serde_json::to_string(&response.headers).unwrap_or_default().into(),
|
||||
),
|
||||
(HttpResponseIden::Version, response.version.as_ref().map(|s| s.as_str()).into()),
|
||||
(HttpResponseIden::State, serde_json::to_value(&response.state)?.as_str().into()),
|
||||
(
|
||||
HttpResponseIden::RemoteAddr,
|
||||
response.remote_addr.as_ref().map(|s| s.as_str()).into(),
|
||||
|
||||
9
src-tauri/yaak_sse/Cargo.toml
Normal file
9
src-tauri/yaak_sse/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "yaak_sse"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
serde_json = "1.0.122"
|
||||
ts-rs = { version = "10.0.0", features = ["serde-json-impl"] }
|
||||
3
src-tauri/yaak_sse/bindings/sse.ts
Normal file
3
src-tauri/yaak_sse/bindings/sse.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ServerSentEvent = { eventType: string, data: string, id: string | null, retry: bigint | null, };
|
||||
1
src-tauri/yaak_sse/index.ts
Normal file
1
src-tauri/yaak_sse/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './bindings/sse';
|
||||
6
src-tauri/yaak_sse/package.json
Normal file
6
src-tauri/yaak_sse/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "@yaakapp-internal/sse",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"main": "index.ts"
|
||||
}
|
||||
1
src-tauri/yaak_sse/src/lib.rs
Normal file
1
src-tauri/yaak_sse/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod sse;
|
||||
12
src-tauri/yaak_sse/src/sse.rs
Normal file
12
src-tauri/yaak_sse/src/sse.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "sse.ts")]
|
||||
pub struct ServerSentEvent {
|
||||
pub event_type: String,
|
||||
pub data: String,
|
||||
pub id: Option<String>,
|
||||
pub retry: Option<u64>,
|
||||
}
|
||||
@@ -24,7 +24,7 @@ const queryClient = new QueryClient({
|
||||
},
|
||||
});
|
||||
|
||||
const ENABLE_REACT_QUERY_DEVTOOLS = true;
|
||||
const ENABLE_REACT_QUERY_DEVTOOLS = false;
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { lazy } from 'react';
|
||||
import { createBrowserRouter, Navigate, RouterProvider, useParams } from 'react-router-dom';
|
||||
import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
|
||||
import { useGenerateThemeCss } from '../hooks/useGenerateThemeCss';
|
||||
import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting';
|
||||
import { useSyncModelStores } from '../hooks/useSyncModelStores';
|
||||
import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting';
|
||||
import { DefaultLayout } from './DefaultLayout';
|
||||
import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace';
|
||||
import RouteError from './RouteError';
|
||||
@@ -54,12 +50,6 @@ const router = createBrowserRouter([
|
||||
]);
|
||||
|
||||
export function AppRouter() {
|
||||
// Add some global hooks that should remain persistent
|
||||
useSyncModelStores();
|
||||
useSyncZoomSetting();
|
||||
useSyncFontSizeSetting();
|
||||
useGenerateThemeCss();
|
||||
|
||||
return <RouterProvider router={router} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import { search } from 'fast-fuzzy';
|
||||
import { fuzzyFilter } from 'fuzzbunny';
|
||||
import type { KeyboardEvent, ReactNode } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||
@@ -328,15 +328,21 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
|
||||
|
||||
const { filteredGroups, filteredAllItems } = useMemo(() => {
|
||||
const result = command
|
||||
? search(command, allItems, {
|
||||
threshold: 0.5,
|
||||
keySelector: (v) => ('searchText' in v ? v.searchText : v.label),
|
||||
})
|
||||
? fuzzyFilter(
|
||||
allItems.map((i) => ({
|
||||
...i,
|
||||
filterBy: 'searchText' in i ? i.searchText : i.label,
|
||||
})),
|
||||
command,
|
||||
{ fields: ['filterBy'] },
|
||||
).map((v) => v.item)
|
||||
: allItems;
|
||||
|
||||
const filteredGroups = groups
|
||||
.map((g) => {
|
||||
g.items = result.filter((i) => g.items.includes(i)).slice(0, MAX_PER_GROUP);
|
||||
g.items = result
|
||||
.filter((i) => g.items.find((i2) => i2.key === i.key))
|
||||
.slice(0, MAX_PER_GROUP);
|
||||
return g;
|
||||
})
|
||||
.filter((g) => g.items.length > 0);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { emit } from '@tauri-apps/api/event';
|
||||
import type { PromptTextRequest, PromptTextResponse } from '@yaakapp-internal/plugin';
|
||||
import { useEnsureActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||
import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast';
|
||||
import {useGenerateThemeCss} from "../hooks/useGenerateThemeCss";
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||
import { useNotificationToast } from '../hooks/useNotificationToast';
|
||||
@@ -10,10 +11,18 @@ import { useRecentCookieJars } from '../hooks/useRecentCookieJars';
|
||||
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||
import {useSyncFontSizeSetting} from "../hooks/useSyncFontSizeSetting";
|
||||
import {useSyncModelStores} from "../hooks/useSyncModelStores";
|
||||
import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels';
|
||||
import {useSyncZoomSetting} from "../hooks/useSyncZoomSetting";
|
||||
import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette';
|
||||
|
||||
export function GlobalHooks() {
|
||||
useSyncModelStores();
|
||||
useSyncZoomSetting();
|
||||
useSyncFontSizeSetting();
|
||||
useGenerateThemeCss();
|
||||
|
||||
// Include here so they always update, even if no component references them
|
||||
useRecentWorkspaces();
|
||||
useRecentEnvironments();
|
||||
|
||||
@@ -188,7 +188,7 @@ function EventRow({
|
||||
className={classNames(
|
||||
'w-full grid grid-cols-[auto_minmax(0,3fr)_auto] gap-2 items-center text-left',
|
||||
'px-1.5 py-1 font-mono cursor-default group focus:outline-none rounded',
|
||||
isActive && '!bg-surface-highlight !text',
|
||||
isActive && '!bg-surface-highlight !text-text',
|
||||
'text-text-subtle hover:text',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { ResponseHeaders } from './ResponseHeaders';
|
||||
import { ResponseInfo } from './ResponseInfo';
|
||||
import { AudioViewer } from './responseViewers/AudioViewer';
|
||||
import { CsvViewer } from './responseViewers/CsvViewer';
|
||||
import { EventStreamViewer } from './responseViewers/EventStreamViewer';
|
||||
import { HTMLOrTextViewer } from './responseViewers/HTMLOrTextViewer';
|
||||
import { ImageViewer } from './responseViewers/ImageViewer';
|
||||
import { PdfViewer } from './responseViewers/PdfViewer';
|
||||
@@ -163,15 +164,17 @@ export const ResponsePane = memo(function ResponsePane({ style, className, activ
|
||||
<div className="pb-2 h-full">
|
||||
<EmptyStateText>Empty Body</EmptyStateText>
|
||||
</div>
|
||||
) : contentType?.startsWith('image') ? (
|
||||
) : contentType?.match(/^text\/event-stream$/i) && viewMode === 'pretty' ? (
|
||||
<EventStreamViewer response={activeResponse} />
|
||||
) : contentType?.match(/^image/i) ? (
|
||||
<EnsureCompleteResponse response={activeResponse} render={ImageViewer} />
|
||||
) : contentType?.startsWith('audio') ? (
|
||||
) : contentType?.match(/^audio/i) ? (
|
||||
<EnsureCompleteResponse response={activeResponse} render={AudioViewer} />
|
||||
) : contentType?.startsWith('video') ? (
|
||||
) : contentType?.match(/^video/i) ? (
|
||||
<EnsureCompleteResponse response={activeResponse} render={VideoViewer} />
|
||||
) : contentType?.match(/pdf/) ? (
|
||||
) : contentType?.match(/pdf/i) ? (
|
||||
<EnsureCompleteResponse response={activeResponse} render={PdfViewer} />
|
||||
) : contentType?.match(/csv|tab-separated/) ? (
|
||||
) : contentType?.match(/csv|tab-separated/i) ? (
|
||||
<CsvViewer className="pb-2" response={activeResponse} />
|
||||
) : (
|
||||
// ) : viewMode === 'pretty' && contentType?.includes('json') ? (
|
||||
|
||||
@@ -10,6 +10,7 @@ import { HeaderSize } from '../HeaderSize';
|
||||
import { SettingsAppearance } from './SettingsAppearance';
|
||||
import { SettingsGeneral } from './SettingsGeneral';
|
||||
import { SettingsPlugins } from './SettingsPlugins';
|
||||
import {SettingsProxy} from "./SettingsProxy";
|
||||
|
||||
interface Props {
|
||||
hide?: () => void;
|
||||
@@ -17,11 +18,12 @@ interface Props {
|
||||
|
||||
enum Tab {
|
||||
General = 'general',
|
||||
Proxy = 'proxy',
|
||||
Appearance = 'appearance',
|
||||
Plugins = 'plugins',
|
||||
}
|
||||
|
||||
const tabs = [Tab.General, Tab.Appearance, Tab.Plugins];
|
||||
const tabs = [Tab.General, Tab.Appearance, Tab.Proxy, Tab.Plugins];
|
||||
|
||||
export default function Settings({ hide }: Props) {
|
||||
const osInfo = useOsInfo();
|
||||
@@ -78,6 +80,9 @@ export default function Settings({ hide }: Props) {
|
||||
<TabContent value={Tab.Plugins} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<SettingsPlugins />
|
||||
</TabContent>
|
||||
<TabContent value={Tab.Proxy} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<SettingsProxy />
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -27,12 +27,13 @@ export function SettingsGeneral() {
|
||||
}
|
||||
|
||||
return (
|
||||
<VStack space={2} className="mb-4">
|
||||
<VStack space={1.5} className="mb-4">
|
||||
<div className="grid grid-cols-[minmax(0,1fr)_auto] gap-1">
|
||||
<Select
|
||||
name="updateChannel"
|
||||
label="Update Channel"
|
||||
labelPosition="left"
|
||||
labelClassName="w-[12rem]"
|
||||
size="sm"
|
||||
value={settings.updateChannel}
|
||||
onChange={(updateChannel) => updateSettings.mutate({ updateChannel })}
|
||||
@@ -54,22 +55,19 @@ export function SettingsGeneral() {
|
||||
name="openWorkspace"
|
||||
label="Open Workspace"
|
||||
labelPosition="left"
|
||||
labelClassName="w-[12rem]"
|
||||
size="sm"
|
||||
value={
|
||||
settings.openWorkspaceNewWindow === true
|
||||
? 'new'
|
||||
: settings.openWorkspaceNewWindow === false
|
||||
? 'current'
|
||||
: 'ask'
|
||||
? 'current'
|
||||
: 'ask'
|
||||
}
|
||||
onChange={(v) => {
|
||||
if (v === 'current') {
|
||||
updateSettings.mutate({ openWorkspaceNewWindow: false });
|
||||
} else if (v === 'new') {
|
||||
updateSettings.mutate({ openWorkspaceNewWindow: true });
|
||||
} else {
|
||||
updateSettings.mutate({ openWorkspaceNewWindow: null });
|
||||
}
|
||||
if (v === 'current') updateSettings.mutate({ openWorkspaceNewWindow: false });
|
||||
else if (v === 'new') updateSettings.mutate({ openWorkspaceNewWindow: true });
|
||||
else updateSettings.mutate({ openWorkspaceNewWindow: null });
|
||||
}}
|
||||
options={[
|
||||
{ label: 'Always Ask', value: 'ask' },
|
||||
@@ -77,7 +75,9 @@ export function SettingsGeneral() {
|
||||
{ label: 'New Window', value: 'new' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
className="mt-3"
|
||||
checked={settings.telemetry}
|
||||
title="Send Usage Statistics"
|
||||
onChange={(telemetry) => updateSettings.mutate({ telemetry })}
|
||||
|
||||
119
src-web/components/Settings/SettingsProxy.tsx
Normal file
119
src-web/components/Settings/SettingsProxy.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import React from 'react';
|
||||
import { useSettings } from '../../hooks/useSettings';
|
||||
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
|
||||
import { Checkbox } from '../core/Checkbox';
|
||||
import { PlainInput } from '../core/PlainInput';
|
||||
import { Select } from '../core/Select';
|
||||
import { Separator } from '../core/Separator';
|
||||
import { HStack, VStack } from '../core/Stacks';
|
||||
|
||||
export function SettingsProxy() {
|
||||
const settings = useSettings();
|
||||
const updateSettings = useUpdateSettings();
|
||||
|
||||
return (
|
||||
<VStack space={1.5} className="mb-4">
|
||||
<Select
|
||||
name="proxy"
|
||||
label="Proxy"
|
||||
hideLabel
|
||||
size="sm"
|
||||
value={settings.proxy?.type ?? 'automatic'}
|
||||
onChange={(v) => {
|
||||
if (v === 'automatic') {
|
||||
updateSettings.mutate({ proxy: undefined });
|
||||
} else if (v === 'enabled') {
|
||||
updateSettings.mutate({
|
||||
proxy: {
|
||||
type: 'enabled',
|
||||
http: '',
|
||||
https: '',
|
||||
auth: { user: '', password: '' },
|
||||
},
|
||||
});
|
||||
} else {
|
||||
updateSettings.mutate({ proxy: { type: 'disabled' } });
|
||||
}
|
||||
}}
|
||||
options={[
|
||||
{ label: 'Automatic Proxy Detection', value: 'automatic' },
|
||||
{ label: 'Custom Proxy Configuration', value: 'enabled' },
|
||||
{ label: 'No Proxy', value: 'disabled' },
|
||||
]}
|
||||
/>
|
||||
{settings.proxy?.type === 'enabled' && (
|
||||
<VStack space={1.5}>
|
||||
<HStack space={1.5} className="mt-3">
|
||||
<PlainInput
|
||||
size="sm"
|
||||
label="HTTP"
|
||||
placeholder="localhost:9090"
|
||||
defaultValue={settings.proxy?.http}
|
||||
onChange={(http) => {
|
||||
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
||||
const auth = settings.proxy?.type === 'enabled' ? settings.proxy.auth : null;
|
||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
||||
}}
|
||||
/>
|
||||
<PlainInput
|
||||
size="sm"
|
||||
label="HTTPS"
|
||||
placeholder="localhost:9090"
|
||||
defaultValue={settings.proxy?.https}
|
||||
onChange={(https) => {
|
||||
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
||||
const auth = settings.proxy?.type === 'enabled' ? settings.proxy.auth : null;
|
||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
<Separator className="my-6"/>
|
||||
<Checkbox
|
||||
checked={settings.proxy.auth != null}
|
||||
title="Enable authentication"
|
||||
onChange={(enabled) => {
|
||||
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
||||
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
||||
const auth = enabled ? { user: '', password: '' } : null;
|
||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
||||
}}
|
||||
/>
|
||||
|
||||
{settings.proxy.auth != null && (
|
||||
<HStack space={1.5}>
|
||||
<PlainInput
|
||||
size="sm"
|
||||
label="User"
|
||||
placeholder="myUser"
|
||||
defaultValue={settings.proxy.auth.user}
|
||||
onChange={(user) => {
|
||||
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
||||
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
||||
const password =
|
||||
settings.proxy?.type === 'enabled' ? (settings.proxy.auth?.password ?? '') : '';
|
||||
const auth = { user, password };
|
||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
||||
}}
|
||||
/>
|
||||
<PlainInput
|
||||
size="sm"
|
||||
label="Password"
|
||||
type="password"
|
||||
placeholder="s3cretPassw0rd"
|
||||
defaultValue={settings.proxy.auth.password}
|
||||
onChange={(password) => {
|
||||
const https = settings.proxy?.type === 'enabled' ? settings.proxy.https : '';
|
||||
const http = settings.proxy?.type === 'enabled' ? settings.proxy.http : '';
|
||||
const user =
|
||||
settings.proxy?.type === 'enabled' ? (settings.proxy.auth?.user ?? '') : '';
|
||||
const auth = { user, password };
|
||||
updateSettings.mutate({ proxy: { type: 'enabled', http, https, auth } });
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
)}
|
||||
</VStack>
|
||||
)}
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
@@ -702,11 +702,11 @@ function SidebarItem({
|
||||
useScrollIntoView(ref.current, isActive);
|
||||
|
||||
const handleSubmitNameEdit = useCallback(
|
||||
(el: HTMLInputElement) => {
|
||||
async (el: HTMLInputElement) => {
|
||||
if (itemModel === 'http_request') {
|
||||
updateHttpRequest.mutate({ id: itemId, update: (r) => ({ ...r, name: el.value }) });
|
||||
await updateHttpRequest.mutateAsync({ id: itemId, update: (r) => ({ ...r, name: el.value }) });
|
||||
} else if (itemModel === 'grpc_request') {
|
||||
updateGrpcRequest.mutate({ id: itemId, update: (r) => ({ ...r, name: el.value }) });
|
||||
await updateGrpcRequest.mutateAsync({ id: itemId, update: (r) => ({ ...r, name: el.value }) });
|
||||
}
|
||||
setEditing(false);
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ export function Banner({ children, className, color = 'secondary' }: Props) {
|
||||
className={classNames(
|
||||
className,
|
||||
`x-theme-banner--${color}`,
|
||||
'whitespace-pre-wrap',
|
||||
'border border-dashed border-border-subtle bg-surface',
|
||||
'italic px-3 py-2 rounded select-auto cursor-text',
|
||||
'overflow-x-auto text-text',
|
||||
|
||||
@@ -8,12 +8,12 @@ import classNames from 'classnames';
|
||||
import { EditorView } from 'codemirror';
|
||||
import type { MutableRefObject, ReactNode } from 'react';
|
||||
import {
|
||||
useEffect,
|
||||
Children,
|
||||
cloneElement,
|
||||
forwardRef,
|
||||
isValidElement,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
@@ -343,6 +343,33 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
[forceUpdateKey],
|
||||
);
|
||||
|
||||
// For read-only mode, update content when `defaultValue` changes
|
||||
useEffect(() => {
|
||||
if (!readOnly || cm.current?.view == null || defaultValue == null) return;
|
||||
|
||||
// Replace codemirror contents
|
||||
const currentDoc = cm.current.view.state.doc.toString();
|
||||
if (defaultValue.startsWith(currentDoc)) {
|
||||
// If we're just appending, append only the changes. This preserves
|
||||
// things like scroll position.
|
||||
cm.current.view.dispatch({
|
||||
changes: cm.current.view.state.changes({
|
||||
from: currentDoc.length,
|
||||
insert: defaultValue.slice(currentDoc.length),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
// If we're replacing everything, reset the entire content
|
||||
cm.current.view.dispatch({
|
||||
changes: cm.current.view.state.changes({
|
||||
from: 0,
|
||||
to: currentDoc.length,
|
||||
insert: currentDoc,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}, [defaultValue, readOnly]);
|
||||
|
||||
// Add bg classes to actions, so they appear over the text
|
||||
const decoratedActions = useMemo(() => {
|
||||
const results = [];
|
||||
|
||||
@@ -6,7 +6,6 @@ export function InlineCode({ className, ...props }: HTMLAttributes<HTMLSpanEleme
|
||||
<code
|
||||
className={classNames(
|
||||
className,
|
||||
'select-text cursor-text',
|
||||
'font-mono text-shrink bg-surface-highlight border border-border-subtle',
|
||||
'px-1.5 py-0.5 rounded text shadow-inner break-words',
|
||||
)}
|
||||
|
||||
@@ -93,7 +93,7 @@ export const PlainInput = forwardRef<HTMLInputElement, PlainInputProps>(function
|
||||
htmlFor={id}
|
||||
className={classNames(
|
||||
labelClassName,
|
||||
'text-text-subtle whitespace-nowrap',
|
||||
'text-text-subtle whitespace-nowrap flex-shrink-0',
|
||||
hideLabel && 'sr-only',
|
||||
)}
|
||||
>
|
||||
@@ -128,6 +128,9 @@ export const PlainInput = forwardRef<HTMLInputElement, PlainInputProps>(function
|
||||
type={type === 'password' && !obscured ? 'text' : type}
|
||||
defaultValue={defaultValue}
|
||||
placeholder={placeholder}
|
||||
autoComplete="off"
|
||||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
onChange={(e) => handleChange(e.target.value)}
|
||||
onPaste={(e) => onPaste?.(e.clipboardData.getData('Text'))}
|
||||
className={inputClassName}
|
||||
|
||||
204
src-web/components/responseViewers/EventStreamViewer.tsx
Normal file
204
src-web/components/responseViewers/EventStreamViewer.tsx
Normal file
@@ -0,0 +1,204 @@
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import type { HttpResponse } from '@yaakapp-internal/models';
|
||||
import type { ServerSentEvent } from '@yaakapp-internal/sse';
|
||||
import classNames from 'classnames';
|
||||
import { motion } from 'framer-motion';
|
||||
import React, { Fragment, useMemo, useRef, useState } from 'react';
|
||||
import { useResponseBodyEventSource } from '../../hooks/useResponseBodyEventSource';
|
||||
import { isJSON } from '../../lib/contentType';
|
||||
import { tryFormatJson } from '../../lib/formatters';
|
||||
import { Button } from '../core/Button';
|
||||
import { Editor } from '../core/Editor';
|
||||
import { Icon } from '../core/Icon';
|
||||
import { InlineCode } from '../core/InlineCode';
|
||||
import { Separator } from '../core/Separator';
|
||||
import { SplitLayout } from '../core/SplitLayout';
|
||||
import { HStack, VStack } from '../core/Stacks';
|
||||
|
||||
interface Props {
|
||||
response: HttpResponse;
|
||||
}
|
||||
|
||||
export function EventStreamViewer({ response }: Props) {
|
||||
return (
|
||||
<Fragment
|
||||
key={response.id} // force a refresh when the response changes
|
||||
>
|
||||
<ActualEventStreamViewer response={response} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
function ActualEventStreamViewer({ response }: Props) {
|
||||
const [showLarge, setShowLarge] = useState<boolean>(false);
|
||||
const [showingLarge, setShowingLarge] = useState<boolean>(false);
|
||||
const [activeEventIndex, setActiveEventIndex] = useState<number | null>(null);
|
||||
const events = useResponseBodyEventSource(response);
|
||||
const activeEvent = useMemo(
|
||||
() => (activeEventIndex == null ? null : events.data?.[activeEventIndex]),
|
||||
[activeEventIndex, events],
|
||||
);
|
||||
|
||||
const language = useMemo<'text' | 'json'>(() => {
|
||||
if (!activeEvent?.data) return 'text';
|
||||
return isJSON(activeEvent?.data) ? 'json' : 'text';
|
||||
}, [activeEvent?.data]);
|
||||
|
||||
return (
|
||||
<SplitLayout
|
||||
layout="vertical"
|
||||
name="grpc_events"
|
||||
defaultRatio={0.4}
|
||||
minHeightPx={20}
|
||||
firstSlot={() => (
|
||||
<EventStreamEventsVirtual
|
||||
events={events.data ?? []}
|
||||
activeEventIndex={activeEventIndex}
|
||||
setActiveEventIndex={setActiveEventIndex}
|
||||
/>
|
||||
)}
|
||||
secondSlot={
|
||||
activeEvent
|
||||
? () => (
|
||||
<div className="grid grid-rows-[auto_minmax(0,1fr)]">
|
||||
<div className="pb-3 px-2">
|
||||
<Separator />
|
||||
</div>
|
||||
<div className="pl-2 overflow-y-auto">
|
||||
<div className="mb-2 select-text cursor-text font-semibold">Message Received</div>
|
||||
{!showLarge && activeEvent.data.length > 1000 * 1000 ? (
|
||||
<VStack space={2} className="italic text-text-subtlest">
|
||||
Message previews larger than 1MB are hidden
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setShowingLarge(true);
|
||||
setTimeout(() => {
|
||||
setShowLarge(true);
|
||||
setShowingLarge(false);
|
||||
}, 500);
|
||||
}}
|
||||
isLoading={showingLarge}
|
||||
color="secondary"
|
||||
variant="border"
|
||||
size="xs"
|
||||
>
|
||||
Try Showing
|
||||
</Button>
|
||||
</div>
|
||||
</VStack>
|
||||
) : (
|
||||
<Editor
|
||||
readOnly
|
||||
defaultValue={tryFormatJson(activeEvent.data)}
|
||||
language={language}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function EventStreamEventsVirtual({
|
||||
events,
|
||||
activeEventIndex,
|
||||
setActiveEventIndex,
|
||||
}: {
|
||||
events: ServerSentEvent[];
|
||||
activeEventIndex: number | null;
|
||||
setActiveEventIndex: (eventId: number | null) => void;
|
||||
}) {
|
||||
// The scrollable element for your list
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// The virtualizer
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: events.length,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 28, // react-virtual requires a height, so we'll give it one
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={parentRef} className="overflow-y-auto">
|
||||
<div
|
||||
style={{
|
||||
height: `${rowVirtualizer.getTotalSize()}px`,
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{rowVirtualizer.getVirtualItems().map((virtualItem) => {
|
||||
const event = events[virtualItem.index]!;
|
||||
return (
|
||||
<div
|
||||
key={virtualItem.key}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: `${virtualItem.size}px`,
|
||||
transform: `translateY(${virtualItem.start}px)`,
|
||||
}}
|
||||
>
|
||||
<EventStreamEvent
|
||||
event={event}
|
||||
isActive={virtualItem.index === activeEventIndex}
|
||||
onClick={() => {
|
||||
if (virtualItem.index === activeEventIndex) setActiveEventIndex(null);
|
||||
else setActiveEventIndex(virtualItem.index);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EventStreamEvent({
|
||||
onClick,
|
||||
isActive,
|
||||
event,
|
||||
className,
|
||||
}: {
|
||||
onClick: () => void;
|
||||
isActive: boolean;
|
||||
event: ServerSentEvent;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<motion.button
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
onClick={onClick}
|
||||
className={classNames(
|
||||
className,
|
||||
'w-full grid grid-cols-[auto_auto_minmax(0,3fr)] gap-2 items-center text-left',
|
||||
'px-1.5 py-1 font-mono cursor-default group focus:outline-none rounded',
|
||||
isActive && '!bg-surface-highlight !text-text',
|
||||
'text-text-subtle hover:text',
|
||||
)}
|
||||
>
|
||||
<Icon className={classNames('text-info')} title="Server Message" icon="arrow_big_down_dash" />
|
||||
<HStack space={1.5} className="text-sm">
|
||||
{event.eventType && (
|
||||
<InlineCode className={classNames('py-0', isActive && 'bg-text-subtlest text-text')}>
|
||||
{event.eventType}
|
||||
</InlineCode>
|
||||
)}
|
||||
{event.id && (
|
||||
<InlineCode className={classNames('py-0', isActive && 'bg-text-subtlest text-text')}>
|
||||
{event.id}
|
||||
</InlineCode>
|
||||
)}
|
||||
</HStack>
|
||||
<div className={classNames('w-full truncate text-xs')}>{event.data.slice(0, 1000)}</div>
|
||||
</motion.button>
|
||||
);
|
||||
}
|
||||
@@ -160,7 +160,6 @@ export function TextViewer({
|
||||
<Editor
|
||||
readOnly
|
||||
className={className}
|
||||
forceUpdateKey={body}
|
||||
defaultValue={body}
|
||||
language={language}
|
||||
actions={actions}
|
||||
|
||||
@@ -1,31 +1,16 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useDialog } from '../components/DialogContext';
|
||||
import Settings from '../components/Settings/Settings';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
||||
import { useAppRoutes } from './useAppRoutes';
|
||||
import { useOsInfo } from './useOsInfo';
|
||||
|
||||
export function useOpenSettings() {
|
||||
const routes = useAppRoutes();
|
||||
const workspace = useActiveWorkspace();
|
||||
const dialog = useDialog();
|
||||
const { osType } = useOsInfo();
|
||||
return useMutation({
|
||||
mutationKey: ['open_settings'],
|
||||
mutationFn: async () => {
|
||||
if (workspace == null) return;
|
||||
|
||||
// HACK: Show settings in dialog on Linux until this is fixed: https://github.com/tauri-apps/tauri/issues/11171
|
||||
if (osType === 'linux') {
|
||||
dialog.show({
|
||||
id: 'settings',
|
||||
size: 'lg',
|
||||
render: ({ hide }) => <Settings hide={hide} />,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await invokeCmd('cmd_new_child_window', {
|
||||
url: routes.paths.workspaceSettings({ workspaceId: workspace.id }),
|
||||
label: 'settings',
|
||||
|
||||
12
src-web/hooks/useResponseBodyEventSource.ts
Normal file
12
src-web/hooks/useResponseBodyEventSource.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { HttpResponse } from '@yaakapp-internal/models';
|
||||
import type { ServerSentEvent } from '@yaakapp-internal/sse';
|
||||
import { getResponseBodyEventSource } from '../lib/responseBody';
|
||||
|
||||
export function useResponseBodyEventSource(response: HttpResponse) {
|
||||
return useQuery<ServerSentEvent[]>({
|
||||
placeholderData: (prev) => prev, // Keep previous data on refetch
|
||||
queryKey: ['response-body-event-source', response.id, response.contentLength],
|
||||
queryFn: () => getResponseBodyEventSource(response),
|
||||
});
|
||||
}
|
||||
@@ -4,6 +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 { cookieJarsAtom } from './useCookieJars';
|
||||
import { environmentsAtom } from './useEnvironments';
|
||||
import { foldersAtom } from './useFolders';
|
||||
@@ -25,6 +26,7 @@ export interface ModelPayload {
|
||||
}
|
||||
|
||||
export function useSyncModelStores() {
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const queryClient = useQueryClient();
|
||||
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
||||
|
||||
@@ -48,10 +50,17 @@ export function useSyncModelStores() {
|
||||
? keyValueQueryKey(model)
|
||||
: null;
|
||||
|
||||
// TODO: Move this logic to useRequestEditor() hook
|
||||
if (model.model === 'http_request' && windowLabel !== getCurrentWebviewWindow().label) {
|
||||
wasUpdatedExternally(model.id);
|
||||
}
|
||||
|
||||
// Only sync models that belong to this workspace, if a workspace ID is present
|
||||
if ('workspaceId' in model && model.workspaceId !== activeWorkspace?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark these models as DESC instead of ASC
|
||||
const pushToFront = (['http_response', 'grpc_connection'] as AnyModel['model'][]).includes(
|
||||
model.model,
|
||||
);
|
||||
|
||||
@@ -35,3 +35,15 @@ function detectFromContent(
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function isJSON(content: string | null | undefined): boolean {
|
||||
if (typeof content !== 'string') return false;
|
||||
|
||||
try {
|
||||
JSON.parse(content)
|
||||
return true;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,34 @@
|
||||
import { readFile } from '@tauri-apps/plugin-fs';
|
||||
import type { HttpResponse } from '@yaakapp-internal/models';
|
||||
import type { ServerSentEvent } from '@yaakapp-internal/sse';
|
||||
import { getCharsetFromContentType } from './model_util';
|
||||
import { invokeCmd } from './tauri';
|
||||
|
||||
export async function getResponseBodyText(response: HttpResponse): Promise<string | null> {
|
||||
if (response.bodyPath) {
|
||||
const bytes = await readFile(response.bodyPath);
|
||||
const charset = getCharsetFromContentType(response.headers);
|
||||
if (!response.bodyPath) return null;
|
||||
|
||||
try {
|
||||
return new TextDecoder(charset ?? 'utf-8', { fatal: true }).decode(bytes);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (err) {
|
||||
// Failed to decode as text, so return null
|
||||
return null;
|
||||
}
|
||||
const bytes = await readFile(response.bodyPath);
|
||||
const charset = getCharsetFromContentType(response.headers);
|
||||
|
||||
try {
|
||||
return new TextDecoder(charset ?? 'utf-8', { fatal: true }).decode(bytes);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (err) {
|
||||
// Failed to decode as text, so return null
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getResponseBodyBlob(response: HttpResponse): Promise<Uint8Array | null> {
|
||||
if (response.bodyPath) {
|
||||
return readFile(response.bodyPath);
|
||||
}
|
||||
return null;
|
||||
if (!response.bodyPath) return null;
|
||||
return readFile(response.bodyPath);
|
||||
}
|
||||
|
||||
export async function getResponseBodyEventSource(
|
||||
response: HttpResponse,
|
||||
): Promise<ServerSentEvent[]> {
|
||||
if (!response.bodyPath) return [];
|
||||
return invokeCmd<ServerSentEvent[]>('cmd_get_sse_events', {
|
||||
filePath: response.bodyPath,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ type TauriCmd =
|
||||
| 'cmd_get_folder'
|
||||
| 'cmd_get_grpc_request'
|
||||
| 'cmd_get_http_request'
|
||||
| 'cmd_get_sse_events'
|
||||
| 'cmd_get_key_value'
|
||||
| 'cmd_get_settings'
|
||||
| 'cmd_get_workspace'
|
||||
|
||||
@@ -19,22 +19,14 @@
|
||||
}
|
||||
|
||||
/* Disable user selection to make it more "app-like" */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
label,
|
||||
code,
|
||||
pre,
|
||||
li {
|
||||
:not(a),
|
||||
:not(input):not(textarea),
|
||||
:not(input):not(textarea)::after,
|
||||
:not(input):not(textarea)::before {
|
||||
@apply select-none cursor-default;
|
||||
}
|
||||
|
||||
input,
|
||||
input,
|
||||
textarea {
|
||||
&::placeholder {
|
||||
@apply text-placeholder;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@react-hook/resize-observer": "^2.0.2",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@tanstack/react-query": "^5.55.4",
|
||||
"@tanstack/react-virtual": "^3.10.8",
|
||||
"@tauri-apps/api": "^2.0.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.0.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0",
|
||||
@@ -34,10 +35,10 @@
|
||||
"codemirror-json-schema": "^0.6.1",
|
||||
"date-fns": "^3.6.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"fast-fuzzy": "^1.12.0",
|
||||
"focus-trap-react": "^10.2.3",
|
||||
"format-graphql": "^1.5.0",
|
||||
"framer-motion": "^11.5.4",
|
||||
"fuzzbunny": "^1.0.1",
|
||||
"jotai": "^2.9.3",
|
||||
"lucide-react": "^0.439.0",
|
||||
"mime": "^4.0.4",
|
||||
|
||||
Reference in New Issue
Block a user