Compare commits

..

25 Commits

Author SHA1 Message Date
Gregory Schier
b9f397e04a Fix response filtering 2024-10-21 07:26:50 -07:00
Gregory Schier
57c3a86799 Animate up instead of down when dropdowns open up 2024-10-18 11:22:05 -07:00
Gregory Schier
52ac41b0c6 Move elapsed calculation 2024-10-18 10:53:04 -07:00
Gregory Schier
741ccbe741 Add labels to plugin event subscribers 2024-10-18 10:46:30 -07:00
Gregory Schier
2ecd86da78 Update README.md 2024-10-18 08:27:24 -07:00
Gregory Schier
30e4e7665a Remove ios config 2024-10-18 07:59:28 -07:00
Gregory Schier
516dfd1f19 Fix GraphQL introspection 2024-10-18 06:57:44 -07:00
Gregory Schier
0cd08499aa Render sending gRPC events 2024-10-17 12:03:35 -07:00
Gregory Schier
c652df82a3 Fix SSE event selection 2024-10-17 11:28:10 -07:00
Gregory Schier
c8342fb0a9 Delete send history for workspace 2024-10-17 11:17:27 -07:00
Gregory Schier
d0b59a0fb4 Show folder structure in request selection 2024-10-17 10:53:48 -07:00
Gregory Schier
6f50f35519 Bump Tauri to fix macOS 13 launch issue 2024-10-15 09:54:21 -07:00
Gregory Schier
4e775b2b49 Undo minimumSystemVersion 2024-10-15 07:49:27 -07:00
Gregory Schier
e77a9e5d44 Rebuild plugins 2024-10-15 07:48:26 -07:00
Gregory Schier
a381e44d8c Prevent stale content flash after editing request name 2024-10-15 07:32:00 -07:00
Gregory Schier
4acf0969e8 Only sync models from active workspace 2024-10-15 07:31:42 -07:00
Gregory Schier
30c4178269 Disable autocomplete/correct/etc in plain input 2024-10-14 21:46:48 -07:00
Gregory Schier
dffe6e0a16 Intelligent readonly editor updates, to preserve scroll 2024-10-14 10:40:09 -07:00
Gregory Schier
8090e67b9e Revert hyper v1 for gRPC 2024-10-12 22:05:17 -07:00
Gregory Schier
f1beabcb6f Try again 2024-10-12 21:33:45 -07:00
Gregory Schier
647b8e2313 Try fix windows build 2024-10-12 21:17:44 -07:00
Gregory Schier
f5b4697608 Npm i 2024-10-12 21:06:19 -07:00
Gregory Schier
f201857d51 Bump Tauri to fix settings window 2024-10-12 20:57:01 -07:00
Gregory Schier
0d982057a5 Add proxy setting for HTTP requests (#127) 2024-10-12 20:55:09 -07:00
Gregory Schier
6fb94384b9 Better fuzzy matching in cmd palette 2024-10-12 07:41:01 -07:00
47 changed files with 1109 additions and 904 deletions

View File

@@ -1,4 +1,4 @@
# [Yaak API Client](https://yaak.app)
# Yaak API Client
Yaak is a desktop API client for organizing and executing REST, GraphQL, and gRPC
requests. It's built using [Tauri](https://tauri.app), Rust, and ReactJS.

157
package-lock.json generated
View File

@@ -18,7 +18,7 @@
"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",
@@ -2689,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": {
@@ -2705,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"
],
@@ -2735,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"
],
@@ -2752,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"
],
@@ -2769,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"
],
@@ -2786,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"
],
@@ -2803,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"
],
@@ -2820,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"
],
@@ -2837,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"
],
@@ -2854,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"
],
@@ -2871,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"
],
@@ -6496,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",
@@ -6924,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",
@@ -7213,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",
@@ -8243,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",
@@ -9802,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",
@@ -10954,19 +10929,6 @@
"react-dom": "*"
}
},
"node_modules/react-virtuoso": {
"version": "4.10.4",
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.10.4.tgz",
"integrity": "sha512-G/gprhTbK+lzMxoo/iStcZxVEGph/cIhc3WANEpt92RuMw+LiCZOmBfKoeoZOHlm/iyftTrDJhGaTCpxyucnkQ==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16 || >=17 || >= 18",
"react-dom": ">=16 || >=17 || >= 18"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -12559,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",
@@ -12883,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",
@@ -13821,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",
@@ -13838,7 +13784,6 @@
"react-pdf": "^9.1.0",
"react-router-dom": "^6.26.2",
"react-use": "^17.5.1",
"react-virtuoso": "^4.10.4",
"slugify": "^1.6.6",
"uuid": "^10.0.0",
"xml-formatter": "^3.6.3"

View File

@@ -31,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",

View File

@@ -1 +1,7 @@
edition = "2018"
# Widths
chain_width = 100
max_width = 100
single_line_if_else_max_width = 100
fn_call_width = 100

212
src-tauri/Cargo.lock generated
View File

@@ -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]]
@@ -2552,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",
@@ -2611,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",
@@ -2624,7 +2624,6 @@ dependencies = [
"pin-project-lite",
"socket2",
"tokio",
"tower",
"tower-service",
"tracing",
]
@@ -3015,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",
]
@@ -3046,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"
@@ -3493,7 +3502,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
"objc_exception",
]
[[package]]
@@ -3501,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"
@@ -3528,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"
@@ -3552,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"
@@ -3571,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"
@@ -3597,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]]
@@ -4212,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]]
@@ -4234,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",
@@ -4256,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",
@@ -4304,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]]
@@ -4713,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",
@@ -5200,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",
]
@@ -5230,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",
@@ -5986,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",
@@ -6278,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",
@@ -6297,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",
@@ -6557,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",
@@ -6667,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",
@@ -6685,7 +6794,7 @@ dependencies = [
"hyper-util",
"percent-encoding",
"pin-project",
"prost 0.13.1",
"prost 0.13.3",
"socket2",
"tokio",
"tokio-stream",
@@ -7739,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",
@@ -7759,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",
@@ -7943,7 +8053,7 @@ dependencies = [
"dunce",
"log",
"path-slash",
"prost 0.13.1",
"prost 0.13.3",
"rand 0.8.5",
"regex",
"reqwest",
@@ -7953,7 +8063,7 @@ dependencies = [
"tauri-plugin-shell",
"thiserror",
"tokio",
"tonic 0.12.1",
"tonic 0.12.3",
"tonic-build",
"ts-rs",
"yaak_models",

View File

@@ -66,4 +66,4 @@ eventsource-client = { git = "https://github.com/yaakapp/rust-eventsource-client
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"] }

View File

@@ -0,0 +1 @@
ALTER TABLE settings ADD COLUMN proxy TEXT;

View File

@@ -14,7 +14,7 @@ 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};
@@ -25,10 +25,11 @@ 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_workspace, update_response_if_id, upsert_cookie_jar,
get_http_response, get_or_create_settings, get_workspace, update_response_if_id,
upsert_cookie_jar,
};
use yaak_plugin_runtime::events::{RenderPurpose, WindowContext};
@@ -40,9 +41,9 @@ pub async fn send_http_request<R: Runtime>(
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),
@@ -61,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 {
@@ -75,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) => {
@@ -196,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);
@@ -214,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(),
@@ -232,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}}}"#,
@@ -326,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
@@ -359,6 +370,7 @@ pub async fn send_http_request<R: Runtime>(
let sendable_req = match request_builder.build() {
Ok(r) => r,
Err(e) => {
warn!("Failed to build request builder {e:?}");
return Ok(response_err(&*response.lock().await, e.to_string(), window).await);
}
};
@@ -392,13 +404,11 @@ pub async fn send_http_request<R: Runtime>(
let response_headers = v.headers().clone();
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");
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())
} else {
base_dir.join(response_id.clone())
};
{
@@ -449,11 +459,11 @@ pub async fn send_http_request<R: Runtime>(
}
match chunk {
Ok(Some(bytes)) => {
let mut r = response.lock().await;
r.elapsed = start.elapsed().as_millis() as i32;
f.write_all(&bytes).await.expect("Failed to write to file");
f.flush().await.expect("Failed to flush file");
written_bytes += bytes.len();
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
@@ -509,7 +519,8 @@ pub async fn send_http_request<R: Runtime>(
}
}
Err(e) => {
response_err(&*response.lock().await, e.to_string(), &window).await;
warn!("Failed to execute request {e}");
response_err(&*response.lock().await, format!("{e}{e:?}"), &window).await;
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -72,9 +72,6 @@
"rpm"
],
"createUpdaterArtifacts": "v1Compatible",
"iOS": {
"developmentTeam": "7PU3P6ELJ8"
},
"macOS": {
"minimumSystemVersion": "13.0",
"exceptionDomain": "",

View File

@@ -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 {

View 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
});

View 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"
}
}

View File

@@ -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",

View File

@@ -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;
}
}]
};

View File

@@ -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;
}
}
]
};

View File

@@ -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))
}
}

View File

@@ -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")
}
}

View File

@@ -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, };

View File

@@ -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),

View File

@@ -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?;
}
@@ -518,7 +509,7 @@ pub async fn get_grpc_connection<R: Runtime>(
Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?)
}
pub async fn list_grpc_connections<R: Runtime>(
pub async fn list_grpc_connections_for_workspace<R: Runtime>(
mgr: &impl Manager<R>,
workspace_id: &str,
) -> Result<Vec<GrpcConnection>> {
@@ -582,6 +573,16 @@ pub async fn delete_all_grpc_connections<R: Runtime>(
Ok(())
}
pub async fn delete_all_grpc_connections_for_workspace<R: Runtime>(
window: &WebviewWindow<R>,
workspace_id: &str,
) -> Result<()> {
for r in list_grpc_connections_for_workspace(window, workspace_id).await? {
delete_grpc_connection(window, &r.id).await?;
}
Ok(())
}
pub async fn upsert_grpc_event<R: Runtime>(
window: &WebviewWindow<R>,
event: &GrpcEvent,
@@ -664,7 +665,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())?;
@@ -757,7 +758,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();
@@ -767,13 +768,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();
@@ -785,11 +788,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>(
@@ -805,39 +805,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()
@@ -1308,10 +1292,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(),
@@ -1391,32 +1372,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(),
@@ -1479,7 +1443,17 @@ pub async fn delete_all_http_responses_for_request<R: Runtime>(
Ok(())
}
pub async fn list_http_responses<R: Runtime>(
pub async fn delete_all_http_responses_for_workspace<R: Runtime>(
window: &WebviewWindow<R>,
workspace_id: &str,
) -> Result<()> {
for r in list_http_responses_for_workspace(window, workspace_id, None).await? {
delete_http_response(window, &r.id).await?;
}
Ok(())
}
pub async fn list_http_responses_for_workspace<R: Runtime>(
mgr: &impl Manager<R>,
workspace_id: &str,
limit: Option<i64>,

View File

@@ -273,9 +273,9 @@ impl PluginManager {
Ok(())
}
pub async fn subscribe(&self) -> (String, mpsc::Receiver<InternalEvent>) {
pub async fn subscribe(&self, label: &str) -> (String, mpsc::Receiver<InternalEvent>) {
let (tx, rx) = mpsc::channel(128);
let rx_id = generate_id();
let rx_id = format!("{label}_{}", generate_id());
self.subscribers.lock().await.insert(rx_id.clone(), tx);
(rx_id, rx)
}
@@ -362,7 +362,8 @@ impl PluginManager {
payload: &InternalEventPayload,
plugins: Vec<PluginHandle>,
) -> Result<Vec<InternalEvent>> {
let (rx_id, mut rx) = self.subscribe().await;
let label = format!("wait[{}]", plugins.len());
let (rx_id, mut rx) = self.subscribe(label.as_str()).await;
// 1. Build the events with IDs and everything
let events_to_send = plugins
@@ -557,9 +558,9 @@ impl PluginManager {
content_type: &str,
) -> Result<FilterResponse> {
let plugin_name = if content_type.to_lowercase().contains("json") {
"filter-jsonpath"
"@yaakapp/filter-jsonpath"
} else {
"filter-xpath"
"@yaakapp/filter-xpath"
};
let plugin = self

View File

@@ -24,7 +24,7 @@ const queryClient = new QueryClient({
},
});
const ENABLE_REACT_QUERY_DEVTOOLS = true;
const ENABLE_REACT_QUERY_DEVTOOLS = false;
export function App() {
return (

View File

@@ -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} />;
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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>
);

View File

@@ -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 })}

View 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>
);
}

View File

@@ -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);
},

View File

@@ -1,3 +1,4 @@
import type { Folder, HttpRequest } from '@yaakapp-internal/models';
import type {
TemplateFunction,
TemplateFunctionArg,
@@ -12,6 +13,7 @@ import classNames from 'classnames';
import { useCallback, useMemo, useState } from 'react';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useDebouncedValue } from '../hooks/useDebouncedValue';
import { useFolders } from '../hooks/useFolders';
import { useHttpRequests } from '../hooks/useHttpRequests';
import { useRenderTemplate } from '../hooks/useRenderTemplate';
import { useTemplateTokensToString } from '../hooks/useTemplateTokensToString';
@@ -253,6 +255,7 @@ function HttpRequestArg({
value: string;
onChange: (v: string) => void;
}) {
const folders = useFolders();
const httpRequests = useHttpRequests();
const activeRequest = useActiveRequest();
return (
@@ -262,15 +265,35 @@ function HttpRequestArg({
onChange={onChange}
value={value}
options={[
...httpRequests.map((r) => ({
label: fallbackRequestName(r) + (activeRequest?.id === r.id ? ' (current)' : ''),
value: r.id,
})),
...httpRequests.map((r) => {
return {
label: buildRequestBreadcrumbs(r, folders).join(' / ') + (r.id == activeRequest?.id ? ' (current)' : ''),
value: r.id,
};
}),
]}
/>
);
}
function buildRequestBreadcrumbs(request: HttpRequest, folders: Folder[]): string[] {
const ancestors: (HttpRequest | Folder)[] = [request];
const next = () => {
const latest = ancestors[0];
if (latest == null) return [];
const parent = folders.find((f) => f.id === latest.folderId);
if (parent == null) return;
ancestors.unshift(parent);
next();
};
next();
return ancestors.map((a) => (a.model === 'folder' ? a.name : fallbackRequestName(a)));
}
function CheckboxArg({
arg,
onChange,

View File

@@ -2,6 +2,7 @@ import classNames from 'classnames';
import { memo, useCallback, useMemo } from 'react';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
import { useDeleteSendHistory } from '../hooks/useDeleteSendHistory';
import { useDeleteWorkspace } from '../hooks/useDeleteWorkspace';
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
import { usePrompt } from '../hooks/usePrompt';
@@ -36,6 +37,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
const settings = useSettings();
const openWorkspace = useOpenWorkspace();
const openWorkspaceNewWindow = settings?.openWorkspaceNewWindow ?? null;
const deleteSendHistory = useDeleteSendHistory();
const { workspaceItems, extraItems } = useMemo<{
workspaceItems: RadioDropdownItem[];
@@ -70,9 +72,15 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
updateWorkspace.mutate({ name });
},
},
{
key: 'delete-responses',
label: 'Clear Send History',
leftSlot: <Icon icon="history" />,
onSelect: deleteSendHistory.mutate,
},
{
key: 'delete',
label: 'Delete',
label: 'Delete Workspace',
leftSlot: <Icon icon="trash" />,
onSelect: deleteWorkspace.mutate,
variant: 'danger',
@@ -90,7 +98,8 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
}, [
activeWorkspace?.name,
activeWorkspaceId,
createWorkspace,
createWorkspace.mutate,
deleteSendHistory.mutate,
deleteWorkspace.mutate,
prompt,
updateWorkspace,

View File

@@ -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',

View File

@@ -350,8 +350,9 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
container: CSSProperties;
menu: CSSProperties;
triangle: CSSProperties;
upsideDown: boolean;
}>(() => {
if (triggerShape == null) return { container: {}, triangle: {}, menu: {} };
if (triggerShape == null) return { container: {}, triangle: {}, menu: {}, upsideDown: false };
const menuMarginY = 5;
const docRect = document.documentElement.getBoundingClientRect();
@@ -364,6 +365,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
const upsideDown = heightBelow < heightAbove && heightBelow < items.length * 25 + 20 + 200;
const triggerWidth = triggerShape.right - triggerShape.left;
return {
upsideDown,
container: {
top: !upsideDown ? top + menuMarginY : undefined,
bottom: upsideDown
@@ -426,7 +428,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
<motion.div
tabIndex={0}
onKeyDown={handleMenuKeyDown}
initial={{ opacity: 0, y: -5, scale: 0.98 }}
initial={{ opacity: 0, y: (styles.upsideDown ? 1 : -1) * 5, scale: 0.98 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
role="menu"
aria-orientation="vertical"

View File

@@ -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: defaultValue,
}),
});
}
}, [defaultValue, readOnly]);
// Add bg classes to actions, so they appear over the text
const decoratedActions = useMemo(() => {
const results = [];

View File

@@ -26,7 +26,7 @@ const icons = {
chevron_down: lucide.ChevronDownIcon,
chevron_right: lucide.ChevronRightIcon,
circle_alert: lucide.CircleAlertIcon,
cloud: lucide.CloudIcon,
clock: lucide.ClockIcon,
code: lucide.CodeIcon,
cookie: lucide.CookieIcon,
copy: lucide.CopyIcon,
@@ -48,6 +48,7 @@ const icons = {
grip_vertical: lucide.GripVerticalIcon,
hand: lucide.HandIcon,
help: lucide.CircleHelpIcon,
history: lucide.HistoryIcon,
house: lucide.HomeIcon,
info: lucide.InfoIcon,
keyboard: lucide.KeyboardIcon,

View File

@@ -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}

View File

@@ -65,7 +65,14 @@ function ActualEventStreamViewer({ response }: Props) {
<Separator />
</div>
<div className="pl-2 overflow-y-auto">
<div className="mb-2 select-text cursor-text font-semibold">Message Received</div>
<HStack space={1.5} className="mb-2 select-text cursor-text font-semibold">
<EventLabels
className="text-sm"
event={activeEvent}
index={activeEventIndex ?? 0}
/>
Message Received
</HStack>
{!showLarge && activeEvent.data.length > 1000 * 1000 ? (
<VStack space={2} className="italic text-text-subtlest">
Message previews larger than 1MB are hidden
@@ -90,7 +97,6 @@ function ActualEventStreamViewer({ response }: Props) {
) : (
<Editor
readOnly
forceUpdateKey={activeEvent.id ?? activeEvent.data}
defaultValue={tryFormatJson(activeEvent.data)}
language={language}
/>
@@ -149,6 +155,7 @@ function EventStreamEventsVirtual({
<EventStreamEvent
event={event}
isActive={virtualItem.index === activeEventIndex}
index={virtualItem.index}
onClick={() => {
if (virtualItem.index === activeEventIndex) setActiveEventIndex(null);
else setActiveEventIndex(virtualItem.index);
@@ -167,11 +174,13 @@ function EventStreamEvent({
isActive,
event,
className,
index,
}: {
onClick: () => void;
isActive: boolean;
event: ServerSentEvent;
className?: string;
index: number;
}) {
return (
<motion.button
@@ -187,19 +196,33 @@ function EventStreamEvent({
)}
>
<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>
<EventLabels className="text-sm" event={event} isActive={isActive} index={index} />
<div className={classNames('w-full truncate text-xs')}>{event.data.slice(0, 1000)}</div>
</motion.button>
);
}
function EventLabels({
className,
event,
index,
isActive,
}: {
event: ServerSentEvent;
index: number;
className: string;
isActive?: boolean;
}) {
return (
<HStack space={1.5} alignItems="center" className={className}>
<InlineCode className={classNames('py-0', isActive && 'bg-text-subtlest text-text')}>
{event.id ?? index}
</InlineCode>
{event.eventType && (
<InlineCode className={classNames('py-0', isActive && 'bg-text-subtlest text-text')}>
{event.eventType}
</InlineCode>
)}
</HStack>
);
}

View File

@@ -41,6 +41,7 @@ export function HTMLOrTextViewer({ response, pretty, textViewerClassName }: Prop
className={textViewerClassName}
onSaveResponse={saveResponse.mutate}
responseId={response.id}
requestId={response.requestId}
/>
);
}

View File

@@ -28,6 +28,7 @@ interface Props {
text: string;
language: EditorProps['language'];
responseId: string;
requestId: string;
onSaveResponse: () => void;
}
@@ -37,20 +38,21 @@ export function TextViewer({
language,
text,
responseId,
requestId,
pretty,
className,
onSaveResponse,
}: Props) {
const [filterTextMap, setFilterTextMap] = useFilterText();
const [showLargeResponse, toggleShowLargeResponse] = useToggle();
const filterText = filterTextMap[responseId] ?? null;
const filterText = filterTextMap[requestId] ?? null;
const copy = useCopy();
const debouncedFilterText = useDebouncedValue(filterText, 200);
const setFilterText = useCallback(
(v: string | null) => {
setFilterTextMap((m) => ({ ...m, [responseId]: v }));
setFilterTextMap((m) => ({ ...m, [requestId]: v }));
},
[setFilterTextMap, responseId],
[setFilterTextMap, requestId],
);
const isSearching = filterText != null;
@@ -75,7 +77,7 @@ export function TextViewer({
nodes.push(
<div key="input" className="w-full !opacity-100">
<Input
key={responseId}
key={requestId}
validate={!filteredResponse.error}
hideLabel
autoFocus
@@ -110,7 +112,7 @@ export function TextViewer({
filteredResponse.error,
isSearching,
language,
responseId,
requestId,
setFilterText,
toggleSearch,
]);
@@ -160,7 +162,6 @@ export function TextViewer({
<Editor
readOnly
className={className}
forceUpdateKey={body}
defaultValue={body}
language={language}
actions={actions}

View File

@@ -4,20 +4,17 @@ import { useDialog } from '../components/DialogContext';
import type { AlertProps } from './Alert';
import { Alert } from './Alert';
interface AlertArg {
id: string;
title: DialogProps['title'];
body: AlertProps['body'];
size?: DialogProps['size'];
}
export function useAlert() {
const dialog = useDialog();
return useCallback(
({
id,
title,
body,
size = 'sm',
}: {
id: string;
title: DialogProps['title'];
body: AlertProps['body'];
size?: DialogProps['size'];
}) =>
return useCallback<(a: AlertArg) => void>(
({ id, title, body, size = 'sm' }: AlertArg) =>
dialog.show({
id,
title,

View File

@@ -0,0 +1,43 @@
import { useMutation } from '@tanstack/react-query';
import { count } from '../lib/pluralize';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAlert } from './useAlert';
import { useConfirm } from './useConfirm';
import { useGrpcConnections } from './useGrpcConnections';
import { useHttpResponses } from './useHttpResponses';
export function useDeleteSendHistory() {
const confirm = useConfirm();
const alert = useAlert();
const activeWorkspace = useActiveWorkspace();
const httpResponses = useHttpResponses();
const grpcConnections = useGrpcConnections();
const labels = [
httpResponses.length > 0 ? count('Http Response', httpResponses.length) : null,
grpcConnections.length > 0 ? count('Grpc Connection', grpcConnections.length) : null,
].filter((l) => l != null);
return useMutation({
mutationKey: ['delete_send_history'],
mutationFn: async () => {
if (labels.length === 0) {
alert({
id: 'no-responses',
title: 'Nothing to Delete',
body: 'There are no Http Response or Grpc Connections to delete',
});
return;
}
const confirmed = await confirm({
id: 'delete-send-history',
title: 'Clear Send History',
variant: 'delete',
description: <>Delete {labels.join(' and ')}?</>,
});
if (!confirmed) return;
await invokeCmd('cmd_delete_send_history', { workspaceId: activeWorkspace?.id ?? 'n/a' });
},
});
}

View File

@@ -3,6 +3,7 @@ import { emit } from '@tauri-apps/api/event';
import type { GrpcConnection, GrpcRequest } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { minPromiseMillis } from '../lib/minPromiseMillis';
import { isResponseLoading } from '../lib/model_util';
import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironment } from './useActiveEnvironment';
import { useDebouncedValue } from './useDebouncedValue';
@@ -22,27 +23,27 @@ export function useGrpc(
const go = useMutation<void, string>({
mutationKey: ['grpc_go', conn?.id],
mutationFn: async () =>
await invokeCmd('cmd_grpc_go', { requestId, environmentId: environment?.id, protoFiles }),
mutationFn: () =>
invokeCmd<void>('cmd_grpc_go', { requestId, environmentId: environment?.id, protoFiles }),
onSettled: () => trackEvent('grpc_request', 'send'),
});
const send = useMutation({
mutationKey: ['grpc_send', conn?.id],
mutationFn: async ({ message }: { message: string }) =>
await emit(`grpc_client_msg_${conn?.id ?? 'none'}`, { Message: message }),
mutationFn: ({ message }: { message: string }) =>
emit(`grpc_client_msg_${conn?.id ?? 'none'}`, { Message: message }),
onSettled: () => trackEvent('grpc_connection', 'send'),
});
const cancel = useMutation({
mutationKey: ['grpc_cancel', conn?.id ?? 'n/a'],
mutationFn: async () => await emit(`grpc_client_msg_${conn?.id ?? 'none'}`, 'Cancel'),
mutationFn: () => emit(`grpc_client_msg_${conn?.id ?? 'none'}`, 'Cancel'),
onSettled: () => trackEvent('grpc_connection', 'cancel'),
});
const commit = useMutation({
mutationKey: ['grpc_commit', conn?.id ?? 'n/a'],
mutationFn: async () => await emit(`grpc_client_msg_${conn?.id ?? 'none'}`, 'Commit'),
mutationFn: () => emit(`grpc_client_msg_${conn?.id ?? 'none'}`, 'Commit'),
onSettled: () => trackEvent('grpc_connection', 'commit'),
});
@@ -51,11 +52,11 @@ export function useGrpc(
const reflect = useQuery<ReflectResponseService[], string>({
enabled: req != null,
queryKey: ['grpc_reflect', req?.id ?? 'n/a', debouncedUrl, protoFiles],
queryFn: async () =>
(await minPromiseMillis(
queryFn: () =>
minPromiseMillis<ReflectResponseService[]>(
invokeCmd('cmd_grpc_reflect', { requestId, protoFiles }),
300,
)) as ReflectResponseService[],
),
});
return {
@@ -63,7 +64,7 @@ export function useGrpc(
reflect,
cancel,
commit,
isStreaming: conn != null && conn.elapsed === 0,
isStreaming: isResponseLoading(conn),
send,
};
}

View File

@@ -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',

View File

@@ -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,
);

View File

@@ -14,6 +14,7 @@ type TauriCmd =
| 'cmd_curl_to_request'
| 'cmd_delete_all_grpc_connections'
| 'cmd_delete_all_http_responses'
| 'cmd_delete_send_history'
| 'cmd_delete_cookie_jar'
| 'cmd_delete_environment'
| 'cmd_delete_folder'

View File

@@ -35,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",