mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-19 00:57:48 +01:00
Compare commits
25 Commits
v2025.7.0-
...
v2025.7.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb1916b773 | ||
|
|
a3df0489b1 | ||
|
|
b19e036a61 | ||
|
|
b51e37f221 | ||
|
|
cf9882b5b9 | ||
|
|
bbf85c953d | ||
|
|
17ddc76223 | ||
|
|
754ec0ba86 | ||
|
|
1198aa7d87 | ||
|
|
43437abae7 | ||
|
|
9439cfa2ba | ||
|
|
a731ccc8bd | ||
|
|
451c8b9dde | ||
|
|
b7682db9a3 | ||
|
|
7e2d72c4e3 | ||
|
|
28bb460409 | ||
|
|
56d635166b | ||
|
|
f6a7257104 | ||
|
|
1fce060ef7 | ||
|
|
5c966e5a95 | ||
|
|
0520ef5d43 | ||
|
|
25b110778a | ||
|
|
327bf84e57 | ||
|
|
1c48b309b5 | ||
|
|
7c5dec821d |
@@ -22,7 +22,7 @@
|
||||
<!-- sponsors-premium --><a href="https://github.com/MVST-Solutions"><img src="https://github.com/MVST-Solutions.png" width="80px" alt="User avatar: MVST-Solutions" /></a> <a href="https://github.com/dharsanb"><img src="https://github.com/dharsanb.png" width="80px" alt="User avatar: dharsanb" /></a> <a href="https://github.com/railwayapp"><img src="https://github.com/railwayapp.png" width="80px" alt="User avatar: railwayapp" /></a> <a href="https://github.com/caseyamcl"><img src="https://github.com/caseyamcl.png" width="80px" alt="User avatar: caseyamcl" /></a> <a href="https://github.com/andriyor"><img src="https://github.com/andriyor.png" width="80px" alt="User avatar: andriyor" /></a> <a href="https://github.com/"><img src="https://raw.githubusercontent.com/JamesIves/github-sponsors-readme-action/dev/.github/assets/placeholder.png" width="80px" alt="User avatar: " /></a> <!-- sponsors-premium -->
|
||||
</p>
|
||||
<p align="center">
|
||||
<!-- sponsors-base --><a href="https://github.com/seanwash"><img src="https://github.com/seanwash.png" width="50px" alt="User avatar: seanwash" /></a> <a href="https://github.com/jerath"><img src="https://github.com/jerath.png" width="50px" alt="User avatar: jerath" /></a> <a href="https://github.com/itsa-sh"><img src="https://github.com/itsa-sh.png" width="50px" alt="User avatar: itsa-sh" /></a> <a href="https://github.com/dmmulroy"><img src="https://github.com/dmmulroy.png" width="50px" alt="User avatar: dmmulroy" /></a> <a href="https://github.com/timcole"><img src="https://github.com/timcole.png" width="50px" alt="User avatar: timcole" /></a> <a href="https://github.com/VLZH"><img src="https://github.com/VLZH.png" width="50px" alt="User avatar: VLZH" /></a> <a href="https://github.com/terasaka2k"><img src="https://github.com/terasaka2k.png" width="50px" alt="User avatar: terasaka2k" /></a> <a href="https://github.com/majudhu"><img src="https://github.com/majudhu.png" width="50px" alt="User avatar: majudhu" /></a> <!-- sponsors-base -->
|
||||
<!-- sponsors-base --><a href="https://github.com/seanwash"><img src="https://github.com/seanwash.png" width="50px" alt="User avatar: seanwash" /></a> <a href="https://github.com/jerath"><img src="https://github.com/jerath.png" width="50px" alt="User avatar: jerath" /></a> <a href="https://github.com/itsa-sh"><img src="https://github.com/itsa-sh.png" width="50px" alt="User avatar: itsa-sh" /></a> <a href="https://github.com/dmmulroy"><img src="https://github.com/dmmulroy.png" width="50px" alt="User avatar: dmmulroy" /></a> <a href="https://github.com/timcole"><img src="https://github.com/timcole.png" width="50px" alt="User avatar: timcole" /></a> <a href="https://github.com/VLZH"><img src="https://github.com/VLZH.png" width="50px" alt="User avatar: VLZH" /></a> <a href="https://github.com/terasaka2k"><img src="https://github.com/terasaka2k.png" width="50px" alt="User avatar: terasaka2k" /></a> <a href="https://github.com/majudhu"><img src="https://github.com/majudhu.png" width="50px" alt="User avatar: majudhu" /></a> <a href="https://github.com/axelrindle"><img src="https://github.com/axelrindle.png" width="50px" alt="User avatar: axelrindle" /></a> <!-- sponsors-base -->
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
148
package-lock.json
generated
148
package-lock.json
generated
@@ -25,6 +25,7 @@
|
||||
"plugins/importer-insomnia",
|
||||
"plugins/importer-openapi",
|
||||
"plugins/importer-postman",
|
||||
"plugins/importer-postman-environment",
|
||||
"plugins/importer-yaak",
|
||||
"plugins/template-function-cookie",
|
||||
"plugins/template-function-encode",
|
||||
@@ -60,7 +61,7 @@
|
||||
"@eslint/compat": "^1.3.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@tauri-apps/cli": "^2.8.4",
|
||||
"@tauri-apps/cli": "^2.9.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
||||
"@typescript-eslint/parser": "^8.27.0",
|
||||
"@yaakapp/cli": "^0.2.7",
|
||||
@@ -3111,9 +3112,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/api": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.8.0.tgz",
|
||||
"integrity": "sha512-ga7zdhbS2GXOMTIZRT0mYjKJtR9fivsXzsyq5U3vjDL0s6DTMwYRm0UHNjzTY5dh4+LSC68Sm/7WEiimbQNYlw==",
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.9.0.tgz",
|
||||
"integrity": "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw==",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -3121,9 +3122,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.8.4.tgz",
|
||||
"integrity": "sha512-ejUZBzuQRcjFV+v/gdj/DcbyX/6T4unZQjMSBZwLzP/CymEjKcc2+Fc8xTORThebHDUvqoXMdsCZt8r+hyN15g==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.9.1.tgz",
|
||||
"integrity": "sha512-kKi2/WWsNXKoMdatBl4xrT7e1Ce27JvsetBVfWuIb6D3ep/Y0WO5SIr70yarXOSWam8NyDur4ipzjZkg6m7VDg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"bin": {
|
||||
@@ -3137,23 +3138,23 @@
|
||||
"url": "https://opencollective.com/tauri"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tauri-apps/cli-darwin-arm64": "2.8.4",
|
||||
"@tauri-apps/cli-darwin-x64": "2.8.4",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.8.4",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.8.4",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.8.4",
|
||||
"@tauri-apps/cli-linux-riscv64-gnu": "2.8.4",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.8.4",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.8.4",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.8.4",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.8.4",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.8.4"
|
||||
"@tauri-apps/cli-darwin-arm64": "2.9.1",
|
||||
"@tauri-apps/cli-darwin-x64": "2.9.1",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.9.1",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.9.1",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.9.1",
|
||||
"@tauri-apps/cli-linux-riscv64-gnu": "2.9.1",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.9.1",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.9.1",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.9.1",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.9.1",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.8.4.tgz",
|
||||
"integrity": "sha512-BKu8HRkYV01SMTa7r4fLx+wjgtRK8Vep7lmBdHDioP6b8XH3q2KgsAyPWfEZaZIkZ2LY4SqqGARaE9oilNe0oA==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.9.1.tgz",
|
||||
"integrity": "sha512-sdwhtsE/6njD0AjgfYEj1JyxZH4SBmCJSXpRm6Ph5fQeuZD6MyjzjdVOrrtFguyREVQ7xn0Ujkwvbo01ULthNg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3168,9 +3169,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.8.4.tgz",
|
||||
"integrity": "sha512-imb9PfSd/7G6VAO7v1bQ2A3ZH4NOCbhGJFLchxzepGcXf9NKkfun157JH9mko29K6sqAwuJ88qtzbKCbWJTH9g==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.9.1.tgz",
|
||||
"integrity": "sha512-c86g+67wTdI4TUCD7CaSd/13+oYuLQxVST4ZNJ5C+6i1kdnU3Us1L68N9MvbDLDQGJc9eo0pvuK6sCWkee+BzA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3185,9 +3186,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.8.4.tgz",
|
||||
"integrity": "sha512-Ml215UnDdl7/fpOrF1CNovym/KjtUbCuPgrcZ4IhqUCnhZdXuphud/JT3E8X97Y03TZ40Sjz8raXYI2ET0exzw==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.9.1.tgz",
|
||||
"integrity": "sha512-IrB3gFQmueQKJjjisOcMktW/Gh6gxgqYO419doA3YZ7yIV5rbE8ZW52Q3I4AO+SlFEyVYer5kpi066p0JBlLGw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -3202,9 +3203,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.8.4.tgz",
|
||||
"integrity": "sha512-pbcgBpMyI90C83CxE5REZ9ODyIlmmAPkkJXtV398X3SgZEIYy5TACYqlyyv2z5yKgD8F8WH4/2fek7+jH+ZXAw==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.9.1.tgz",
|
||||
"integrity": "sha512-Ke7TyXvu6HbWSkmVkFbbH19D3cLsd117YtXP/u9NIvSpYwKeFtnbpirrIUfPm44Q+PZFZ2Hvg8X9qoUiAK0zKw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3219,9 +3220,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.8.4.tgz",
|
||||
"integrity": "sha512-zumFeaU1Ws5Ay872FTyIm7z8kfzEHu8NcIn8M6TxbJs0a7GRV21KBdpW1zNj2qy7HynnpQCqjAYXTUUmm9JAOw==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.9.1.tgz",
|
||||
"integrity": "sha512-sGvy75sv55oeMulR5ArwPD28DsDQxqTzLhXCrpU9/nbFg/JImmI7k994YE9fr3V0qE3Cjk5gjLldRNv7I9sjwQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3236,9 +3237,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.8.4.tgz",
|
||||
"integrity": "sha512-qiqbB3Zz6IyO201f+1ojxLj65WYj8mixL5cOMo63nlg8CIzsP23cPYUrx1YaDPsCLszKZo7tVs14pc7BWf+/aQ==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.9.1.tgz",
|
||||
"integrity": "sha512-tEKbJydV3BdIxpAx8aGHW6VDg1xW4LlQuRD/QeFZdZNTreHJpMbJEcdvAcI+Hg6vgQpVpaoEldR9W4F6dYSLqQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -3253,9 +3254,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.8.4.tgz",
|
||||
"integrity": "sha512-TaqaDd9Oy6k45Hotx3pOf+pkbsxLaApv4rGd9mLuRM1k6YS/aw81YrsMryYPThrxrScEIUcmNIHaHsLiU4GMkw==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.9.1.tgz",
|
||||
"integrity": "sha512-mg5msXHagtHpyCVWgI01M26JeSrgE/otWyGdYcuTwyRYZYEJRTbcNt7hscOkdNlPBe7isScW7PVKbxmAjJJl4g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3270,9 +3271,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.8.4.tgz",
|
||||
"integrity": "sha512-ot9STAwyezN8w+bBHZ+bqSQIJ0qPZFlz/AyscpGqB/JnJQVDFQcRDmUPFEaAtt2UUHSWzN3GoTJ5ypqLBp2WQA==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.9.1.tgz",
|
||||
"integrity": "sha512-lFZEXkpDreUe3zKilvnMsrnKP9gwQudaEjDnOz/GMzbzNceIuPfFZz0cR/ky1Aoq4eSvZonPKHhROq4owz4fzg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3287,9 +3288,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.8.4.tgz",
|
||||
"integrity": "sha512-+2aJ/g90dhLiOLFSD1PbElXX3SoMdpO7HFPAZB+xot3CWlAZD1tReUFy7xe0L5GAR16ZmrxpIDM9v9gn5xRy/w==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.9.1.tgz",
|
||||
"integrity": "sha512-ejc5RAp/Lm1Aj0EQHaT+Wdt5PHfdgQV5hIDV00MV6HNbIb5W4ZUFxMDaRkAg65gl9MvY2fH396riePW3RoKXDw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3304,9 +3305,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.8.4.tgz",
|
||||
"integrity": "sha512-yj7WDxkL1t9Uzr2gufQ1Hl7hrHuFKTNEOyascbc109EoiAqCp0tgZ2IykQqOZmZOHU884UAWI1pVMqBhS/BfhA==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.9.1.tgz",
|
||||
"integrity": "sha512-fSATtJDc0fNjVB6ystyi8NbwhNFk8i8E05h6KrsC8Fio5eaJIJvPCbC9pdrPl6kkxN1X7fj25ErBbgfqgcK8Fg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -3321,9 +3322,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.8.4.tgz",
|
||||
"integrity": "sha512-XuvGB4ehBdd7QhMZ9qbj/8icGEatDuBNxyYHbLKsTYh90ggUlPa/AtaqcC1Fo69lGkTmq9BOKrs1aWSi7xDonA==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.9.1.tgz",
|
||||
"integrity": "sha512-/JHlOzpUDhjBOO9w167bcYxfJbcMQv7ykS/Y07xjtcga8np0rzUzVGWYmLMH7orKcDMC7wjhheEW1x8cbGma/Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4189,6 +4190,10 @@
|
||||
"resolved": "plugins/importer-postman",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaak/importer-postman-environment": {
|
||||
"resolved": "plugins/importer-postman-environment",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaak/importer-yaak": {
|
||||
"resolved": "plugins/importer-yaak",
|
||||
"link": true
|
||||
@@ -17852,9 +17857,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.0.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.7.tgz",
|
||||
"integrity": "sha512-hc6LujN/EkJHmxeiDJMs0qBontZ1cdBvvoCbWhVjzUFTU329VRyOC46gHNSA8NcOC5yzCeXpwI40tieI3DEZqg==",
|
||||
"version": "7.0.8",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.8.tgz",
|
||||
"integrity": "sha512-cJBdq0/u+8rgstg9t7UkBilf8ipLmeXJO30NxD5HAHOivnj10ocV8YtR/XBvd2wQpN3TmcaxNKaHX3tN7o5F5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -18520,26 +18525,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml-formatter": {
|
||||
"version": "3.6.6",
|
||||
"resolved": "https://registry.npmjs.org/xml-formatter/-/xml-formatter-3.6.6.tgz",
|
||||
"integrity": "sha512-yfofQht42x2sN1YThT6Er6GFXiQinfDAsMTNvMPi2uZw5/Vtc2PYHfvALR8U+b2oN2ekBxLd2tGWV06rAM8nQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xml-parser-xo": "^4.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-parser-xo": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/xml-parser-xo/-/xml-parser-xo-4.1.4.tgz",
|
||||
"integrity": "sha512-wo+yWDNeMwd1ctzH4CsiGXaAappDsxuR+VnmPewOzHk/zvefksT2ZlcWpAePl11THOWgnIZM4GjvumevurNWZw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
"node_modules/xml-beautify": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/xml-beautify/-/xml-beautify-1.2.3.tgz",
|
||||
"integrity": "sha512-VsYpkqoVawIP84pi00XukPsgQHqOhgrpwTHlXqqRMAgYZ1u+Yw3KHIUhO1Igf19d5CQ5h6ExJT1hFCJRLmzADg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xpath": {
|
||||
"version": "0.0.34",
|
||||
@@ -18882,6 +18872,10 @@
|
||||
"name": "@yaak/importer-postman",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"plugins/importer-postman-environment": {
|
||||
"name": "@yaak/importer-postman-environment",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"plugins/importer-yaak": {
|
||||
"name": "@yaak/importer-yaak",
|
||||
"version": "0.1.0"
|
||||
@@ -19055,7 +19049,7 @@
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@tanstack/react-router": "^1.133.13",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tauri-apps/api": "^2.8.0",
|
||||
"@tauri-apps/api": "^2.9.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.2",
|
||||
@@ -19095,7 +19089,7 @@
|
||||
"slugify": "^1.6.6",
|
||||
"uuid": "^11.1.0",
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"xml-formatter": "^3.6.3",
|
||||
"xml-beautify": "^1.2.3",
|
||||
"yaml": "^2.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -19119,7 +19113,7 @@
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-nesting": "^13.0.2",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"vite": "^7.0.7",
|
||||
"vite": "^7.0.8",
|
||||
"vite-plugin-static-copy": "^3.1.2",
|
||||
"vite-plugin-svgr": "^4.3.0",
|
||||
"vite-plugin-top-level-await": "^1.5.0",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"plugins/importer-insomnia",
|
||||
"plugins/importer-openapi",
|
||||
"plugins/importer-postman",
|
||||
"plugins/importer-postman-environment",
|
||||
"plugins/importer-yaak",
|
||||
"plugins/template-function-cookie",
|
||||
"plugins/template-function-encode",
|
||||
@@ -80,7 +81,7 @@
|
||||
"@eslint/compat": "^1.3.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.29.0",
|
||||
"@tauri-apps/cli": "^2.8.4",
|
||||
"@tauri-apps/cli": "^2.9.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.27.0",
|
||||
"@typescript-eslint/parser": "^8.27.0",
|
||||
"@yaakapp/cli": "^0.2.7",
|
||||
|
||||
@@ -57,16 +57,17 @@ export const plugin: PluginDefinition = {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Support body signing here
|
||||
headers['x-amz-content-sha256'] = 'UNSIGNED-PAYLOAD';
|
||||
if (args.method !== 'GET') {
|
||||
headers['x-amz-content-sha256'] = 'UNSIGNED-PAYLOAD';
|
||||
}
|
||||
|
||||
const signature = aws4.sign(
|
||||
{
|
||||
host: url.host,
|
||||
method: args.method,
|
||||
path: url.pathname + (url.search || '') || undefined,
|
||||
service: String(values.service || 'sts') || undefined,
|
||||
region: String(values.region || 'us-east-1') || undefined,
|
||||
path: url.pathname + (url.search || ''),
|
||||
service: String(values.service || 'sts'),
|
||||
region: values.region ? String(values.region) : undefined,
|
||||
headers,
|
||||
},
|
||||
{
|
||||
@@ -81,8 +82,6 @@ export const plugin: PluginDefinition = {
|
||||
// - opts.headers["X-Amz-Date"]
|
||||
// - optionally content sha256 header etc
|
||||
|
||||
console.log('ADDING STUFF', signature);
|
||||
|
||||
if (signature.headers == null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -184,6 +184,7 @@ function importEnvironment(
|
||||
workspaceId: string,
|
||||
isParent?: boolean,
|
||||
): PartialImportResources['environments'][0] {
|
||||
isParent ??= e.parentId === workspaceId;
|
||||
return {
|
||||
id: convertId(e._id),
|
||||
createdAt: e.created ? new Date(e.created).toISOString().replace('Z', '') : undefined,
|
||||
@@ -192,7 +193,8 @@ function importEnvironment(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
sortPriority: e.metaSortKey, // Will be added to Yaak later
|
||||
base: isParent ?? e.parentId === workspaceId,
|
||||
parentModel: isParent ? 'workspace' : 'environment',
|
||||
parentId: null,
|
||||
model: 'environment',
|
||||
name: e.name,
|
||||
variables: Object.entries(e.data).map(([name, value]) => ({
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"createdAt": "2025-01-13T15:15:43.767",
|
||||
"updatedAt": "2025-01-13T15:15:55.209",
|
||||
"sortPriority": 1736781343767,
|
||||
"base": true,
|
||||
"parentId": null,
|
||||
"parentModel": "workspace",
|
||||
"id": "GENERATE_ID::env_16c0dec5b77c414ae0e419b8f10c3701300c5900",
|
||||
"model": "environment",
|
||||
"name": "Base Environment",
|
||||
@@ -22,7 +23,8 @@
|
||||
"createdAt": "2025-01-13T15:15:58.515",
|
||||
"updatedAt": "2025-01-13T15:16:34.705",
|
||||
"sortPriority": 1736781358515,
|
||||
"base": false,
|
||||
"parentId": null,
|
||||
"parentModel": "environment",
|
||||
"id": "GENERATE_ID::env_799ae3d723ef44af91b4817e5d057e6d",
|
||||
"model": "environment",
|
||||
"name": "Production",
|
||||
@@ -39,7 +41,8 @@
|
||||
"createdAt": "2025-01-13T15:16:14.707",
|
||||
"updatedAt": "2025-01-13T15:16:31.078",
|
||||
"sortPriority": 1736781358565,
|
||||
"base": false,
|
||||
"parentId": null,
|
||||
"parentModel": "environment",
|
||||
"id": "GENERATE_ID::env_030fbfdbb274426ebd78e2e6518f8553",
|
||||
"model": "environment",
|
||||
"name": "Staging",
|
||||
|
||||
14
plugins/importer-postman-environment/package.json
Normal file
14
plugins/importer-postman-environment/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@yaak/importer-postman-environment",
|
||||
"displayName": "Postman Environment Importer",
|
||||
"description": "Import environments from Postman",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"main": "./build/index.js",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx",
|
||||
"test": "vitest --run tests"
|
||||
}
|
||||
}
|
||||
138
plugins/importer-postman-environment/src/index.ts
Normal file
138
plugins/importer-postman-environment/src/index.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import type {
|
||||
Context,
|
||||
Environment,
|
||||
PartialImportResources,
|
||||
PluginDefinition,
|
||||
Workspace,
|
||||
} from '@yaakapp/api';
|
||||
import type { ImportPluginResponse } from '@yaakapp/api/lib/plugins/ImporterPlugin';
|
||||
|
||||
type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>;
|
||||
|
||||
interface ExportResources {
|
||||
workspaces: AtLeast<Workspace, 'name' | 'id' | 'model'>[];
|
||||
environments: AtLeast<Environment, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
}
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
importer: {
|
||||
name: 'Postman Environment',
|
||||
description: 'Import postman environment exports',
|
||||
onImport(_ctx: Context, args: { text: string }) {
|
||||
return convertPostmanEnvironment(args.text);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function convertPostmanEnvironment(contents: string): ImportPluginResponse | undefined {
|
||||
const root = parseJSONToRecord(contents);
|
||||
if (root == null) return;
|
||||
|
||||
// Validate that it looks like a Postman Environment export
|
||||
const values = toArray<{
|
||||
key?: string;
|
||||
value?: unknown;
|
||||
enabled?: boolean;
|
||||
description?: string;
|
||||
type?: string;
|
||||
}>(root.values);
|
||||
const scope = root._postman_variable_scope;
|
||||
const hasEnvMarkers = typeof scope === 'string';
|
||||
|
||||
if (values.length === 0 || (!hasEnvMarkers && typeof root.name !== 'string')) {
|
||||
// Not a Postman environment file, skip
|
||||
return;
|
||||
}
|
||||
|
||||
const exportResources: ExportResources = {
|
||||
workspaces: [],
|
||||
environments: [],
|
||||
};
|
||||
|
||||
const envVariables = values
|
||||
.map((v) => ({
|
||||
enabled: v.enabled ?? true,
|
||||
name: String(v.key ?? ''),
|
||||
value: String(v.value),
|
||||
description: v.description ? String(v.description) : null,
|
||||
}))
|
||||
.filter((v) => v.name.length > 0);
|
||||
|
||||
const environment: ExportResources['environments'][0] = {
|
||||
model: 'environment',
|
||||
id: generateId('environment'),
|
||||
name: root.name ? String(root.name) : 'Environment',
|
||||
workspaceId: 'CURRENT_WORKSPACE',
|
||||
parentModel: 'environment',
|
||||
parentId: null,
|
||||
variables: envVariables,
|
||||
};
|
||||
exportResources.environments.push(environment);
|
||||
|
||||
const resources = deleteUndefinedAttrs(
|
||||
convertTemplateSyntax(exportResources),
|
||||
) as PartialImportResources;
|
||||
|
||||
return { resources };
|
||||
}
|
||||
|
||||
function parseJSONToRecord<T>(jsonStr: string): Record<string, T> | null {
|
||||
try {
|
||||
return toRecord(JSON.parse(jsonStr));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function toRecord<T>(value: Record<string, T> | unknown): Record<string, T> {
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
return value as Record<string, T>;
|
||||
}
|
||||
return {} as Record<string, T>;
|
||||
}
|
||||
|
||||
function toArray<T>(value: unknown): T[] {
|
||||
if (Object.prototype.toString.call(value) === '[object Array]') return value as T[];
|
||||
else return [] as T[];
|
||||
}
|
||||
|
||||
/** Recursively render all nested object properties */
|
||||
function convertTemplateSyntax<T>(obj: T): T {
|
||||
if (typeof obj === 'string') {
|
||||
return obj.replace(
|
||||
/{{\s*(_\.)?([^}]*)\s*}}/g,
|
||||
(_m, _dot, expr) => '${[' + expr.trim() + ']}',
|
||||
) as T;
|
||||
} else if (Array.isArray(obj) && obj != null) {
|
||||
return obj.map(convertTemplateSyntax) as T;
|
||||
} else if (typeof obj === 'object' && obj != null) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj as Record<string, unknown>).map(([k, v]) => [k, convertTemplateSyntax(v)]),
|
||||
) as T;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
function deleteUndefinedAttrs<T>(obj: T): T {
|
||||
if (Array.isArray(obj) && obj != null) {
|
||||
return obj.map(deleteUndefinedAttrs) as T;
|
||||
} else if (typeof obj === 'object' && obj != null) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj as Record<string, unknown>)
|
||||
.filter(([, v]) => v !== undefined)
|
||||
.map(([k, v]) => [k, deleteUndefinedAttrs(v)]),
|
||||
) as T;
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
const idCount: Partial<Record<string, number>> = {};
|
||||
|
||||
function generateId(model: string): string {
|
||||
idCount[model] = (idCount[model] ?? -1) + 1;
|
||||
return `GENERATE_ID::${model.toUpperCase()}_${idCount[model]}`;
|
||||
}
|
||||
|
||||
export default plugin;
|
||||
27
plugins/importer-postman-environment/tests/fixtures/environment.input.json
vendored
Normal file
27
plugins/importer-postman-environment/tests/fixtures/environment.input.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"id": "123",
|
||||
"name": "My Environment",
|
||||
"values": [
|
||||
{
|
||||
"key": "baseUrl",
|
||||
"value": "https://api.example.com",
|
||||
"type": "default",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"key": "token",
|
||||
"value": "{{ access_token }}",
|
||||
"type": "default",
|
||||
"description": "Access token for the API.",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"key": "disabled",
|
||||
"type": "secret",
|
||||
"value": "hello",
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
"_postman_variable_scope": "environment",
|
||||
"_postman_exported_using": "PostmanRuntime/1.0.0"
|
||||
}
|
||||
35
plugins/importer-postman-environment/tests/fixtures/environment.output.json
vendored
Normal file
35
plugins/importer-postman-environment/tests/fixtures/environment.output.json
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"resources": {
|
||||
"workspaces": [],
|
||||
"environments": [
|
||||
{
|
||||
"id": "GENERATE_ID::ENVIRONMENT_0",
|
||||
"model": "environment",
|
||||
"name": "My Environment",
|
||||
"variables": [
|
||||
{
|
||||
"enabled": true,
|
||||
"description": null,
|
||||
"name": "baseUrl",
|
||||
"value": "https://api.example.com"
|
||||
},
|
||||
{
|
||||
"enabled": true,
|
||||
"description": "Access token for the API.",
|
||||
"name": "token",
|
||||
"value": "${[access_token]}"
|
||||
},
|
||||
{
|
||||
"enabled": false,
|
||||
"description": null,
|
||||
"name": "disabled",
|
||||
"value": "hello"
|
||||
}
|
||||
],
|
||||
"workspaceId": "CURRENT_WORKSPACE",
|
||||
"parentId": null,
|
||||
"parentModel": "environment"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
22
plugins/importer-postman-environment/tests/index.test.ts
Normal file
22
plugins/importer-postman-environment/tests/index.test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { convertPostmanEnvironment } from '../src';
|
||||
|
||||
describe('importer-postman-environment', () => {
|
||||
const p = path.join(__dirname, 'fixtures');
|
||||
const fixtures = fs.readdirSync(p);
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
if (fixture.includes('.output')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
test('Imports ' + fixture, () => {
|
||||
const contents = fs.readFileSync(path.join(p, fixture), 'utf-8');
|
||||
const expected = fs.readFileSync(path.join(p, fixture.replace('.input', '.output')), 'utf-8');
|
||||
const result = convertPostmanEnvironment(contents);
|
||||
expect(result).toEqual(JSON.parse(expected));
|
||||
});
|
||||
}
|
||||
});
|
||||
3
plugins/importer-postman-environment/tsconfig.json
Normal file
3
plugins/importer-postman-environment/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json"
|
||||
}
|
||||
@@ -376,7 +376,7 @@ function toArray<T>(value: unknown): T[] {
|
||||
/** Recursively render all nested object properties */
|
||||
function convertTemplateSyntax<T>(obj: T): T {
|
||||
if (typeof obj === 'string') {
|
||||
return obj.replace(/{{\s*(_\.)?([^}]+)\s*}}/g, '${[$2]}') as T;
|
||||
return obj.replace(/{{\s*(_\.)?([^}]*)\s*}}/g, (_m, _dot, expr) => '${[' + expr.trim() + ']}') as T;
|
||||
} else if (Array.isArray(obj) && obj != null) {
|
||||
return obj.map(convertTemplateSyntax) as T;
|
||||
} else if (typeof obj === 'object' && obj != null) {
|
||||
|
||||
299
src-tauri/Cargo.lock
generated
299
src-tauri/Cargo.lock
generated
@@ -2,15 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
@@ -85,12 +76,6 @@ dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_log-sys"
|
||||
version = "0.3.2"
|
||||
@@ -428,21 +413,6 @@ dependencies = [
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base32"
|
||||
version = "0.5.1"
|
||||
@@ -717,7 +687,7 @@ dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -816,17 +786,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
version = "0.4.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1290,7 +1259,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2002,12 +1971,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.18.4"
|
||||
@@ -2389,9 +2352,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.14"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
|
||||
checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
@@ -2405,7 +2368,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"socket2 0.6.1",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
@@ -3205,7 +3168,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -3628,15 +3591,6 @@ dependencies = [
|
||||
"objc2-security",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
@@ -3782,7 +3736,7 @@ dependencies = [
|
||||
"objc2-osa-kit",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4334,8 +4288,8 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"thiserror 2.0.12",
|
||||
"socket2 0.5.10",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
@@ -4356,7 +4310,7 @@ dependencies = [
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
@@ -4371,7 +4325,7 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
@@ -4552,7 +4506,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4766,12 +4720,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
@@ -4815,9 +4763,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.27"
|
||||
version = "0.23.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
|
||||
checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
@@ -4851,9 +4799,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eda84358ed17f1f354cf4b1909ad346e6c7bc2513e8c40eb08e0157aa13a9070"
|
||||
checksum = "be59af91596cac372a6942530653ad0c3a246cdd491aaa9dcaee47f88d67d5a0"
|
||||
dependencies = [
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
@@ -4878,9 +4826,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.3"
|
||||
version = "0.103.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
|
||||
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@@ -4981,7 +4929,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.101",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5065,9 +5013,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.226"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
@@ -5107,18 +5055,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.226"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.226"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -5138,14 +5086,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.140"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5392,6 +5341,16 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "softbuffer"
|
||||
version = "0.4.6"
|
||||
@@ -5587,9 +5546,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tao"
|
||||
version = "0.34.3"
|
||||
version = "0.34.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "959469667dbcea91e5485fc48ba7dd6023face91bb0f1a14681a70f99847c3f7"
|
||||
checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"block2 0.6.1",
|
||||
@@ -5661,9 +5620,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.8.5"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d1d3b3dc4c101ac989fd7db77e045cc6d91a25349cd410455cb5c57d510c1c"
|
||||
checksum = "7f07c6590706b2fc0ab287b041cf5ce9c435b3850bdae5571e19d9d27584e89d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -5701,7 +5660,7 @@ dependencies = [
|
||||
"tauri-runtime",
|
||||
"tauri-runtime-wry",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tray-icon",
|
||||
"url",
|
||||
@@ -5714,9 +5673,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "2.4.1"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c432ccc9ff661803dab74c6cd78de11026a578a9307610bbc39d3c55be7943f"
|
||||
checksum = "f71be1f494b683ac439e6d61c16ab5c472c6f9c6ee78995b29556d9067c021a1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@@ -5736,9 +5695,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a"
|
||||
checksum = "6c1fe64c74cc40f90848281a90058a6db931eb400b60205840e09801ee30f190"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"brotli",
|
||||
@@ -5754,7 +5713,7 @@ dependencies = [
|
||||
"sha2",
|
||||
"syn 2.0.101",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"time",
|
||||
"url",
|
||||
"uuid",
|
||||
@@ -5763,9 +5722,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e"
|
||||
checksum = "260c5d2eb036b76206b9fca20b7be3614cfd21046c5396f7959e0e64a4b07f2f"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@@ -5777,9 +5736,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f"
|
||||
checksum = "3d7ce9aab979296b2f91e6fbf154207c2e3512b12ddca0b24bfa0e0cde6b2976"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"glob",
|
||||
@@ -5804,7 +5763,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5821,7 +5780,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"url",
|
||||
"windows-registry",
|
||||
@@ -5842,7 +5801,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-fs",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -5863,7 +5822,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"toml 0.9.5",
|
||||
"url",
|
||||
]
|
||||
@@ -5886,7 +5845,7 @@ dependencies = [
|
||||
"swift-rs",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"time",
|
||||
]
|
||||
|
||||
@@ -5906,7 +5865,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"url",
|
||||
"windows",
|
||||
"zbus",
|
||||
@@ -5927,7 +5886,7 @@ dependencies = [
|
||||
"sys-locale",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5947,7 +5906,7 @@ dependencies = [
|
||||
"shared_child",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -5961,7 +5920,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin-deep-link",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
"zbus",
|
||||
@@ -5991,7 +5950,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"time",
|
||||
"tokio",
|
||||
"url",
|
||||
@@ -6011,14 +5970,14 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.8.0"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846"
|
||||
checksum = "3367f0b47df90e9195cd9f04a56b0055a2cba45aa11923c6c253d748778176fc"
|
||||
dependencies = [
|
||||
"cookie",
|
||||
"dpi",
|
||||
@@ -6032,7 +5991,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
@@ -6041,9 +6000,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "2.8.1"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807"
|
||||
checksum = "80d91d29ca680c545364cf75ba2f2e3c7ea2ab6376bfa3be26b56fa2463a5b5e"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
@@ -6068,9 +6027,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "2.7.0"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212"
|
||||
checksum = "f6b8bbe426abdbf52d050e52ed693130dbd68375b9ad82a3fb17efb4c8d85673"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"brotli",
|
||||
@@ -6096,7 +6055,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"swift-rs",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"toml 0.9.5",
|
||||
"url",
|
||||
"urlpattern",
|
||||
@@ -6159,11 +6118,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.12",
|
||||
"thiserror-impl 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6179,9 +6138,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.12"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6268,27 +6227,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.45.1"
|
||||
version = "1.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
|
||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"socket2 0.6.1",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6478,7 +6436,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower 0.4.13",
|
||||
@@ -6614,7 +6572,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -6645,21 +6603,21 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "11.0.1"
|
||||
version = "11.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be"
|
||||
checksum = "4994acea2522cd2b3b85c1d9529a55991e3ad5e25cdcd3de9d505972c4379424"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"ts-rs-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "11.0.1"
|
||||
version = "11.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a"
|
||||
checksum = "ee6ff59666c9cbaec3533964505d39154dc4e0a56151fdea30a09ed0301f62e2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6682,7 +6640,7 @@ dependencies = [
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"sha1",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
@@ -7204,7 +7162,7 @@ version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c"
|
||||
dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"windows",
|
||||
"windows-core",
|
||||
]
|
||||
@@ -7270,7 +7228,7 @@ dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core",
|
||||
"windows-future",
|
||||
"windows-link",
|
||||
"windows-link 0.1.1",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
@@ -7291,7 +7249,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-link 0.1.1",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
@@ -7303,7 +7261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
"windows-link 0.1.1",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
@@ -7335,6 +7293,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.2.0"
|
||||
@@ -7342,7 +7306,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
"windows-link 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7351,7 +7315,7 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.1",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
@@ -7362,7 +7326,7 @@ version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7371,7 +7335,7 @@ version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7410,6 +7374,15 @@ dependencies = [
|
||||
"windows-targets 0.53.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
@@ -7478,7 +7451,7 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7487,7 +7460,7 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-link 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7718,7 +7691,7 @@ dependencies = [
|
||||
"os_pipe",
|
||||
"rustix 0.38.44",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"tree_magic_mini",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
@@ -7734,9 +7707,9 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.53.3"
|
||||
version = "0.53.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31f0e9642a0d061f6236c54ccae64c2722a7879ad4ec7dff59bd376d446d8e90"
|
||||
checksum = "6d78ec082b80fa088569a970d043bb3050abaabf4454101d44514ee8d9a8c9f6"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.6.1",
|
||||
@@ -7766,7 +7739,7 @@ dependencies = [
|
||||
"sha2",
|
||||
"soup3",
|
||||
"tao-macros",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webkit2gtk-sys",
|
||||
@@ -7843,6 +7816,7 @@ dependencies = [
|
||||
"cookie",
|
||||
"eventsource-client",
|
||||
"http",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"md5 0.8.0",
|
||||
"mime_guess",
|
||||
@@ -7865,9 +7839,10 @@ dependencies = [
|
||||
"tauri-plugin-single-instance",
|
||||
"tauri-plugin-updater",
|
||||
"tauri-plugin-window-state",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tower-service",
|
||||
"ts-rs",
|
||||
"uuid",
|
||||
"yaak-common",
|
||||
@@ -7894,7 +7869,7 @@ dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"tauri",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7909,7 +7884,7 @@ dependencies = [
|
||||
"serde",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"yaak-models",
|
||||
]
|
||||
|
||||
@@ -7921,7 +7896,7 @@ dependencies = [
|
||||
"serde",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
@@ -7937,7 +7912,7 @@ dependencies = [
|
||||
"serde_yaml",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"ts-rs",
|
||||
"yaak-models",
|
||||
"yaak-sync",
|
||||
@@ -7991,7 +7966,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"ts-rs",
|
||||
"yaak-common",
|
||||
"yaak-models",
|
||||
@@ -8030,9 +8005,9 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-dialog",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"thiserror 2.0.17",
|
||||
"ts-rs",
|
||||
"yaak-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8057,7 +8032,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-shell",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"ts-rs",
|
||||
@@ -8091,7 +8066,7 @@ dependencies = [
|
||||
"sha1",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"ts-rs",
|
||||
"yaak-models",
|
||||
@@ -8106,7 +8081,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"ts-rs",
|
||||
"wasm-bindgen",
|
||||
@@ -8124,7 +8099,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"yaak-http",
|
||||
@@ -8345,7 +8320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aed5f10c571472911e37d8f7601a8dfba52b4f7f73a344015291b82ab292faf6"
|
||||
dependencies = [
|
||||
"log",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.17",
|
||||
"zip",
|
||||
]
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ updater = []
|
||||
license = ["yaak-license"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.4.1", features = [] }
|
||||
tauri-build = { version = "2.5.0", features = [] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
openssl-sys = { version = "0.9.105", features = ["vendored"] } # For Ubuntu installation to work
|
||||
@@ -68,6 +68,8 @@ tauri-plugin-shell = { workspace = true }
|
||||
tauri-plugin-single-instance = { version = "2.3.4", features = ["deep-link"] }
|
||||
tauri-plugin-updater = "2.9.0"
|
||||
tauri-plugin-window-state = "2.4.0"
|
||||
hyper-util = { version = "0.1.17", default-features = false, features = ["client-legacy"] }
|
||||
tower-service = "0.3.3"
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
tokio-stream = "0.1.17"
|
||||
@@ -89,23 +91,23 @@ yaak-templates = { workspace = true }
|
||||
yaak-ws = { path = "yaak-ws" }
|
||||
|
||||
[workspace.dependencies]
|
||||
chrono = "0.4.41"
|
||||
chrono = "0.4.42"
|
||||
hex = "0.4.3"
|
||||
keyring = "3.6.3"
|
||||
reqwest = "0.12.20"
|
||||
reqwest_cookie_store = "0.8.0"
|
||||
rustls = { version = "0.23.27", default-features = false }
|
||||
rustls-platform-verifier = "0.6.0"
|
||||
serde = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
rustls = { version = "0.23.33", default-features = false }
|
||||
rustls-platform-verifier = "0.6.1"
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.145"
|
||||
sha2 = "0.10.9"
|
||||
tauri = "2.8.5"
|
||||
tauri-plugin = "2.4.0"
|
||||
tauri = "2.9.0"
|
||||
tauri-plugin = "2.5.0"
|
||||
tauri-plugin-dialog = "2.4.0"
|
||||
tauri-plugin-shell = "2.3.1"
|
||||
thiserror = "2.0.12"
|
||||
tokio = "1.45.1"
|
||||
ts-rs = "11.0.1"
|
||||
thiserror = "2.0.17"
|
||||
tokio = "1.48.0"
|
||||
ts-rs = "11.1.0"
|
||||
yaak-common = { path = "yaak-common" }
|
||||
yaak-crypto = { path = "yaak-crypto" }
|
||||
yaak-fonts = { path = "yaak-fonts" }
|
||||
|
||||
54
src-tauri/src/dns.rs
Normal file
54
src-tauri/src/dns.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use hyper_util::client::legacy::connect::dns::{
|
||||
GaiResolver as HyperGaiResolver, Name as HyperName,
|
||||
};
|
||||
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tower_service::Service;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct LocalhostResolver {
|
||||
fallback: HyperGaiResolver,
|
||||
}
|
||||
|
||||
impl LocalhostResolver {
|
||||
pub fn new() -> Arc<Self> {
|
||||
let resolver = HyperGaiResolver::new();
|
||||
Arc::new(Self { fallback: resolver })
|
||||
}
|
||||
}
|
||||
|
||||
impl Resolve for LocalhostResolver {
|
||||
fn resolve(&self, name: Name) -> Resolving {
|
||||
let host = name.as_str().to_lowercase();
|
||||
|
||||
let is_localhost = host.ends_with(".localhost");
|
||||
if is_localhost {
|
||||
// Port 0 is fine; reqwest replaces it with the URL's explicit
|
||||
// port or the scheme’s default (80/443, etc.).
|
||||
// (See docs note below.)
|
||||
let addrs: Vec<SocketAddr> = vec![
|
||||
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
|
||||
SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 0),
|
||||
];
|
||||
|
||||
return Box::pin(async move {
|
||||
Ok::<Addrs, Box<dyn std::error::Error + Send + Sync>>(Box::new(addrs.into_iter()))
|
||||
});
|
||||
}
|
||||
|
||||
let mut fallback = self.fallback.clone();
|
||||
let name_str = name.as_str().to_string();
|
||||
Box::pin(async move {
|
||||
match HyperName::from_str(&name_str) {
|
||||
Ok(n) => fallback
|
||||
.call(n)
|
||||
.await
|
||||
.map(|addrs| Box::new(addrs) as Addrs)
|
||||
.map_err(|err| Box::new(err) as Box<dyn std::error::Error + Send + Sync>),
|
||||
Err(e) => Err(Box::new(e) as Box<dyn std::error::Error + Send + Sync>),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ use yaak_plugins::events::{
|
||||
use yaak_plugins::manager::PluginManager;
|
||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||
use yaak_templates::{RenderErrorBehavior, RenderOptions};
|
||||
use crate::dns::LocalhostResolver;
|
||||
|
||||
pub async fn send_http_request<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
@@ -110,6 +111,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
.gzip(true)
|
||||
.brotli(true)
|
||||
.deflate(true)
|
||||
.dns_resolver(LocalhostResolver::new())
|
||||
.referer(false)
|
||||
.tls_info(true);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ pub(crate) async fn import_data<R: Runtime>(
|
||||
.workspaces
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<Workspace>(v.id.as_str(), &mut id_map);
|
||||
v.id = maybe_gen_id::<Workspace, R>(window, v.id.as_str(), &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
@@ -37,11 +37,12 @@ pub(crate) async fn import_data<R: Runtime>(
|
||||
.environments
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<Environment>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.id = maybe_gen_id::<Environment, R>(window, v.id.as_str(), &mut id_map);
|
||||
v.workspace_id =
|
||||
maybe_gen_id::<Workspace, R>(window, v.workspace_id.as_str(), &mut id_map);
|
||||
match (v.parent_model.as_str(), v.parent_id.clone().as_deref()) {
|
||||
("folder", Some(parent_id)) => {
|
||||
v.parent_id = Some(maybe_gen_id::<Folder>(&parent_id, &mut id_map));
|
||||
v.parent_id = Some(maybe_gen_id::<Folder, R>(window, &parent_id, &mut id_map));
|
||||
}
|
||||
("", _) => {
|
||||
// Fix any empty ones
|
||||
@@ -60,9 +61,10 @@ pub(crate) async fn import_data<R: Runtime>(
|
||||
.folders
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<Folder>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
||||
v.id = maybe_gen_id::<Folder, R>(window, v.id.as_str(), &mut id_map);
|
||||
v.workspace_id =
|
||||
maybe_gen_id::<Workspace, R>(window, v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder, R>(window, v.folder_id, &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
@@ -71,9 +73,10 @@ pub(crate) async fn import_data<R: Runtime>(
|
||||
.http_requests
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<HttpRequest>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
||||
v.id = maybe_gen_id::<HttpRequest, R>(window, v.id.as_str(), &mut id_map);
|
||||
v.workspace_id =
|
||||
maybe_gen_id::<Workspace, R>(window, v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder, R>(window, v.folder_id, &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
@@ -82,9 +85,10 @@ pub(crate) async fn import_data<R: Runtime>(
|
||||
.grpc_requests
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<GrpcRequest>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
||||
v.id = maybe_gen_id::<GrpcRequest, R>(window, v.id.as_str(), &mut id_map);
|
||||
v.workspace_id =
|
||||
maybe_gen_id::<Workspace, R>(window, v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder, R>(window, v.folder_id, &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
@@ -93,9 +97,10 @@ pub(crate) async fn import_data<R: Runtime>(
|
||||
.websocket_requests
|
||||
.into_iter()
|
||||
.map(|mut v| {
|
||||
v.id = maybe_gen_id::<WebsocketRequest>(v.id.as_str(), &mut id_map);
|
||||
v.workspace_id = maybe_gen_id::<Workspace>(v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder>(v.folder_id, &mut id_map);
|
||||
v.id = maybe_gen_id::<WebsocketRequest, R>(window, v.id.as_str(), &mut id_map);
|
||||
v.workspace_id =
|
||||
maybe_gen_id::<Workspace, R>(window, v.workspace_id.as_str(), &mut id_map);
|
||||
v.folder_id = maybe_gen_id_opt::<Folder, R>(window, v.folder_id, &mut id_map);
|
||||
v
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -45,6 +45,7 @@ use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||
use yaak_sse::sse::ServerSentEvent;
|
||||
use yaak_templates::format::format_json;
|
||||
use yaak_templates::{RenderErrorBehavior, RenderOptions, Tokens, transform_args};
|
||||
use yaak_templates::format_xml::format_xml;
|
||||
|
||||
mod commands;
|
||||
mod encoding;
|
||||
@@ -60,6 +61,7 @@ mod updates;
|
||||
mod uri_scheme;
|
||||
mod window;
|
||||
mod window_menu;
|
||||
mod dns;
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
@@ -738,6 +740,11 @@ async fn cmd_format_json(text: &str) -> YaakResult<String> {
|
||||
Ok(format_json(text, " "))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_format_xml(text: &str) -> YaakResult<String> {
|
||||
Ok(format_xml(text, " "))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_http_response_body<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
@@ -1414,6 +1421,7 @@ pub fn run() {
|
||||
cmd_export_data,
|
||||
cmd_http_response_body,
|
||||
cmd_format_json,
|
||||
cmd_format_xml,
|
||||
cmd_get_http_authentication_summaries,
|
||||
cmd_get_http_authentication_config,
|
||||
cmd_get_sse_events,
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"createUpdaterArtifacts": "v1Compatible",
|
||||
"createUpdaterArtifacts": true,
|
||||
"windows": {
|
||||
"signCommand": "trusted-signing-cli -e https://eus.codesigning.azure.net/ -a Yaak -c yaakapp %1"
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ sha2 = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-dialog = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
ts-rs = { workspace = true, features = ["chrono-impl", "serde-json-impl"] }
|
||||
yaak-common = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { workspace = true, features = ["build"] }
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::models::{
|
||||
AnyModel, Environment, Folder, GrpcRequest, HttpRequest, UpsertModelInfo, WebsocketRequest,
|
||||
Workspace, WorkspaceIden,
|
||||
};
|
||||
use yaak_common::window::WorkspaceWindowTrait;
|
||||
use crate::query_manager::QueryManagerExt;
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use log::warn;
|
||||
@@ -158,7 +159,17 @@ pub fn get_workspace_export_resources<R: Runtime>(
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn maybe_gen_id<M: UpsertModelInfo>(id: &str, ids: &mut BTreeMap<String, String>) -> String {
|
||||
pub fn maybe_gen_id<M: UpsertModelInfo, R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
id: &str,
|
||||
ids: &mut BTreeMap<String, String>,
|
||||
) -> String {
|
||||
if id == "CURRENT_WORKSPACE" {
|
||||
if let Some(wid) = window.workspace_id() {
|
||||
return wid.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
if !id.starts_with("GENERATE_ID::") {
|
||||
return id.to_string();
|
||||
}
|
||||
@@ -173,12 +184,13 @@ pub fn maybe_gen_id<M: UpsertModelInfo>(id: &str, ids: &mut BTreeMap<String, Str
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_gen_id_opt<M: UpsertModelInfo>(
|
||||
pub fn maybe_gen_id_opt<M: UpsertModelInfo, R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
id: Option<String>,
|
||||
ids: &mut BTreeMap<String, String>,
|
||||
) -> Option<String> {
|
||||
match id {
|
||||
Some(id) => Some(maybe_gen_id::<M>(id.as_str(), ids)),
|
||||
Some(id) => Some(maybe_gen_id::<M, R>(window, id.as_str(), ids)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use reqwest::{Response, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use tauri::{AppHandle, Runtime, is_dev};
|
||||
use tauri::{AppHandle, Runtime};
|
||||
use ts_rs::TS;
|
||||
use yaak_common::api_client::yaak_api_client;
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
|
||||
@@ -503,10 +503,22 @@ impl PluginManager {
|
||||
.iter()
|
||||
.find(|r| r.functions.iter().any(|f| f.name == fn_name))
|
||||
.ok_or_else(|| PluginNotFoundErr(fn_name.into()))?;
|
||||
let plugin = self
|
||||
.get_plugin_by_ref_id(&r.plugin_ref_id)
|
||||
.await
|
||||
.ok_or_else(|| PluginNotFoundErr(r.plugin_ref_id.clone()))?;
|
||||
|
||||
let plugin = match self.get_plugin_by_ref_id(&r.plugin_ref_id).await {
|
||||
None => {
|
||||
// It's probably a native function, so just fallback to the summary
|
||||
let function = r
|
||||
.functions
|
||||
.iter()
|
||||
.find(|f| f.name == fn_name)
|
||||
.ok_or_else(|| PluginNotFoundErr(fn_name.into()))?;
|
||||
return Ok(GetTemplateFunctionConfigResponse {
|
||||
function: function.clone(),
|
||||
plugin_ref_id: r.plugin_ref_id.clone(),
|
||||
});
|
||||
}
|
||||
Some(v) => v,
|
||||
};
|
||||
|
||||
let window_context = &PluginWindowContext::new(&window);
|
||||
let vars = &make_vars_hashmap(environment_chain);
|
||||
|
||||
@@ -78,6 +78,10 @@ pub fn template_function_secure_run<R: Runtime>(
|
||||
_ => return Ok("".to_string()),
|
||||
};
|
||||
|
||||
if value.is_empty() {
|
||||
return Ok("".to_string());
|
||||
}
|
||||
|
||||
let value = match value.strip_prefix("YENC_") {
|
||||
None => {
|
||||
return Err(RenderError("Could not decrypt non-encrypted value".to_string()));
|
||||
|
||||
345
src-tauri/yaak-templates/src/format_xml.rs
Normal file
345
src-tauri/yaak-templates/src/format_xml.rs
Normal file
@@ -0,0 +1,345 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum XmlTok<'a> {
|
||||
OpenTag { raw: &'a str, name: &'a str }, // "<tag ...>"
|
||||
CloseTag { raw: &'a str, name: &'a str }, // "</tag>"
|
||||
SelfCloseTag(&'a str), // "<tag .../>"
|
||||
Comment(&'a str), // "<!-- ... -->"
|
||||
CData(&'a str), // "<![CDATA[ ... ]]>"
|
||||
ProcInst(&'a str), // "<?xml ...?>"
|
||||
Doctype(&'a str), // "<!DOCTYPE ...>"
|
||||
Text(&'a str), // "text between tags"
|
||||
Template(&'a str), // "${[ ... ]}"
|
||||
}
|
||||
|
||||
fn writeln_indented(out: &mut String, depth: usize, indent: &str, s: &str) {
|
||||
for _ in 0..depth {
|
||||
out.push_str(indent);
|
||||
}
|
||||
out.push_str(s);
|
||||
out.push('\n');
|
||||
}
|
||||
|
||||
pub fn format_xml(input: &str, indent: &str) -> String {
|
||||
use XmlTok::*;
|
||||
let tokens = tokenize_with_templates(input);
|
||||
|
||||
let mut out = String::new();
|
||||
let mut depth = 0usize;
|
||||
let mut i = 0usize;
|
||||
|
||||
while i < tokens.len() {
|
||||
match tokens[i] {
|
||||
OpenTag {
|
||||
raw: open_raw,
|
||||
name: open_name,
|
||||
} => {
|
||||
if i + 2 < tokens.len() {
|
||||
if let Text(text_raw) = tokens[i + 1] {
|
||||
let trimmed = text_raw.trim();
|
||||
let no_newlines = !trimmed.contains('\n');
|
||||
if no_newlines && !trimmed.is_empty() {
|
||||
if let CloseTag {
|
||||
raw: close_raw,
|
||||
name: close_name,
|
||||
} = tokens[i + 2]
|
||||
{
|
||||
if open_name == close_name {
|
||||
for _ in 0..depth {
|
||||
out.push_str(indent);
|
||||
}
|
||||
out.push_str(open_raw);
|
||||
out.push_str(trimmed);
|
||||
out.push_str(close_raw);
|
||||
out.push('\n');
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
writeln_indented(&mut out, depth, indent, open_raw);
|
||||
depth = depth.saturating_add(1);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
CloseTag { raw, .. } => {
|
||||
depth = depth.saturating_sub(1);
|
||||
writeln_indented(&mut out, depth, indent, raw);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
SelfCloseTag(raw) | Comment(raw) | ProcInst(raw) | Doctype(raw) | CData(raw)
|
||||
| Template(raw) => {
|
||||
writeln_indented(&mut out, depth, indent, raw);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
Text(text_raw) => {
|
||||
if text_raw.chars().any(|c| !c.is_whitespace()) {
|
||||
let trimmed = text_raw.trim();
|
||||
writeln_indented(&mut out, depth, indent, trimmed);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if out.ends_with('\n') {
|
||||
out.pop();
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn tokenize_with_templates(input: &str) -> Vec<XmlTok<'_>> {
|
||||
use XmlTok::*;
|
||||
let bytes = input.as_bytes();
|
||||
let mut i = 0usize;
|
||||
let mut toks = Vec::<XmlTok>::new();
|
||||
|
||||
let starts_with =
|
||||
|s: &[u8], i: usize, pat: &str| s.get(i..).map_or(false, |t| t.starts_with(pat.as_bytes()));
|
||||
|
||||
while i < bytes.len() {
|
||||
// Template block: ${[ ... ]}
|
||||
if starts_with(bytes, i, "${[") {
|
||||
let start = i;
|
||||
i += 3;
|
||||
while i < bytes.len() && !starts_with(bytes, i, "]}") {
|
||||
i += 1;
|
||||
}
|
||||
if starts_with(bytes, i, "]}") {
|
||||
i += 2;
|
||||
}
|
||||
toks.push(Template(&input[start..i]));
|
||||
continue;
|
||||
}
|
||||
|
||||
if bytes[i] == b'<' {
|
||||
// Comments
|
||||
if starts_with(bytes, i, "<!--") {
|
||||
let start = i;
|
||||
i += 4;
|
||||
while i < bytes.len() && !starts_with(bytes, i, "-->") {
|
||||
i += 1;
|
||||
}
|
||||
if starts_with(bytes, i, "-->") {
|
||||
i += 3;
|
||||
}
|
||||
toks.push(Comment(&input[start..i]));
|
||||
continue;
|
||||
}
|
||||
// CDATA
|
||||
if starts_with(bytes, i, "<![CDATA[") {
|
||||
let start = i;
|
||||
i += 9;
|
||||
while i < bytes.len() && !starts_with(bytes, i, "]]>") {
|
||||
i += 1;
|
||||
}
|
||||
if starts_with(bytes, i, "]]>") {
|
||||
i += 3;
|
||||
}
|
||||
toks.push(CData(&input[start..i]));
|
||||
continue;
|
||||
}
|
||||
// Processing Instruction
|
||||
if starts_with(bytes, i, "<?") {
|
||||
let start = i;
|
||||
i += 2;
|
||||
while i < bytes.len() && !starts_with(bytes, i, "?>") {
|
||||
i += 1;
|
||||
}
|
||||
if starts_with(bytes, i, "?>") {
|
||||
i += 2;
|
||||
}
|
||||
toks.push(ProcInst(&input[start..i]));
|
||||
continue;
|
||||
}
|
||||
// DOCTYPE or other "<!"
|
||||
if starts_with(bytes, i, "<!") {
|
||||
let start = i;
|
||||
i += 2;
|
||||
while i < bytes.len() && bytes[i] != b'>' {
|
||||
i += 1;
|
||||
}
|
||||
if i < bytes.len() {
|
||||
i += 1;
|
||||
}
|
||||
toks.push(Doctype(&input[start..i]));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Normal tag (open/close/self)
|
||||
let start = i;
|
||||
i += 1; // '<'
|
||||
|
||||
let is_close = if i < bytes.len() && bytes[i] == b'/' {
|
||||
i += 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// read until '>' (respecting quotes)
|
||||
let mut in_quote: Option<u8> = None;
|
||||
while i < bytes.len() {
|
||||
let c = bytes[i];
|
||||
if let Some(q) = in_quote {
|
||||
if c == q {
|
||||
in_quote = None;
|
||||
}
|
||||
i += 1;
|
||||
} else {
|
||||
if c == b'\'' || c == b'"' {
|
||||
in_quote = Some(c);
|
||||
i += 1;
|
||||
} else if c == b'>' {
|
||||
i += 1;
|
||||
break;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let raw = &input[start..i];
|
||||
let is_self = raw.as_bytes().len() >= 2 && raw.as_bytes()[raw.len() - 2] == b'/';
|
||||
if is_close {
|
||||
let name = parse_close_name(raw);
|
||||
toks.push(CloseTag { raw, name });
|
||||
} else if is_self {
|
||||
toks.push(SelfCloseTag(raw));
|
||||
} else {
|
||||
let name = parse_open_name(raw);
|
||||
toks.push(OpenTag { raw, name });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Text node until next '<' or template start
|
||||
let start = i;
|
||||
while i < bytes.len() && bytes[i] != b'<' && !starts_with(bytes, i, "${[") {
|
||||
i += 1;
|
||||
}
|
||||
toks.push(XmlTok::Text(&input[start..i]));
|
||||
}
|
||||
|
||||
toks
|
||||
}
|
||||
|
||||
fn parse_open_name(raw: &str) -> &str {
|
||||
// raw looks like "<name ...>" or "<name>"
|
||||
// slice between '<' and first whitespace or '>' or '/>'
|
||||
let s = &raw[1..]; // skip '<'
|
||||
let end = s.find(|c: char| c.is_whitespace() || c == '>' || c == '/').unwrap_or(s.len());
|
||||
&s[..end]
|
||||
}
|
||||
|
||||
fn parse_close_name(raw: &str) -> &str {
|
||||
// raw looks like "</name>"
|
||||
let s = &raw[2..]; // skip "</"
|
||||
let end = s.find('>').unwrap_or(s.len());
|
||||
&s[..end]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::format_xml;
|
||||
|
||||
#[test]
|
||||
fn inline_text_child() {
|
||||
let src = r#"<root><foo>this might be a string</foo><bar attr="x">ok</bar></root>"#;
|
||||
let want = r#"<root>
|
||||
<foo>this might be a string</foo>
|
||||
<bar attr="x">ok</bar>
|
||||
</root>"#;
|
||||
assert_eq!(format_xml(src, " "), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn works_when_nested() {
|
||||
let src = r#"<root><foo><b>bold</b></foo></root>"#;
|
||||
let want = r#"<root>
|
||||
<foo>
|
||||
<b>bold</b>
|
||||
</foo>
|
||||
</root>"#;
|
||||
assert_eq!(format_xml(src, " "), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trims_and_keeps_nonempty() {
|
||||
let src = "<root><foo> hi </foo></root>";
|
||||
let want = "<root>\n <foo>hi</foo>\n</root>";
|
||||
assert_eq!(format_xml(src, " "), want);
|
||||
}
|
||||
#[test]
|
||||
fn attributes_inline_text_child() {
|
||||
// Keeps attributes verbatim and inlines simple text children
|
||||
let src = r#"<root><item id="42" class='a b'>value</item></root>"#;
|
||||
let want = r#"<root>
|
||||
<item id="42" class='a b'>value</item>
|
||||
</root>"#;
|
||||
assert_eq!(format_xml(src, " "), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes_with_irregular_spacing_preserved() {
|
||||
// We don't normalize spaces inside the tag; raw is preserved
|
||||
let src = r#"<root><a x = "1" y='2' >t</a></root>"#;
|
||||
let want = r#"<root>
|
||||
<a x = "1" y='2' >t</a>
|
||||
</root>"#;
|
||||
assert_eq!(format_xml(src, " "), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_closing_with_attributes() {
|
||||
let src =
|
||||
r#"<root><img src="x" alt='hello "world"' width="10" height="20"/></root>"#;
|
||||
let want = r#"<root>
|
||||
<img src="x" alt='hello "world"' width="10" height="20"/>
|
||||
</root>"#;
|
||||
assert_eq!(format_xml(src, " "), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn template_in_attribute_self_closing() {
|
||||
let src = r#"<root><x attr=${[ compute(1, "two") ]}/></root>"#;
|
||||
let want = r#"<root>
|
||||
<x attr=${[ compute(1, "two") ]}/>
|
||||
</root>"#;
|
||||
assert_eq!(format_xml(src, " "), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attributes_and_nested_children_expand() {
|
||||
// Not inlined because child is an element, not plain text
|
||||
let src = r#"<root><box kind="card"><b>bold</b></box></root>"#;
|
||||
let want = r#"<root>
|
||||
<box kind="card">
|
||||
<b>bold</b>
|
||||
</box>
|
||||
</root>"#;
|
||||
assert_eq!(format_xml(src, " "), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn namespace_and_xml_attrs() {
|
||||
let src = r#"<root><ns:el xml:lang="en">ok</ns:el></root>"#;
|
||||
let want = r#"<root>
|
||||
<ns:el xml:lang="en">ok</ns:el>
|
||||
</root>"#;
|
||||
assert_eq!(format_xml(src, " "), want);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_quote_styles_in_attributes() {
|
||||
// Single-quoted attr containing double quotes is fine; we don't re-quote
|
||||
let src = r#"<root><a title='He said "hi"'>hello</a></root>"#;
|
||||
let want = r#"<root>
|
||||
<a title='He said "hi"'>hello</a>
|
||||
</root>"#;
|
||||
assert_eq!(format_xml(src, " "), want);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ pub mod format;
|
||||
pub mod parser;
|
||||
pub mod renderer;
|
||||
pub mod wasm;
|
||||
pub mod format_xml;
|
||||
|
||||
pub use parser::*;
|
||||
pub use renderer::*;
|
||||
|
||||
@@ -85,6 +85,11 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
action: 'settings.show',
|
||||
onSelect: () => openSettings.mutate(null),
|
||||
},
|
||||
{
|
||||
key: 'folder.create',
|
||||
label: 'Create Folder',
|
||||
onSelect: () => createFolder.mutate({}),
|
||||
},
|
||||
{
|
||||
key: 'app.create',
|
||||
label: 'Create Workspace',
|
||||
@@ -177,7 +182,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
});
|
||||
|
||||
commands.push({
|
||||
key: 'sidebar.delete_selected_item',
|
||||
key: 'sidebar.selected.delete',
|
||||
label: 'Delete Request',
|
||||
onSelect: () => deleteModelWithConfirm(activeRequest),
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Environment } from '@yaakapp-internal/models';
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
|
||||
import { useIsEncryptionEnabled } from '../hooks/useIsEncryptionEnabled';
|
||||
@@ -19,7 +20,6 @@ import { Heading } from './core/Heading';
|
||||
import type { PairWithId } from './core/PairEditor';
|
||||
import { ensurePairId } from './core/PairEditor.util';
|
||||
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { EnvironmentColorIndicator } from './EnvironmentColorIndicator';
|
||||
import { EnvironmentSharableTooltip } from './EnvironmentSharableTooltip';
|
||||
|
||||
@@ -98,68 +98,69 @@ export function EnvironmentEditor({
|
||||
};
|
||||
|
||||
return (
|
||||
<VStack space={4} className={className}>
|
||||
<Heading className="w-full flex items-center gap-0.5">
|
||||
<EnvironmentColorIndicator clickToEdit environment={environment ?? null} />
|
||||
{!hideName && <div className="mr-2">{environment?.name}</div>}
|
||||
{isEncryptionEnabled ? (
|
||||
!allVariableAreEncrypted ? (
|
||||
<BadgeButton color="notice" onClick={() => encryptEnvironment(environment)}>
|
||||
Encrypt All Variables
|
||||
</BadgeButton>
|
||||
<div className={classNames(className, 'h-full grid grid-rows-[auto_minmax(0,1fr)] gap-2')}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Heading className="w-full flex items-center gap-0.5">
|
||||
<EnvironmentColorIndicator clickToEdit environment={environment ?? null} />
|
||||
{!hideName && <div className="mr-2">{environment?.name}</div>}
|
||||
{isEncryptionEnabled ? (
|
||||
!allVariableAreEncrypted ? (
|
||||
<BadgeButton color="notice" onClick={() => encryptEnvironment(environment)}>
|
||||
Encrypt All Variables
|
||||
</BadgeButton>
|
||||
) : (
|
||||
<BadgeButton color="secondary" onClick={setupOrConfigureEncryption}>
|
||||
Encryption Settings
|
||||
</BadgeButton>
|
||||
)
|
||||
) : (
|
||||
<BadgeButton color="secondary" onClick={setupOrConfigureEncryption}>
|
||||
Encryption Settings
|
||||
<BadgeButton color="secondary" onClick={() => valueVisibility.set((v) => !v)}>
|
||||
{valueVisibility.value ? 'Hide Values' : 'Show Values'}
|
||||
</BadgeButton>
|
||||
)
|
||||
) : (
|
||||
<BadgeButton color="secondary" onClick={() => valueVisibility.set((v) => !v)}>
|
||||
{valueVisibility.value ? 'Hide Values' : 'Show Values'}
|
||||
)}
|
||||
<BadgeButton
|
||||
color="secondary"
|
||||
rightSlot={<EnvironmentSharableTooltip />}
|
||||
onClick={async () => {
|
||||
await patchModel(environment, { public: !environment.public });
|
||||
}}
|
||||
>
|
||||
{environment.public ? 'Sharable' : 'Private'}
|
||||
</BadgeButton>
|
||||
</Heading>
|
||||
{environment.public && (!isEncryptionEnabled || !allVariableAreEncrypted) && (
|
||||
<DismissibleBanner
|
||||
id={`warn-unencrypted-${environment.id}`}
|
||||
color="notice"
|
||||
className="mr-3"
|
||||
actions={[
|
||||
{
|
||||
label: 'Encrypt Variables',
|
||||
onClick: () => encryptEnvironment(environment),
|
||||
color: 'success',
|
||||
},
|
||||
]}
|
||||
>
|
||||
This sharable environment contains plain-text secrets
|
||||
</DismissibleBanner>
|
||||
)}
|
||||
<BadgeButton
|
||||
color="secondary"
|
||||
rightSlot={<EnvironmentSharableTooltip />}
|
||||
onClick={async () => {
|
||||
await patchModel(environment, { public: !environment.public });
|
||||
}}
|
||||
>
|
||||
{environment.public ? 'Sharable' : 'Private'}
|
||||
</BadgeButton>
|
||||
</Heading>
|
||||
{environment.public && (!isEncryptionEnabled || !allVariableAreEncrypted) && (
|
||||
<DismissibleBanner
|
||||
id={`warn-unencrypted-${environment.id}`}
|
||||
color="notice"
|
||||
className="mr-3"
|
||||
actions={[
|
||||
{
|
||||
label: 'Encrypt Variables',
|
||||
onClick: () => encryptEnvironment(environment),
|
||||
color: 'success',
|
||||
},
|
||||
]}
|
||||
>
|
||||
This sharable environment contains plain-text secrets
|
||||
</DismissibleBanner>
|
||||
)}
|
||||
<div className="h-full pr-2 pb-2 grid grid-rows-[minmax(0,1fr)] overflow-auto">
|
||||
<PairOrBulkEditor
|
||||
allowMultilineValues
|
||||
preferenceName="environment"
|
||||
nameAutocomplete={nameAutocomplete}
|
||||
namePlaceholder="VAR_NAME"
|
||||
nameValidate={validateName}
|
||||
valueType={valueType}
|
||||
valueAutocompleteVariables='environment'
|
||||
valueAutocompleteFunctions
|
||||
forceUpdateKey={`${environment.id}::${forceUpdateKey}`}
|
||||
pairs={environment.variables}
|
||||
onChange={handleChange}
|
||||
stateKey={`environment.${environment.id}`}
|
||||
forcedEnvironmentId={environment.id}
|
||||
/>
|
||||
</div>
|
||||
</VStack>
|
||||
<PairOrBulkEditor
|
||||
className="h-full"
|
||||
allowMultilineValues
|
||||
preferenceName="environment"
|
||||
nameAutocomplete={nameAutocomplete}
|
||||
namePlaceholder="VAR_NAME"
|
||||
nameValidate={validateName}
|
||||
valueType={valueType}
|
||||
valueAutocompleteVariables="environment"
|
||||
valueAutocompleteFunctions
|
||||
forceUpdateKey={`${environment.id}::${forceUpdateKey}`}
|
||||
pairs={environment.variables}
|
||||
onChange={handleChange}
|
||||
stateKey={`environment.${environment.id}`}
|
||||
forcedEnvironmentId={environment.id}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { activeRequestAtom } from '../hooks/useActiveRequest';
|
||||
import { useSubscribeActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
||||
import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast';
|
||||
import { useSubscribeHotKeys } from '../hooks/useHotKey';
|
||||
import { useHotKey, useSubscribeHotKeys } from '../hooks/useHotKey';
|
||||
import { useSubscribeHttpAuthentication } from '../hooks/useHttpAuthentication';
|
||||
import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting';
|
||||
import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels';
|
||||
import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting';
|
||||
import { useSubscribeTemplateFunctions } from '../hooks/useTemplateFunctions';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { renameModelWithPrompt } from '../lib/renameModelWithPrompt';
|
||||
|
||||
export function GlobalHooks() {
|
||||
useSyncZoomSetting();
|
||||
@@ -21,5 +24,15 @@ export function GlobalHooks() {
|
||||
useActiveWorkspaceChangedToast();
|
||||
useSubscribeHotKeys();
|
||||
|
||||
useHotKey(
|
||||
'request.rename',
|
||||
async () => {
|
||||
const model = jotaiStore.get(activeRequestAtom);
|
||||
if (model == null) return;
|
||||
await renameModelWithPrompt(model);
|
||||
},
|
||||
{ allowDefault: true },
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
|
||||
await grpc.reflect.refetch();
|
||||
}}
|
||||
>
|
||||
Add Files
|
||||
Add Proto Files
|
||||
</Button>
|
||||
<Button
|
||||
variant="border"
|
||||
@@ -76,7 +76,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp
|
||||
await grpc.reflect.refetch();
|
||||
}}
|
||||
>
|
||||
Add Directories
|
||||
Add Import Folders
|
||||
</Button>
|
||||
<Button
|
||||
isLoading={grpc.reflect.isFetching}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HttpResponse } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties, ReactNode} from 'react';
|
||||
import React, { Suspense , lazy, useCallback, useMemo } from 'react';
|
||||
import type { ComponentType, CSSProperties } from 'react';
|
||||
import React, { lazy, Suspense, useCallback, useMemo } from 'react';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
|
||||
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
||||
@@ -10,16 +10,18 @@ import { getMimeTypeFromContentType } from '../lib/contentType';
|
||||
import { getContentTypeFromHeaders } from '../lib/model_util';
|
||||
import { ConfirmLargeResponse } from './ConfirmLargeResponse';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
import { CountBadge } from './core/CountBadge';
|
||||
import { HttpResponseDurationTag } from './core/HttpResponseDurationTag';
|
||||
import { HotKeyList } from './core/HotKeyList';
|
||||
import { HttpResponseDurationTag } from './core/HttpResponseDurationTag';
|
||||
import { HttpStatusTag } from './core/HttpStatusTag';
|
||||
import { LoadingIcon } from './core/LoadingIcon';
|
||||
import { SizeTag } from './core/SizeTag';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
import { HttpStatusTag } from './core/HttpStatusTag';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import type { TabItem} from './core/Tabs/Tabs';
|
||||
import { Tabs , TabContent} from './core/Tabs/Tabs';
|
||||
import { EmptyStateText } from './EmptyStateText';
|
||||
import { ErrorBoundary } from './ErrorBoundary';
|
||||
import { RecentHttpResponsesDropdown } from './RecentHttpResponsesDropdown';
|
||||
import { ResponseHeaders } from './ResponseHeaders';
|
||||
import { ResponseInfo } from './ResponseInfo';
|
||||
@@ -30,8 +32,6 @@ import { HTMLOrTextViewer } from './responseViewers/HTMLOrTextViewer';
|
||||
import { ImageViewer } from './responseViewers/ImageViewer';
|
||||
import { SvgViewer } from './responseViewers/SvgViewer';
|
||||
import { VideoViewer } from './responseViewers/VideoViewer';
|
||||
import { ErrorBoundary } from './ErrorBoundary';
|
||||
import { Button } from './core/Button';
|
||||
|
||||
const PdfViewer = lazy(() =>
|
||||
import('./responseViewers/PdfViewer').then((m) => ({ default: m.PdfViewer })),
|
||||
@@ -184,13 +184,13 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
||||
) : mimeType?.match(/^image\/svg/) ? (
|
||||
<SvgViewer response={activeResponse} />
|
||||
) : mimeType?.match(/^image/i) ? (
|
||||
<EnsureCompleteResponse response={activeResponse} render={ImageViewer} />
|
||||
<EnsureCompleteResponse response={activeResponse} Component={ImageViewer} />
|
||||
) : mimeType?.match(/^audio/i) ? (
|
||||
<EnsureCompleteResponse response={activeResponse} render={AudioViewer} />
|
||||
<EnsureCompleteResponse response={activeResponse} Component={AudioViewer} />
|
||||
) : mimeType?.match(/^video/i) ? (
|
||||
<EnsureCompleteResponse response={activeResponse} render={VideoViewer} />
|
||||
<EnsureCompleteResponse response={activeResponse} Component={VideoViewer} />
|
||||
) : mimeType?.match(/pdf/i) ? (
|
||||
<EnsureCompleteResponse response={activeResponse} render={PdfViewer} />
|
||||
<EnsureCompleteResponse response={activeResponse} Component={PdfViewer} />
|
||||
) : mimeType?.match(/csv|tab-separated/i) ? (
|
||||
<CsvViewer className="pb-2" response={activeResponse} />
|
||||
) : (
|
||||
@@ -220,10 +220,10 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) {
|
||||
|
||||
function EnsureCompleteResponse({
|
||||
response,
|
||||
render,
|
||||
Component,
|
||||
}: {
|
||||
response: HttpResponse;
|
||||
render: (v: { bodyPath: string }) => ReactNode;
|
||||
Component: ComponentType<{ bodyPath: string }>;
|
||||
}) {
|
||||
if (response.bodyPath === null) {
|
||||
return <div>Empty response body</div>;
|
||||
@@ -238,5 +238,5 @@ function EnsureCompleteResponse({
|
||||
);
|
||||
}
|
||||
|
||||
return render({ bodyPath: response.bodyPath });
|
||||
return <Component bodyPath={response.bodyPath} />;
|
||||
}
|
||||
|
||||
@@ -256,38 +256,53 @@ const sidebarTreeAtom = atom((get) => {
|
||||
});
|
||||
|
||||
const actions = {
|
||||
'sidebar.delete_selected_item': async function (items: SidebarModel[]) {
|
||||
await deleteModelWithConfirm(items);
|
||||
'sidebar.selected.delete': {
|
||||
enable: isSidebarFocused,
|
||||
cb: async function (_: TreeHandle, items: SidebarModel[]) {
|
||||
await deleteModelWithConfirm(items);
|
||||
},
|
||||
},
|
||||
'model.duplicate': async function (items: SidebarModel[]) {
|
||||
if (items.length === 1) {
|
||||
const item = items[0]!;
|
||||
const newId = await duplicateModel(item);
|
||||
navigateToRequestOrFolderOrWorkspace(newId, item.model);
|
||||
} else {
|
||||
await Promise.all(items.map(duplicateModel));
|
||||
}
|
||||
'sidebar.selected.rename': {
|
||||
enable: isSidebarFocused,
|
||||
allowDefault: true,
|
||||
cb: async function (tree: TreeHandle, items: SidebarModel[]) {
|
||||
const item = items[0];
|
||||
if (items.length === 1 && item != null) {
|
||||
tree.renameItem(item.id);
|
||||
}
|
||||
},
|
||||
},
|
||||
'request.send': async function (items: SidebarModel[]) {
|
||||
await Promise.all(
|
||||
items.filter((i) => i.model === 'http_request').map((i) => sendAnyHttpRequest.mutate(i.id)),
|
||||
);
|
||||
'sidebar.selected.duplicate': {
|
||||
priority: 999,
|
||||
enable: isSidebarFocused,
|
||||
cb: async function (_: TreeHandle, items: SidebarModel[]) {
|
||||
if (items.length === 1) {
|
||||
const item = items[0]!;
|
||||
const newId = await duplicateModel(item);
|
||||
navigateToRequestOrFolderOrWorkspace(newId, item.model);
|
||||
} else {
|
||||
await Promise.all(items.map(duplicateModel));
|
||||
}
|
||||
},
|
||||
},
|
||||
'request.send': {
|
||||
enable: isSidebarFocused,
|
||||
cb: async function (_: TreeHandle, items: SidebarModel[]) {
|
||||
await Promise.all(
|
||||
items.filter((i) => i.model === 'http_request').map((i) => sendAnyHttpRequest.mutate(i.id)),
|
||||
);
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const hotkeys: TreeProps<SidebarModel>['hotkeys'] = {
|
||||
priority: 10, // So these ones take precedence over global hotkeys when the sidebar is focused
|
||||
actions,
|
||||
enable: () => isSidebarFocused(),
|
||||
};
|
||||
const hotkeys: TreeProps<SidebarModel>['hotkeys'] = { actions };
|
||||
|
||||
async function getContextMenu(items: SidebarModel[]): Promise<DropdownItem[]> {
|
||||
async function getContextMenu(tree: TreeHandle, items: SidebarModel[]): Promise<DropdownItem[]> {
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
const child = items[0];
|
||||
|
||||
// No children means we're in the root
|
||||
if (child == null) {
|
||||
console.log('HELLO', child);
|
||||
return getCreateDropdownItems({ workspaceId, activeRequest: null, folderId: null });
|
||||
}
|
||||
|
||||
@@ -321,7 +336,7 @@ async function getContextMenu(items: SidebarModel[]): Promise<DropdownItem[]> {
|
||||
hotKeyLabelOnly: true,
|
||||
hidden: !onlyHttpRequests,
|
||||
leftSlot: <Icon icon="send_horizontal" />,
|
||||
onSelect: () => actions['request.send'](items),
|
||||
onSelect: () => actions['request.send'].cb(tree, items),
|
||||
},
|
||||
...(items.length === 1 && child.model === 'http_request'
|
||||
? await getHttpRequestActions()
|
||||
@@ -362,6 +377,8 @@ async function getContextMenu(items: SidebarModel[]): Promise<DropdownItem[]> {
|
||||
label: 'Rename',
|
||||
leftSlot: <Icon icon="pencil" />,
|
||||
hidden: items.length > 1,
|
||||
hotKeyAction: 'sidebar.selected.rename',
|
||||
hotKeyLabelOnly: true,
|
||||
onSelect: async () => {
|
||||
const request = getModel(
|
||||
['folder', 'http_request', 'grpc_request', 'websocket_request'],
|
||||
@@ -375,7 +392,7 @@ async function getContextMenu(items: SidebarModel[]): Promise<DropdownItem[]> {
|
||||
hotKeyAction: 'model.duplicate',
|
||||
hotKeyLabelOnly: true, // Would trigger for every request (bad)
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
onSelect: () => actions['model.duplicate'](items),
|
||||
onSelect: () => actions['sidebar.selected.duplicate'].cb(tree, items),
|
||||
},
|
||||
{
|
||||
label: 'Move',
|
||||
@@ -393,10 +410,10 @@ async function getContextMenu(items: SidebarModel[]): Promise<DropdownItem[]> {
|
||||
{
|
||||
color: 'danger',
|
||||
label: 'Delete',
|
||||
hotKeyAction: 'sidebar.delete_selected_item',
|
||||
hotKeyAction: 'sidebar.selected.delete',
|
||||
hotKeyLabelOnly: true,
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: () => actions['sidebar.delete_selected_item'](items),
|
||||
onSelect: () => actions['sidebar.selected.delete'].cb(tree, items),
|
||||
},
|
||||
...modelCreationItems,
|
||||
];
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import classNames from 'classnames';
|
||||
import { FocusTrap } from 'focus-trap-react';
|
||||
import * as m from 'motion/react-m';
|
||||
import type { ReactNode} from 'react';
|
||||
import React, { Suspense , lazy, useRef } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import { Portal } from './Portal';
|
||||
|
||||
const FocusTrap = lazy(() => import('focus-trap-react'));
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
portalName: string;
|
||||
@@ -51,52 +50,50 @@ export function Overlay({
|
||||
return (
|
||||
<Portal name={portalName}>
|
||||
{open && (
|
||||
<Suspense>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
allowOutsideClick: true, // So we can still click toasts and things
|
||||
delayInitialFocus: true,
|
||||
fallbackFocus: () => containerRef.current!, // always have a target
|
||||
initialFocus: () =>
|
||||
// Doing this explicitly seems to work better than the default behavior for some reason
|
||||
containerRef.current?.querySelector<HTMLElement>(
|
||||
[
|
||||
'a[href]',
|
||||
'input:not([disabled])',
|
||||
'select:not([disabled])',
|
||||
'textarea:not([disabled])',
|
||||
'button:not([disabled])',
|
||||
'[tabindex]:not([tabindex="-1"])',
|
||||
'[contenteditable]:not([contenteditable="false"])',
|
||||
].join(', '),
|
||||
) ?? undefined,
|
||||
}}
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
allowOutsideClick: true, // So we can still click toasts and things
|
||||
delayInitialFocus: true,
|
||||
fallbackFocus: () => containerRef.current!, // always have a target
|
||||
initialFocus: () =>
|
||||
// Doing this explicitly seems to work better than the default behavior for some reason
|
||||
containerRef.current?.querySelector<HTMLElement>(
|
||||
[
|
||||
'a[href]',
|
||||
'input:not([disabled])',
|
||||
'select:not([disabled])',
|
||||
'textarea:not([disabled])',
|
||||
'button:not([disabled])',
|
||||
'[tabindex]:not([tabindex="-1"])',
|
||||
'[contenteditable]:not([contenteditable="false"])',
|
||||
].join(', '),
|
||||
) ?? undefined,
|
||||
}}
|
||||
>
|
||||
<m.div
|
||||
ref={containerRef}
|
||||
tabIndex={-1}
|
||||
className={classNames('fixed inset-0', zIndexes[zIndex])}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
>
|
||||
<m.div
|
||||
ref={containerRef}
|
||||
tabIndex={-1}
|
||||
className={classNames('fixed inset-0', zIndexes[zIndex])}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
>
|
||||
<div
|
||||
aria-hidden
|
||||
onClick={onClose}
|
||||
className={classNames(
|
||||
'absolute inset-0',
|
||||
variant === 'default' && 'bg-backdrop backdrop-blur-sm',
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Show the draggable region at the top */}
|
||||
{/* TODO: Figure out tauri drag region and also make clickable still */}
|
||||
{variant === 'default' && (
|
||||
<div data-tauri-drag-region className="absolute top-0 left-0 h-md right-0" />
|
||||
<div
|
||||
aria-hidden
|
||||
onClick={onClose}
|
||||
className={classNames(
|
||||
'absolute inset-0',
|
||||
variant === 'default' && 'bg-backdrop backdrop-blur-sm',
|
||||
)}
|
||||
{children}
|
||||
</m.div>
|
||||
</FocusTrap>
|
||||
</Suspense>
|
||||
/>
|
||||
|
||||
{/* Show the draggable region at the top */}
|
||||
{/* TODO: Figure out tauri drag region and also make clickable still */}
|
||||
{variant === 'default' && (
|
||||
<div data-tauri-drag-region className="absolute top-0 left-0 h-md right-0" />
|
||||
)}
|
||||
{children}
|
||||
</m.div>
|
||||
</FocusTrap>
|
||||
)}
|
||||
</Portal>
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ export function RecentRequestsDropdown({ className }: Props) {
|
||||
}
|
||||
});
|
||||
|
||||
useHotKey('request_switcher.prev', () => {
|
||||
useHotKey('switcher.prev', () => {
|
||||
if (!dropdownRef.current?.isOpen) {
|
||||
// Select the second because the first is the current request
|
||||
dropdownRef.current?.open(1);
|
||||
@@ -41,7 +41,7 @@ export function RecentRequestsDropdown({ className }: Props) {
|
||||
}
|
||||
});
|
||||
|
||||
useHotKey('request_switcher.next', () => {
|
||||
useHotKey('switcher.next', () => {
|
||||
if (!dropdownRef.current?.isOpen) dropdownRef.current?.open();
|
||||
dropdownRef.current?.prev?.();
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export function RecentRequestsDropdown({ className }: Props) {
|
||||
<Dropdown ref={dropdownRef} items={items}>
|
||||
<Button
|
||||
size="sm"
|
||||
hotkeyAction="request_switcher.toggle"
|
||||
hotkeyAction="switcher.toggle"
|
||||
className={classNames(
|
||||
className,
|
||||
'truncate pointer-events-auto',
|
||||
|
||||
@@ -128,7 +128,7 @@ export function SettingsGeneral() {
|
||||
|
||||
<Checkbox
|
||||
checked={workspace.settingValidateCertificates}
|
||||
help="When disabled, skip validatation of server certificates, useful when interacting with self-signed certs."
|
||||
help="When disabled, skip validation of server certificates, useful when interacting with self-signed certs."
|
||||
title="Validate TLS Certificates"
|
||||
onChange={(settingValidateCertificates) =>
|
||||
patchModel(workspace, { settingValidateCertificates })
|
||||
|
||||
@@ -44,7 +44,7 @@ export function Confirm({
|
||||
onChange={setConfirm}
|
||||
label={
|
||||
<>
|
||||
Type <strong className="select-auto cursor-text">{requireTyping}</strong> to confirm
|
||||
Type <strong className="!select-auto cursor-auto">{requireTyping}</strong> to confirm
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -413,7 +413,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
close: handleClose,
|
||||
prev: handlePrev,
|
||||
next: handleNext,
|
||||
async select() {
|
||||
select: async () => {
|
||||
const item = items[selectedIndexRef.current ?? -1] ?? null;
|
||||
if (!item) return;
|
||||
await handleSelect(item);
|
||||
@@ -569,10 +569,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
<div
|
||||
key={i}
|
||||
className={classNames('my-1 mx-2 max-w-xs')}
|
||||
onClick={() => {
|
||||
// Ensure the dropdown is closed when anything in the content is clicked
|
||||
onClose();
|
||||
}}
|
||||
onClick={onClose}
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
@apply text-surface !important;
|
||||
}
|
||||
|
||||
/* Matching bracket */
|
||||
.cm-matchingBracket {
|
||||
@apply bg-transparent border-b border-b-text-subtle;
|
||||
}
|
||||
|
||||
&:not(.cm-focused) {
|
||||
.cm-cursor, .cm-fat-cursor {
|
||||
@apply hidden;
|
||||
@@ -233,15 +238,15 @@
|
||||
|
||||
.cm-tooltip.cm-tooltip-hover {
|
||||
@apply shadow-lg bg-surface rounded text-text-subtle border border-border-subtle z-50 pointer-events-auto text-sm;
|
||||
@apply px-2 py-1;
|
||||
@apply p-1.5;
|
||||
|
||||
/* Style the tooltip for popping up "open in browser" and other stuff */
|
||||
a, button {
|
||||
@apply text-text hover:bg-surface-highlight w-full h-sm flex items-center px-2 rounded;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-text;
|
||||
|
||||
&:hover {
|
||||
@apply underline;
|
||||
}
|
||||
|
||||
@apply cursor-default !important;
|
||||
&::after {
|
||||
@apply text-text bg-text h-3 w-3 ml-1;
|
||||
content: '';
|
||||
|
||||
@@ -151,7 +151,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
}, [allEnvironmentVariables, autocompleteVariables]);
|
||||
// Track a local key for updates. If the default value is changed when the input is not in focus,
|
||||
// regenerate this to force the field to update.
|
||||
const [focusedUpdateKey, regenerateFocusedUpdateKey] = useRandomKey();
|
||||
const [focusedUpdateKey, regenerateFocusedUpdateKey] = useRandomKey('initial');
|
||||
const forceUpdateKey = `${forceUpdateKeyFromAbove}::${focusedUpdateKey}`;
|
||||
|
||||
if (settings && wrapLines === undefined) {
|
||||
@@ -352,17 +352,6 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
[],
|
||||
);
|
||||
|
||||
// Force input to update when receiving change and not in focus
|
||||
useLayoutEffect(() => {
|
||||
const currDoc = cm.current?.view.state.doc.toString() || '';
|
||||
const nextDoc = defaultValue || '';
|
||||
const notFocused = !cm.current?.view.hasFocus;
|
||||
const hasChanged = currDoc !== nextDoc;
|
||||
if (notFocused && hasChanged) {
|
||||
regenerateFocusedUpdateKey();
|
||||
}
|
||||
}, [defaultValue, regenerateFocusedUpdateKey]);
|
||||
|
||||
const [, { focusParamValue }] = useRequestEditor();
|
||||
const onClickPathParameter = useCallback(
|
||||
async (name: string) => {
|
||||
@@ -487,33 +476,24 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
// For read-only mode, update content when `defaultValue` changes
|
||||
useEffect(
|
||||
function updateReadOnlyEditor() {
|
||||
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,
|
||||
}),
|
||||
});
|
||||
if (readOnly && cm.current?.view != null) {
|
||||
updateContents(cm.current.view, defaultValue || '');
|
||||
}
|
||||
},
|
||||
[defaultValue, readOnly],
|
||||
);
|
||||
|
||||
// Force input to update when receiving change and not in focus
|
||||
useLayoutEffect(
|
||||
function updateNonFocusedEditor() {
|
||||
const notFocused = !cm.current?.view.hasFocus;
|
||||
if (notFocused && cm.current != null) {
|
||||
updateContents(cm.current.view, defaultValue || '');
|
||||
}
|
||||
},
|
||||
[defaultValue, readOnly, regenerateFocusedUpdateKey],
|
||||
);
|
||||
|
||||
// Add bg classes to actions, so they appear over the text
|
||||
const decoratedActions = useMemo(() => {
|
||||
const results = [];
|
||||
@@ -720,3 +700,30 @@ function getCachedEditorState(doc: string, stateKey: string | null) {
|
||||
function computeFullStateKey(stateKey: string): string {
|
||||
return `editor.${stateKey}`;
|
||||
}
|
||||
|
||||
function updateContents(view: EditorView, text: string) {
|
||||
// Replace codemirror contents
|
||||
const currentDoc = view.state.doc.toString();
|
||||
|
||||
if (currentDoc === text) {
|
||||
return;
|
||||
} else if (text.startsWith(currentDoc)) {
|
||||
// If we're just appending, append only the changes. This preserves
|
||||
// things like scroll position.
|
||||
view.dispatch({
|
||||
changes: view.state.changes({
|
||||
from: currentDoc.length,
|
||||
insert: text.slice(currentDoc.length),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
// If we're replacing everything, reset the entire content
|
||||
view.dispatch({
|
||||
changes: view.state.changes({
|
||||
from: 0,
|
||||
to: currentDoc.length,
|
||||
insert: text,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { json } from '@codemirror/lang-json';
|
||||
import { markdown } from '@codemirror/lang-markdown';
|
||||
import { xml } from '@codemirror/lang-xml';
|
||||
import type { LanguageSupport } from '@codemirror/language';
|
||||
import {
|
||||
import { bracketMatching ,
|
||||
codeFolding,
|
||||
foldGutter,
|
||||
foldKeymap,
|
||||
@@ -258,6 +258,7 @@ export const multiLineExtensions = ({ hideGutter }: { hideGutter?: boolean }) =>
|
||||
indentOnInput(),
|
||||
rectangularSelection(),
|
||||
crosshairCursor(),
|
||||
bracketMatching(),
|
||||
highlightActiveLineGutter(),
|
||||
keymap.of([...searchKeymap, ...foldKeymap, ...lintKeymap]),
|
||||
];
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
|
||||
import { Decoration, EditorView, hoverTooltip, MatchDecorator, ViewPlugin } from '@codemirror/view';
|
||||
import { activeWorkspaceIdAtom } from '../../../../hooks/useActiveWorkspace';
|
||||
import { copyToClipboard } from '../../../../lib/copy';
|
||||
import { createRequestAndNavigate } from '../../../../lib/createRequestAndNavigate';
|
||||
import { jotaiStore } from '../../../../lib/jotai';
|
||||
|
||||
const REGEX =
|
||||
/(https?:\/\/([-a-zA-Z0-9@:%._+*~#=]{1,256})+(\.[a-zA-Z0-9()]{1,6})?\b([-a-zA-Z0-9()@:%_+*.~#?&/={}[\]]*))/g;
|
||||
@@ -32,11 +36,38 @@ const tooltip = hoverTooltip(
|
||||
pos: found.start,
|
||||
end: found.end,
|
||||
create() {
|
||||
const dom = document.createElement('a');
|
||||
dom.textContent = 'Open in browser';
|
||||
dom.href = text.substring(found!.start - from, found!.end - from);
|
||||
dom.target = '_blank';
|
||||
dom.rel = 'noopener noreferrer';
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
const link = text.substring(found!.start - from, found!.end - from);
|
||||
const dom = document.createElement('div');
|
||||
|
||||
const $open = document.createElement('a');
|
||||
$open.textContent = 'Open in browser';
|
||||
$open.href = link;
|
||||
$open.target = '_blank';
|
||||
$open.rel = 'noopener noreferrer';
|
||||
|
||||
const $copy = document.createElement('button');
|
||||
$copy.textContent = 'Copy to clipboard';
|
||||
$copy.addEventListener('click', () => {
|
||||
copyToClipboard(link);
|
||||
});
|
||||
|
||||
const $create = document.createElement('button');
|
||||
$create.textContent = 'Create new request';
|
||||
$create.addEventListener('click', async () => {
|
||||
await createRequestAndNavigate({
|
||||
model: 'http_request',
|
||||
workspaceId: workspaceId ?? 'n/a',
|
||||
url: link,
|
||||
});
|
||||
});
|
||||
|
||||
dom.appendChild($open);
|
||||
dom.appendChild($copy);
|
||||
if (workspaceId != null) {
|
||||
dom.appendChild($create);
|
||||
}
|
||||
|
||||
return { dom };
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
import classNames from 'classnames';
|
||||
import type {
|
||||
CSSProperties,
|
||||
KeyboardEvent,
|
||||
ReactNode} from 'react';
|
||||
import React, {
|
||||
lazy,
|
||||
Suspense,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { CSSProperties, KeyboardEvent, ReactNode } from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { generateId } from '../../lib/generateId';
|
||||
|
||||
const Portal = lazy(() => import('../Portal').then((m) => ({ default: m.Portal })));
|
||||
import { Portal } from '../Portal';
|
||||
|
||||
export interface TooltipProps {
|
||||
children: ReactNode;
|
||||
@@ -75,7 +66,7 @@ export function Tooltip({ children, content, tabIndex, size = 'md' }: TooltipPro
|
||||
const id = useRef(`tooltip-${generateId()}`);
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
<>
|
||||
<Portal name="tooltip">
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
@@ -114,7 +105,7 @@ export function Tooltip({ children, content, tabIndex, size = 'md' }: TooltipPro
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
import type { SelectableTreeNode, TreeNode } from './common';
|
||||
import { equalSubtree, getSelectedItems, hasAncestor } from './common';
|
||||
import { TreeDragOverlay } from './TreeDragOverlay';
|
||||
import type { TreeItemProps } from './TreeItem';
|
||||
import type { TreeItemHandle, TreeItemProps } from './TreeItem';
|
||||
import type { TreeItemListProps } from './TreeItemList';
|
||||
import { TreeItemList } from './TreeItemList';
|
||||
import { useSelectableItems } from './useSelectableItems';
|
||||
@@ -45,13 +45,23 @@ export interface TreeProps<T extends { id: string }> {
|
||||
root: TreeNode<T>;
|
||||
treeId: string;
|
||||
getItemKey: (item: T) => string;
|
||||
getContextMenu?: (items: T[]) => Promise<ContextMenuProps['items']>;
|
||||
getContextMenu?: (t: TreeHandle, items: T[]) => Promise<ContextMenuProps['items']>;
|
||||
ItemInner: ComponentType<{ treeId: string; item: T }>;
|
||||
ItemLeftSlot?: ComponentType<{ treeId: string; item: T }>;
|
||||
className?: string;
|
||||
onActivate?: (item: T) => void;
|
||||
onDragEnd?: (opt: { items: T[]; parent: T; children: T[]; insertAt: number }) => void;
|
||||
hotkeys?: { actions: Partial<Record<HotkeyAction, (items: T[]) => void>> } & HotKeyOptions;
|
||||
hotkeys?: {
|
||||
actions: Partial<
|
||||
Record<
|
||||
HotkeyAction,
|
||||
{
|
||||
cb: (h: TreeHandle, items: T[]) => void;
|
||||
enable?: boolean | ((h: TreeHandle) => boolean);
|
||||
} & Omit<HotKeyOptions, 'enable'>
|
||||
>
|
||||
>;
|
||||
};
|
||||
getEditOptions?: (item: T) => {
|
||||
defaultValue: string;
|
||||
placeholder?: string;
|
||||
@@ -62,6 +72,7 @@ export interface TreeProps<T extends { id: string }> {
|
||||
export interface TreeHandle {
|
||||
focus: () => void;
|
||||
selectItem: (id: string) => void;
|
||||
renameItem: (id: string) => void;
|
||||
}
|
||||
|
||||
function TreeInner<T extends { id: string }>(
|
||||
@@ -87,6 +98,15 @@ function TreeInner<T extends { id: string }>(
|
||||
x: number;
|
||||
y: number;
|
||||
} | null>(null);
|
||||
const treeItemRefs = useRef<Record<string, TreeItemHandle>>({});
|
||||
|
||||
const handleAddTreeItemRef = useCallback((item: T, r: TreeItemHandle | null) => {
|
||||
if (r == null) {
|
||||
delete treeItemRefs.current[item.id];
|
||||
} else {
|
||||
treeItemRefs.current[item.id] = r;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleCloseContextMenu = useCallback(() => {
|
||||
setShowContextMenu(null);
|
||||
@@ -105,11 +125,11 @@ function TreeInner<T extends { id: string }>(
|
||||
[treeId, tryFocus],
|
||||
);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
(): TreeHandle => ({
|
||||
const treeHandle = useMemo<TreeHandle>(
|
||||
() => ({
|
||||
focus: tryFocus,
|
||||
selectItem(id) {
|
||||
renameItem: (id) => treeItemRefs.current[id]?.rename(),
|
||||
selectItem: (id) => {
|
||||
setSelected([id], false);
|
||||
jotaiStore.set(focusIdsFamily(treeId), { anchorId: id, lastId: id });
|
||||
},
|
||||
@@ -117,6 +137,8 @@ function TreeInner<T extends { id: string }>(
|
||||
[setSelected, treeId, tryFocus],
|
||||
);
|
||||
|
||||
useImperativeHandle(ref, (): TreeHandle => treeHandle, [treeHandle]);
|
||||
|
||||
const handleGetContextMenu = useMemo(() => {
|
||||
if (getContextMenu == null) return;
|
||||
return (item: T) => {
|
||||
@@ -124,16 +146,16 @@ function TreeInner<T extends { id: string }>(
|
||||
const isSelected = items.find((i) => i.id === item.id);
|
||||
if (isSelected) {
|
||||
// If right-clicked an item that was in the multiple-selection, use the entire selection
|
||||
return getContextMenu(items);
|
||||
return getContextMenu(treeHandle, items);
|
||||
} else {
|
||||
// If right-clicked an item that was NOT in the multiple-selection, just use that one
|
||||
// Also update the selection with it
|
||||
jotaiStore.set(selectedIdsFamily(treeId), [item.id]);
|
||||
jotaiStore.set(focusIdsFamily(treeId), (prev) => ({ ...prev, lastId: item.id }));
|
||||
return getContextMenu([item]);
|
||||
return getContextMenu(treeHandle, [item]);
|
||||
}
|
||||
};
|
||||
}, [getContextMenu, selectableItems, treeId]);
|
||||
}, [getContextMenu, selectableItems, treeHandle, treeId]);
|
||||
|
||||
const handleSelect = useCallback<NonNullable<TreeItemProps<T>['onClick']>>(
|
||||
(item, { shiftKey, metaKey, ctrlKey }) => {
|
||||
@@ -141,7 +163,7 @@ function TreeInner<T extends { id: string }>(
|
||||
const selectedIdsAtom = selectedIdsFamily(treeId);
|
||||
const selectedIds = jotaiStore.get(selectedIdsAtom);
|
||||
|
||||
// Mark item as the last one selected
|
||||
// Mark the item as the last one selected
|
||||
jotaiStore.set(focusIdsFamily(treeId), (prev) => ({ ...prev, lastId: item.id }));
|
||||
|
||||
if (shiftKey) {
|
||||
@@ -427,17 +449,22 @@ function TreeInner<T extends { id: string }>(
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const items = await getContextMenu([]);
|
||||
const items = await getContextMenu(treeHandle, []);
|
||||
setShowContextMenu({ items, x: e.clientX, y: e.clientY });
|
||||
},
|
||||
[getContextMenu],
|
||||
[getContextMenu, treeHandle],
|
||||
);
|
||||
|
||||
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } }));
|
||||
|
||||
return (
|
||||
<>
|
||||
<TreeHotKeys treeId={treeId} hotkeys={hotkeys} selectableItems={selectableItems} />
|
||||
<TreeHotKeys
|
||||
treeHandle={treeHandle}
|
||||
treeId={treeId}
|
||||
hotkeys={hotkeys}
|
||||
selectableItems={selectableItems}
|
||||
/>
|
||||
{showContextMenu && (
|
||||
<ContextMenu
|
||||
items={showContextMenu.items}
|
||||
@@ -466,6 +493,7 @@ function TreeInner<T extends { id: string }>(
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'[&_.tree-item.selected_.tree-item-inner]:text-text',
|
||||
'[&:focus-within]:[&_.tree-item.selected]:bg-surface-active',
|
||||
'[&:not(:focus-within)]:[&_.tree-item.selected]:bg-surface-highlight',
|
||||
|
||||
@@ -478,7 +506,12 @@ function TreeInner<T extends { id: string }>(
|
||||
'[&_.tree-item.selected:has(+.drop-marker+.tree-item.selected)]:rounded-b-none',
|
||||
)}
|
||||
>
|
||||
<TreeItemList nodes={selectableItems} treeId={treeId} {...treeItemListProps} />
|
||||
<TreeItemList
|
||||
addTreeItemRef={handleAddTreeItemRef}
|
||||
nodes={selectableItems}
|
||||
treeId={treeId}
|
||||
{...treeItemListProps}
|
||||
/>
|
||||
</div>
|
||||
{/* Assign root ID so we can reuse our same move/end logic */}
|
||||
<DropRegionAfterList id={root.item.id} onContextMenu={handleContextMenu} />
|
||||
@@ -522,11 +555,14 @@ function DropRegionAfterList({
|
||||
return <div ref={setNodeRef} onContextMenu={onContextMenu} />;
|
||||
}
|
||||
|
||||
interface TreeHotKeyProps<T extends { id: string }> extends HotKeyOptions {
|
||||
interface TreeHotKeyProps<T extends { id: string }> {
|
||||
action: HotkeyAction;
|
||||
selectableItems: SelectableTreeNode<T>[];
|
||||
treeId: string;
|
||||
onDone: (items: T[]) => void;
|
||||
onDone: (h: TreeHandle, items: T[]) => void;
|
||||
treeHandle: TreeHandle;
|
||||
priority?: number;
|
||||
enable?: boolean | ((h: TreeHandle) => boolean);
|
||||
}
|
||||
|
||||
function TreeHotKey<T extends { id: string }>({
|
||||
@@ -534,14 +570,23 @@ function TreeHotKey<T extends { id: string }>({
|
||||
action,
|
||||
onDone,
|
||||
selectableItems,
|
||||
treeHandle,
|
||||
enable,
|
||||
...options
|
||||
}: TreeHotKeyProps<T>) {
|
||||
useHotKey(
|
||||
action,
|
||||
() => {
|
||||
onDone(getSelectedItems(treeId, selectableItems));
|
||||
onDone(treeHandle, getSelectedItems(treeId, selectableItems));
|
||||
},
|
||||
{
|
||||
...options,
|
||||
enable: () => {
|
||||
if (enable == null) return true;
|
||||
if (typeof enable === 'function') return enable(treeHandle);
|
||||
else return enable;
|
||||
},
|
||||
},
|
||||
options,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -550,24 +595,26 @@ function TreeHotKeys<T extends { id: string }>({
|
||||
treeId,
|
||||
hotkeys,
|
||||
selectableItems,
|
||||
treeHandle,
|
||||
}: {
|
||||
treeId: string;
|
||||
hotkeys: TreeProps<T>['hotkeys'];
|
||||
selectableItems: SelectableTreeNode<T>[];
|
||||
treeHandle: TreeHandle;
|
||||
}) {
|
||||
if (hotkeys == null) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.entries(hotkeys.actions).map(([hotkey, onDone]) => (
|
||||
{Object.entries(hotkeys.actions).map(([hotkey, { cb, ...options }]) => (
|
||||
<TreeHotKey
|
||||
key={hotkey}
|
||||
action={hotkey as HotkeyAction}
|
||||
priority={hotkeys.priority}
|
||||
enable={hotkeys.enable}
|
||||
treeId={treeId}
|
||||
onDone={onDone}
|
||||
onDone={cb}
|
||||
treeHandle={treeHandle}
|
||||
selectableItems={selectableItems}
|
||||
{...options}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { memo } from 'react';
|
||||
import { hoveredParentDepthFamily } from './atoms';
|
||||
import { hoveredParentDepthFamily, isParentHoveredFamily } from './atoms';
|
||||
|
||||
export const TreeIndentGuide = memo(function TreeIndentGuide({
|
||||
treeId,
|
||||
depth,
|
||||
parentId,
|
||||
}: {
|
||||
treeId: string;
|
||||
depth: number;
|
||||
parentId: string | null;
|
||||
}) {
|
||||
const parentDepth = useAtomValue(hoveredParentDepthFamily(treeId));
|
||||
const isHovered = useAtomValue(isParentHoveredFamily({ treeId, parentId }));
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
@@ -19,7 +22,7 @@ export const TreeIndentGuide = memo(function TreeIndentGuide({
|
||||
key={i}
|
||||
className={classNames(
|
||||
'w-[1rem] border-r border-r-text-subtlest',
|
||||
parentDepth !== i + 1 && 'opacity-30',
|
||||
!(parentDepth === i + 1 && isHovered) && 'opacity-30',
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -30,8 +30,14 @@ export type TreeItemProps<T extends { id: string }> = Pick<
|
||||
onClick?: (item: T, e: OnClickEvent) => void;
|
||||
getContextMenu?: (item: T) => Promise<ContextMenuProps['items']>;
|
||||
depth: number;
|
||||
addRef?: (item: T, n: TreeItemHandle | null) => void;
|
||||
};
|
||||
|
||||
export interface TreeItemHandle {
|
||||
rename: () => void;
|
||||
isRenaming: boolean;
|
||||
}
|
||||
|
||||
const HOVER_CLOSED_FOLDER_DELAY = 800;
|
||||
|
||||
function TreeItem_<T extends { id: string }>({
|
||||
@@ -44,8 +50,9 @@ function TreeItem_<T extends { id: string }>({
|
||||
getEditOptions,
|
||||
className,
|
||||
depth,
|
||||
addRef,
|
||||
}: TreeItemProps<T>) {
|
||||
const ref = useRef<HTMLLIElement>(null);
|
||||
const listItemRef = useRef<HTMLLIElement>(null);
|
||||
const draggableRef = useRef<HTMLButtonElement>(null);
|
||||
const isSelected = useAtomValue(isSelectedFamily({ treeId, itemId: node.item.id }));
|
||||
const isCollapsed = useAtomValue(isCollapsedFamily({ treeId, itemId: node.item.id }));
|
||||
@@ -54,6 +61,17 @@ function TreeItem_<T extends { id: string }>({
|
||||
const [dropHover, setDropHover] = useState<null | 'drop' | 'animate'>(null);
|
||||
const startedHoverTimeout = useRef<NodeJS.Timeout>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
addRef?.(node.item, {
|
||||
rename: () => {
|
||||
if (getEditOptions != null) {
|
||||
setEditing(true);
|
||||
}
|
||||
},
|
||||
isRenaming: editing,
|
||||
});
|
||||
}, [addRef, editing, getEditOptions, node.item]);
|
||||
|
||||
const isAncestorCollapsedAtom = useMemo(
|
||||
() =>
|
||||
selectAtom(
|
||||
@@ -80,7 +98,7 @@ function TreeItem_<T extends { id: string }>({
|
||||
useEffect(
|
||||
function scrollIntoViewWhenSelected() {
|
||||
return jotaiStore.sub(isSelectedFamily({ treeId, itemId: node.item.id }), () => {
|
||||
ref.current?.scrollIntoView({ block: 'nearest' });
|
||||
listItemRef.current?.scrollIntoView({ block: 'nearest' });
|
||||
});
|
||||
},
|
||||
[node.item.id, treeId],
|
||||
@@ -103,10 +121,11 @@ function TreeItem_<T extends { id: string }>({
|
||||
const handleSubmitNameEdit = useCallback(
|
||||
async function submitNameEdit(el: HTMLInputElement) {
|
||||
getEditOptions?.(node.item).onChange(node.item, el.value);
|
||||
onClick?.(node.item, { shiftKey: false, ctrlKey: false, metaKey: false });
|
||||
// Slight delay for the model to propagate to the local store
|
||||
setTimeout(() => setEditing(false), 200);
|
||||
},
|
||||
[getEditOptions, node.item],
|
||||
[getEditOptions, node.item, onClick],
|
||||
);
|
||||
|
||||
const handleEditFocus = useCallback(function handleEditFocus(el: HTMLInputElement | null) {
|
||||
@@ -126,8 +145,10 @@ function TreeItem_<T extends { id: string }>({
|
||||
e.stopPropagation();
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
e.preventDefault();
|
||||
await handleSubmitNameEdit(e.currentTarget);
|
||||
if (editing) {
|
||||
e.preventDefault();
|
||||
await handleSubmitNameEdit(e.currentTarget);
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
e.preventDefault();
|
||||
@@ -135,7 +156,7 @@ function TreeItem_<T extends { id: string }>({
|
||||
break;
|
||||
}
|
||||
},
|
||||
[handleSubmitNameEdit],
|
||||
[editing, handleSubmitNameEdit],
|
||||
);
|
||||
|
||||
const handleDoubleClick = useCallback(() => {
|
||||
@@ -222,7 +243,7 @@ function TreeItem_<T extends { id: string }>({
|
||||
|
||||
return (
|
||||
<li
|
||||
ref={ref}
|
||||
ref={listItemRef}
|
||||
role="treeitem"
|
||||
aria-level={depth + 1}
|
||||
aria-expanded={node.children == null ? undefined : !isCollapsed}
|
||||
@@ -239,7 +260,7 @@ function TreeItem_<T extends { id: string }>({
|
||||
isSelected && 'selected',
|
||||
)}
|
||||
>
|
||||
<TreeIndentGuide treeId={treeId} depth={depth} />
|
||||
<TreeIndentGuide treeId={treeId} depth={depth} parentId={node.parent?.item.id ?? null} />
|
||||
<div
|
||||
className={classNames(
|
||||
'text-text-subtle',
|
||||
@@ -275,7 +296,7 @@ function TreeItem_<T extends { id: string }>({
|
||||
onClick={handleClick}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
disabled={editing}
|
||||
className="px-2 focus:outline-none flex items-center gap-2 h-full whitespace-nowrap"
|
||||
className="cursor-default tree-item-inner px-2 focus:outline-none flex items-center gap-2 h-full whitespace-nowrap"
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
tabIndex={isLastSelected ? 0 : -1}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Fragment, memo } from 'react';
|
||||
import type { SelectableTreeNode } from './common';
|
||||
import type { TreeProps } from './Tree';
|
||||
import { TreeDropMarker } from './TreeDropMarker';
|
||||
import type { TreeItemProps } from './TreeItem';
|
||||
import type { TreeItemHandle, TreeItemProps } from './TreeItem';
|
||||
import { TreeItem } from './TreeItem';
|
||||
|
||||
export type TreeItemListProps<T extends { id: string }> = Pick<
|
||||
@@ -15,6 +15,7 @@ export type TreeItemListProps<T extends { id: string }> = Pick<
|
||||
style?: CSSProperties;
|
||||
className?: string;
|
||||
forceDepth?: number;
|
||||
addTreeItemRef?: (item: T, n: TreeItemHandle | null) => void;
|
||||
};
|
||||
|
||||
function TreeItemList_<T extends { id: string }>({
|
||||
@@ -29,6 +30,7 @@ function TreeItemList_<T extends { id: string }>({
|
||||
style,
|
||||
treeId,
|
||||
forceDepth,
|
||||
addTreeItemRef,
|
||||
}: TreeItemListProps<T>) {
|
||||
return (
|
||||
<ul role="tree" style={style} className={className}>
|
||||
@@ -36,6 +38,7 @@ function TreeItemList_<T extends { id: string }>({
|
||||
{nodes.map((child, i) => (
|
||||
<Fragment key={getItemKey(child.node.item)}>
|
||||
<TreeItem
|
||||
addRef={addTreeItemRef}
|
||||
treeId={treeId}
|
||||
node={child.node}
|
||||
ItemInner={ItemInner}
|
||||
@@ -46,7 +49,7 @@ function TreeItemList_<T extends { id: string }>({
|
||||
getItemKey={getItemKey}
|
||||
depth={forceDepth == null ? child.depth : forceDepth}
|
||||
/>
|
||||
<TreeDropMarker node={child.node} treeId={treeId} index={i+1} />
|
||||
<TreeDropMarker node={child.node} treeId={treeId} index={i + 1} />
|
||||
</Fragment>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -45,8 +45,14 @@ export const hoveredParentFamily = atomFamily((_treeId: string) => {
|
||||
});
|
||||
});
|
||||
|
||||
export const isParentHoveredFamily = atomFamily(
|
||||
({ treeId, parentId }: { treeId: string; parentId: string | null }) =>
|
||||
selectAtom(hoveredParentFamily(treeId), (v) => v.parentId === parentId, Object.is),
|
||||
(a, b) => a.treeId === b.treeId && a.parentId === b.parentId,
|
||||
);
|
||||
|
||||
export const isIndexHoveredFamily = atomFamily(
|
||||
({ treeId, index }: { treeId: string; index: number}) =>
|
||||
({ treeId, index }: { treeId: string; index: number }) =>
|
||||
selectAtom(hoveredParentFamily(treeId), (v) => v.index === index, Object.is),
|
||||
(a, b) => a.treeId === b.treeId && a.index === b.index,
|
||||
);
|
||||
@@ -55,8 +61,8 @@ export const hoveredParentDepthFamily = atomFamily((treeId: string) =>
|
||||
selectAtom(
|
||||
hoveredParentFamily(treeId),
|
||||
(s) => s.parentDepth,
|
||||
(a, b) => Object.is(a, b) // prevents re-render unless the value changes
|
||||
)
|
||||
(a, b) => Object.is(a, b), // prevents re-render unless the value changes
|
||||
),
|
||||
);
|
||||
|
||||
export const collapsedFamily = atomFamily((workspaceId: string) => {
|
||||
|
||||
@@ -3,12 +3,10 @@ import 'react-pdf/dist/Page/AnnotationLayer.css';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import './PdfViewer.css';
|
||||
import type { PDFDocumentProxy } from 'pdfjs-dist';
|
||||
import React, { lazy, useRef, useState } from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Document, Page } from 'react-pdf';
|
||||
import { useContainerSize } from '../../hooks/useContainerQuery';
|
||||
|
||||
const Document = lazy(() => import('react-pdf').then((m) => ({ default: m.Document })));
|
||||
const Page = lazy(() => import('react-pdf').then((m) => ({ default: m.Page })));
|
||||
|
||||
import('react-pdf').then(({ pdfjs }) => {
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
||||
'pdfjs-dist/build/pdf.worker.min.mjs',
|
||||
|
||||
@@ -132,7 +132,7 @@ export function TextViewer({ language, text, response, requestId, pretty, classN
|
||||
language={language}
|
||||
actions={actions}
|
||||
extraExtensions={extraExtensions}
|
||||
stateKey={null}
|
||||
stateKey={'response.body.' + response.id}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { capitalize } from '../lib/capitalize';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
|
||||
const HOLD_KEYS = ['Shift', 'Control', 'Command', 'Alt', 'Meta'];
|
||||
const SINGLE_WHITELIST = ['Delete', 'Enter', 'Backspace'];
|
||||
|
||||
export type HotkeyAction =
|
||||
| 'app.zoom_in'
|
||||
@@ -17,11 +18,14 @@ export type HotkeyAction =
|
||||
| 'model.create'
|
||||
| 'model.duplicate'
|
||||
| 'request.send'
|
||||
| 'request_switcher.next'
|
||||
| 'request_switcher.prev'
|
||||
| 'request_switcher.toggle'
|
||||
| 'request.rename'
|
||||
| 'switcher.next'
|
||||
| 'switcher.prev'
|
||||
| 'switcher.toggle'
|
||||
| 'settings.show'
|
||||
| 'sidebar.delete_selected_item'
|
||||
| 'sidebar.selected.delete'
|
||||
| 'sidebar.selected.duplicate'
|
||||
| 'sidebar.selected.rename'
|
||||
| 'sidebar.focus'
|
||||
| 'url_bar.focus'
|
||||
| 'workspace_settings.show';
|
||||
@@ -32,15 +36,18 @@ const hotkeys: Record<HotkeyAction, string[]> = {
|
||||
'app.zoom_reset': ['CmdCtrl+0'],
|
||||
'command_palette.toggle': ['CmdCtrl+k'],
|
||||
'environmentEditor.toggle': ['CmdCtrl+Shift+E', 'CmdCtrl+Shift+e'],
|
||||
'request.rename': type() === 'macos' ? ['Control+Shift+r'] : ['F2'],
|
||||
'request.send': ['CmdCtrl+Enter', 'CmdCtrl+r'],
|
||||
'hotkeys.showHelp': ['CmdCtrl+Shift+/', 'CmdCtrl+Shift+?'], // when shift is pressed, it might be a question mark
|
||||
'model.create': ['CmdCtrl+n'],
|
||||
'model.duplicate': ['CmdCtrl+d'],
|
||||
'request_switcher.next': ['Control+Shift+Tab'],
|
||||
'request_switcher.prev': ['Control+Tab'],
|
||||
'request_switcher.toggle': ['CmdCtrl+p'],
|
||||
'switcher.next': ['Control+Shift+Tab'],
|
||||
'switcher.prev': ['Control+Tab'],
|
||||
'switcher.toggle': ['CmdCtrl+p'],
|
||||
'settings.show': ['CmdCtrl+,'],
|
||||
'sidebar.delete_selected_item': ['Delete', 'CmdCtrl+Backspace'],
|
||||
'sidebar.selected.delete': ['Delete', 'CmdCtrl+Backspace'],
|
||||
'sidebar.selected.duplicate': ['CmdCtrl+d'],
|
||||
'sidebar.selected.rename': ['Enter'],
|
||||
'sidebar.focus': ['CmdCtrl+b'],
|
||||
'url_bar.focus': ['CmdCtrl+l'],
|
||||
'workspace_settings.show': ['CmdCtrl+;'],
|
||||
@@ -55,12 +62,15 @@ const hotkeyLabels: Record<HotkeyAction, string> = {
|
||||
'hotkeys.showHelp': 'Show Keyboard Shortcuts',
|
||||
'model.create': 'New Request',
|
||||
'model.duplicate': 'Duplicate Request',
|
||||
'request.rename': 'Rename',
|
||||
'request.send': 'Send',
|
||||
'request_switcher.next': 'Go To Previous Request',
|
||||
'request_switcher.prev': 'Go To Next Request',
|
||||
'request_switcher.toggle': 'Toggle Request Switcher',
|
||||
'switcher.next': 'Go To Previous Request',
|
||||
'switcher.prev': 'Go To Next Request',
|
||||
'switcher.toggle': 'Toggle Request Switcher',
|
||||
'settings.show': 'Open Settings',
|
||||
'sidebar.delete_selected_item': 'Delete Request',
|
||||
'sidebar.selected.delete': 'Delete',
|
||||
'sidebar.selected.duplicate': 'Duplicate',
|
||||
'sidebar.selected.rename': 'Rename',
|
||||
'sidebar.focus': 'Focus or Toggle Sidebar',
|
||||
'url_bar.focus': 'Focus URL',
|
||||
'workspace_settings.show': 'Open Workspace Settings',
|
||||
@@ -73,6 +83,7 @@ export const hotkeyActions: HotkeyAction[] = Object.keys(hotkeys) as (keyof type
|
||||
export type HotKeyOptions = {
|
||||
enable?: boolean | (() => boolean);
|
||||
priority?: number;
|
||||
allowDefault?: boolean;
|
||||
};
|
||||
|
||||
interface Callback {
|
||||
@@ -142,7 +153,7 @@ function handleKeyUp(e: KeyboardEvent) {
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
// Don't add key if not holding modifier
|
||||
const isValidKeymapKey =
|
||||
e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.key === 'Backspace' || e.key === 'Delete';
|
||||
e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || SINGLE_WHITELIST.includes(e.key);
|
||||
if (!isValidKeymapKey) {
|
||||
return;
|
||||
}
|
||||
@@ -162,7 +173,7 @@ function handleKeyDown(e: KeyboardEvent) {
|
||||
if (e.metaKey) currentKeysWithModifiers.add('Meta');
|
||||
if (e.shiftKey) currentKeysWithModifiers.add('Shift');
|
||||
|
||||
for (const [hkAction, hkKeys] of Object.entries(hotkeys) as [HotkeyAction, string[]][]) {
|
||||
outer: for (const [hkAction, hkKeys] of Object.entries(hotkeys) as [HotkeyAction, string[]][]) {
|
||||
if (
|
||||
(e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) &&
|
||||
currentKeysWithModifiers.size === 1 &&
|
||||
@@ -175,24 +186,24 @@ function handleKeyDown(e: KeyboardEvent) {
|
||||
|
||||
const executed: string[] = [];
|
||||
for (const { action, callback, options } of jotaiStore.get(sortedCallbacksAtom)) {
|
||||
const enable = typeof options.enable === 'function' ? options.enable() : options.enable;
|
||||
if (enable === false) {
|
||||
if (hkAction !== action) {
|
||||
continue;
|
||||
}
|
||||
if (hkAction !== action) {
|
||||
const enable = typeof options.enable === 'function' ? options.enable() : options.enable;
|
||||
if (enable === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const hkKey of hkKeys) {
|
||||
const keys = hkKey.split('+').map(resolveHotkeyKey);
|
||||
if (
|
||||
keys.length === currentKeysWithModifiers.size &&
|
||||
keys.every((key) => currentKeysWithModifiers.has(key))
|
||||
) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (compareKeys(keys, Array.from(currentKeysWithModifiers))) {
|
||||
if (!options.allowDefault) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
callback(e);
|
||||
executed.push(`${action} ${options.priority ?? 0}`);
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,3 +269,10 @@ const resolveHotkeyKey = (key: string) => {
|
||||
else if (key === 'CmdCtrl') return 'Control';
|
||||
else return key;
|
||||
};
|
||||
|
||||
function compareKeys(keysA: string[], keysB: string[]) {
|
||||
if (keysA.length !== keysB.length) return false;
|
||||
const sortedA = keysA.map((k) => k.toLowerCase()).sort().join('::');
|
||||
const sortedB = keysB.map((k) => k.toLowerCase()).sort().join('::');
|
||||
return sortedA === sortedB;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { generateId } from '../lib/generateId';
|
||||
|
||||
export function useRandomKey() {
|
||||
const [value, setValue] = useState<string>(generateId());
|
||||
export function useRandomKey(initialValue?: string) {
|
||||
const [value, setValue] = useState<string>(initialValue ?? generateId());
|
||||
const regenerate = useCallback(() => setValue(generateId()), []);
|
||||
return [value, regenerate] as const;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export function useResponseBodyText({
|
||||
filter: string | null;
|
||||
}) {
|
||||
return useQuery({
|
||||
placeholderData: (prev) => prev, // Keep previous data on refetch
|
||||
queryKey: [
|
||||
'response_body_text',
|
||||
response.id,
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import xmlFormat from 'xml-formatter';
|
||||
import { invokeCmd } from './tauri';
|
||||
|
||||
const INDENT = ' ';
|
||||
|
||||
export async function tryFormatJson(text: string): Promise<string> {
|
||||
if (text === '') return text;
|
||||
|
||||
@@ -11,14 +8,12 @@ export async function tryFormatJson(text: string): Promise<string> {
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.warn('Failed to format JSON', err);
|
||||
// Nothing
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(text), null, 2);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (err) {
|
||||
// Nothing
|
||||
console.log('JSON beautify failed', err);
|
||||
}
|
||||
|
||||
return text;
|
||||
@@ -28,9 +23,11 @@ export async function tryFormatXml(text: string): Promise<string> {
|
||||
if (text === '') return text;
|
||||
|
||||
try {
|
||||
return xmlFormat(text, { throwOnFailure: true, strictMode: false, indentation: INDENT });
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const result = await invokeCmd<string>('cmd_format_xml', { text });
|
||||
return result;
|
||||
} catch (err) {
|
||||
return text;
|
||||
console.warn('Failed to format XML', err);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,9 @@ async function performImport(filePath: string): Promise<boolean> {
|
||||
return (
|
||||
<VStack space={3} className="pb-4">
|
||||
<ul className="list-disc pl-6">
|
||||
<li>{pluralizeCount('Workspace', imported.workspaces.length)}</li>
|
||||
{imported.workspaces.length > 0 && (
|
||||
<li>{pluralizeCount('Workspace', imported.workspaces.length)}</li>
|
||||
)}
|
||||
{imported.environments.length > 0 && (
|
||||
<li>{pluralizeCount('Environment', imported.environments.length)}</li>
|
||||
)}
|
||||
|
||||
@@ -15,6 +15,7 @@ type TauriCmd =
|
||||
| 'cmd_dismiss_notification'
|
||||
| 'cmd_export_data'
|
||||
| 'cmd_format_json'
|
||||
| 'cmd_format_xml'
|
||||
| 'cmd_get_http_authentication_config'
|
||||
| 'cmd_get_http_authentication_summaries'
|
||||
| 'cmd_get_sse_events'
|
||||
|
||||
@@ -6,7 +6,9 @@ import { resolveAppearance } from './appearance';
|
||||
export async function getThemes() {
|
||||
const themes = (await invokeCmd<GetThemesResponse[]>('cmd_get_themes')).flatMap((t) => t.themes);
|
||||
themes.sort((a, b) => a.label.localeCompare(b.label));
|
||||
return { themes: [yaakDark, yaakLight, ...themes] };
|
||||
// Remove duplicates, in case multiple plugins provide the same theme
|
||||
const uniqueThemes = Array.from(new Map(themes.map((t) => [t.id, t])).values());
|
||||
return { themes: [yaakDark, yaakLight, ...uniqueThemes] };
|
||||
}
|
||||
|
||||
export async function getResolvedTheme(
|
||||
|
||||
1
src-web/modules.d.ts
vendored
1
src-web/modules.d.ts
vendored
@@ -1 +1,2 @@
|
||||
declare module 'format-graphql';
|
||||
declare module 'xml-beautify';
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@tanstack/react-router": "^1.133.13",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@tauri-apps/api": "^2.8.0",
|
||||
"@tauri-apps/api": "^2.9.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.2",
|
||||
@@ -66,7 +66,7 @@
|
||||
"slugify": "^1.6.6",
|
||||
"uuid": "^11.1.0",
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"xml-formatter": "^3.6.3",
|
||||
"xml-beautify": "^1.2.3",
|
||||
"yaml": "^2.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -90,7 +90,7 @@
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-nesting": "^13.0.2",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"vite": "^7.0.7",
|
||||
"vite": "^7.0.8",
|
||||
"vite-plugin-static-copy": "^3.1.2",
|
||||
"vite-plugin-svgr": "^4.3.0",
|
||||
"vite-plugin-top-level-await": "^1.5.0",
|
||||
|
||||
Reference in New Issue
Block a user