mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-15 15:17:45 +01:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e6523f477 | ||
|
|
0c8d180928 | ||
|
|
e0f547b93f | ||
|
|
0a3506f81e | ||
|
|
e72c1e68e5 | ||
|
|
ef1ba9b834 | ||
|
|
846f4d9551 | ||
|
|
4780bfe41f | ||
|
|
d0d01b3897 | ||
|
|
fc1e8baa23 | ||
|
|
d35116c494 | ||
|
|
1d257b365b | ||
|
|
1076d57e8a | ||
|
|
1c93d5775f | ||
|
|
7b78fac24e | ||
|
|
6534b3f622 | ||
|
|
daba21fbca | ||
|
|
3b99ea1cad | ||
|
|
937d7aa72a | ||
|
|
5bf7278479 | ||
|
|
095af8cf4b |
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
@@ -1,15 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: gschier
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom: https://yaak.app/pricing
|
||||
|
||||
@@ -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/"><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/andriyor"><img src="https://github.com/andriyor.png" width="50px" alt="User avatar: andriyor" /></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> <a href="https://github.com/jirizverina"><img src="https://github.com/jirizverina.png" width="50px" alt="User avatar: jirizverina" /></a> <a href="https://github.com/chip-well"><img src="https://github.com/chip-well.png" width="50px" alt="User avatar: chip-well" /></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/andriyor"><img src="https://github.com/andriyor.png" width="50px" alt="User avatar: andriyor" /></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> <a href="https://github.com/jirizverina"><img src="https://github.com/jirizverina.png" width="50px" alt="User avatar: jirizverina" /></a> <a href="https://github.com/chip-well"><img src="https://github.com/chip-well.png" width="50px" alt="User avatar: chip-well" /></a> <a href="https://github.com/GRAYAH"><img src="https://github.com/GRAYAH.png" width="50px" alt="User avatar: GRAYAH" /></a> <!-- sponsors-base -->
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
162
package-lock.json
generated
162
package-lock.json
generated
@@ -61,7 +61,7 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.7",
|
||||
"@tauri-apps/cli": "^2.9.1",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@yaakapp/cli": "^0.3.4",
|
||||
"nodejs-file-downloader": "^4.13.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
@@ -3216,9 +3216,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/api": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.9.0.tgz",
|
||||
"integrity": "sha512-qD5tMjh7utwBk9/5PrTA/aGr3i5QaJ/Mlt7p8NilQ45WgbifUNPyKWsA63iQ8YfQq6R8ajMapU+/Q8nMcPRLNw==",
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.9.1.tgz",
|
||||
"integrity": "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw==",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -3226,9 +3226,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.9.1.tgz",
|
||||
"integrity": "sha512-kKi2/WWsNXKoMdatBl4xrT7e1Ce27JvsetBVfWuIb6D3ep/Y0WO5SIr70yarXOSWam8NyDur4ipzjZkg6m7VDg==",
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.9.6.tgz",
|
||||
"integrity": "sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"bin": {
|
||||
@@ -3242,23 +3242,23 @@
|
||||
"url": "https://opencollective.com/tauri"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@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"
|
||||
"@tauri-apps/cli-darwin-arm64": "2.9.6",
|
||||
"@tauri-apps/cli-darwin-x64": "2.9.6",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.9.6",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.9.6",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.9.6",
|
||||
"@tauri-apps/cli-linux-riscv64-gnu": "2.9.6",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.9.6",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.9.6",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.9.6",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.9.6",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.9.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||
"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==",
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.9.6.tgz",
|
||||
"integrity": "sha512-gf5no6N9FCk1qMrti4lfwP77JHP5haASZgVbBgpZG7BUepB3fhiLCXGUK8LvuOjP36HivXewjg72LTnPDScnQQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3273,9 +3273,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||
"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==",
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.9.6.tgz",
|
||||
"integrity": "sha512-oWh74WmqbERwwrwcueJyY6HYhgCksUc6NT7WKeXyrlY/FPmNgdyQAgcLuTSkhRFuQ6zh4Np1HZpOqCTpeZBDcw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3290,9 +3290,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"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==",
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.9.6.tgz",
|
||||
"integrity": "sha512-/zde3bFroFsNXOHN204DC2qUxAcAanUjVXXSdEGmhwMUZeAQalNj5cz2Qli2elsRjKN/hVbZOJj0gQ5zaYUjSg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -3307,9 +3307,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"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==",
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.9.6.tgz",
|
||||
"integrity": "sha512-pvbljdhp9VOo4RnID5ywSxgBs7qiylTPlK56cTk7InR3kYSTJKYMqv/4Q/4rGo/mG8cVppesKIeBMH42fw6wjg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3324,9 +3324,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||
"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==",
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.9.6.tgz",
|
||||
"integrity": "sha512-02TKUndpodXBCR0oP//6dZWGYcc22Upf2eP27NvC6z0DIqvkBBFziQUcvi2n6SrwTRL0yGgQjkm9K5NIn8s6jw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3341,9 +3341,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
|
||||
"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==",
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.9.6.tgz",
|
||||
"integrity": "sha512-fmp1hnulbqzl1GkXl4aTX9fV+ubHw2LqlLH1PE3BxZ11EQk+l/TmiEongjnxF0ie4kV8DQfDNJ1KGiIdWe1GvQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -3358,9 +3358,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||
"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==",
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.9.6.tgz",
|
||||
"integrity": "sha512-vY0le8ad2KaV1PJr+jCd8fUF9VOjwwQP/uBuTJvhvKTloEwxYA/kAjKK9OpIslGA9m/zcnSo74czI6bBrm2sYA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3375,9 +3375,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||
"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==",
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.9.6.tgz",
|
||||
"integrity": "sha512-TOEuB8YCFZTWVDzsO2yW0+zGcoMiPPwcUgdnW1ODnmgfwccpnihDRoks+ABT1e3fHb1ol8QQWsHSCovb3o2ENQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3392,9 +3392,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"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==",
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.9.6.tgz",
|
||||
"integrity": "sha512-ujmDGMRc4qRLAnj8nNG26Rlz9klJ0I0jmZs2BPpmNNf0gM/rcVHhqbEkAaHPTBVIrtUdf7bGvQAD2pyIiUrBHQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -3409,9 +3409,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"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==",
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.9.6.tgz",
|
||||
"integrity": "sha512-S4pT0yAJgFX8QRCyKA1iKjZ9Q/oPjCZf66A/VlG5Yw54Nnr88J1uBpmenINbXxzyhduWrIXBaUbEY1K80ZbpMg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -3426,9 +3426,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||
"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==",
|
||||
"version": "2.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.9.6.tgz",
|
||||
"integrity": "sha512-ldWuWSSkWbKOPjQMJoYVj9wLHcOniv7diyI5UAJ4XsBdtaFB0pKHQsqw/ItUma0VXGC7vB4E9fZjivmxur60aw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3443,63 +3443,63 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-clipboard-manager": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.3.0.tgz",
|
||||
"integrity": "sha512-81NOBA2P+OTY8RLkBwyl9ZR/0CeggLub4F6zxcxUIfFOAqtky7J61+K/MkH2SC1FMxNBxrX0swDuKvkjkHadlA==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.3.2.tgz",
|
||||
"integrity": "sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.6.0"
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-dialog": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.4.0.tgz",
|
||||
"integrity": "sha512-OvXkrEBfWwtd8tzVCEXIvRfNEX87qs2jv6SqmVPiHcJjBhSF/GUvjqUNIDmKByb5N8nvDqVUM7+g1sXwdC/S9w==",
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.4.2.tgz",
|
||||
"integrity": "sha512-lNIn5CZuw8WZOn8zHzmFmDSzg5zfohWoa3mdULP0YFh/VogVdMVWZPcWSHlydsiJhRQYaTNSYKN7RmZKE2lCYQ==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-fs": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.2.tgz",
|
||||
"integrity": "sha512-YGhmYuTgXGsi6AjoV+5mh2NvicgWBfVJHHheuck6oHD+HC9bVWPaHvCP0/Aw4pHDejwrvT8hE3+zZAaWf+hrig==",
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.4.tgz",
|
||||
"integrity": "sha512-MTorXxIRmOnOPT1jZ3w96vjSuScER38ryXY88vl5F0uiKdnvTKKTtaEjTEo8uPbl4e3gnUtfsDVwC7h77GQLvQ==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-log": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-log/-/plugin-log-2.7.0.tgz",
|
||||
"integrity": "sha512-81XQ2f93x4vmIB5OY0XlYAxy60cHdYLs0Ki8Qp38tNATRiuBit+Orh3frpY3qfYQnqEvYVyRub7YRJWlmW2RRA==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-log/-/plugin-log-2.7.1.tgz",
|
||||
"integrity": "sha512-jdb+o0wxQc8PjnLktgGpOs9Dh1YupaOGDXzO+Y8peA1UZ1ep3eXv4E1oiJ7nIQVN0XUFDDhnn3aBszl8ijhR+A==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-opener": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.0.tgz",
|
||||
"integrity": "sha512-B0LShOYae4CZjN8leiNDbnfjSrTwoZakqKaWpfoH6nXiJwt6Rgj6RnVIffG3DoJiKsffRhMkjmBV9VeilSb4TA==",
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.2.tgz",
|
||||
"integrity": "sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-os": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.3.1.tgz",
|
||||
"integrity": "sha512-ty5V8XDUIFbSnrk3zsFoP3kzN+vAufYzalJSlmrVhQTImIZa1aL1a03bOaP2vuBvfR+WDRC6NgV2xBl8G07d+w==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-os/-/plugin-os-2.3.2.tgz",
|
||||
"integrity": "sha512-n+nXWeuSeF9wcEsSPmRnBEGrRgOy6jjkSU+UVCOV8YUGKb2erhDOxis7IqRXiRVHhY8XMKks00BJ0OAdkpf6+A==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-shell": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.1.tgz",
|
||||
"integrity": "sha512-jjs2WGDO/9z2pjNlydY/F5yYhNsscv99K5lCmU5uKjsVvQ3dRlDhhtVYoa4OLDmktLtQvgvbQjCFibMl6tgGfw==",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.3.tgz",
|
||||
"integrity": "sha512-Xod+pRcFxmOWFWEnqH5yZcA7qwAMuaaDkMR1Sply+F8VfBj++CGnj2xf5UoialmjZ2Cvd8qrvSCbU+7GgNVsKQ==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.8.0"
|
||||
@@ -18687,14 +18687,14 @@
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@tanstack/react-router": "^1.133.13",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@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",
|
||||
"@tauri-apps/plugin-log": "^2.7.0",
|
||||
"@tauri-apps/plugin-opener": "^2.5.0",
|
||||
"@tauri-apps/plugin-os": "^2.3.1",
|
||||
"@tauri-apps/plugin-shell": "^2.3.1",
|
||||
"@tauri-apps/api": "^2.9.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.2",
|
||||
"@tauri-apps/plugin-fs": "^2.4.4",
|
||||
"@tauri-apps/plugin-log": "^2.7.1",
|
||||
"@tauri-apps/plugin-opener": "^2.5.2",
|
||||
"@tauri-apps/plugin-os": "^2.3.2",
|
||||
"@tauri-apps/plugin-shell": "^2.3.3",
|
||||
"buffer": "^6.0.3",
|
||||
"classnames": "^2.5.1",
|
||||
"cm6-graphql": "^0.2.1",
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.7",
|
||||
"@tauri-apps/cli": "^2.9.1",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@yaakapp/cli": "^0.3.4",
|
||||
"nodejs-file-downloader": "^4.13.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
||||
@@ -71,10 +71,10 @@ export async function getOrRefreshAccessToken(
|
||||
httpRequest.authenticationType = 'none'; // Don't inherit workspace auth
|
||||
const resp = await ctx.httpRequest.send({ httpRequest });
|
||||
|
||||
if (resp.status === 401) {
|
||||
// Bad refresh token, so we'll force it to fetch a fresh access token by deleting
|
||||
// and returning null;
|
||||
console.log('[oauth2] Unauthorized refresh_token request');
|
||||
if (resp.status >= 400 && resp.status < 500) {
|
||||
// Client errors (4xx) indicate the refresh token is invalid, expired, or revoked
|
||||
// Delete the token and return null to trigger a fresh authorization flow
|
||||
console.log('[oauth2] Refresh token request failed with client error, deleting token');
|
||||
await deleteToken(ctx, tokenArgs);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,34 @@ export const plugin: PluginDefinition = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes escape sequences in shell $'...' strings
|
||||
* Handles Unicode escape sequences (\uXXXX) and common escape codes
|
||||
*/
|
||||
function decodeShellString(str: string): string {
|
||||
return str
|
||||
.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
||||
.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
|
||||
.replace(/\\n/g, '\n')
|
||||
.replace(/\\r/g, '\r')
|
||||
.replace(/\\t/g, '\t')
|
||||
.replace(/\\'/g, "'")
|
||||
.replace(/\\"/g, '"')
|
||||
.replace(/\\\\/g, '\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a string might contain escape sequences that need decoding
|
||||
* If so, decodes them; otherwise returns the string as-is
|
||||
*/
|
||||
function maybeDecodeEscapeSequences(str: string): string {
|
||||
// Check if the string contains escape sequences that shell-quote might not handle
|
||||
if (str.includes('\\u') || str.includes('\\x')) {
|
||||
return decodeShellString(str);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export function convertCurl(rawData: string) {
|
||||
if (!rawData.match(/^\s*curl /)) {
|
||||
return null;
|
||||
@@ -86,9 +114,11 @@ export function convertCurl(rawData: string) {
|
||||
for (const parseEntry of normalizedParseEntries) {
|
||||
if (typeof parseEntry === 'string') {
|
||||
if (parseEntry.startsWith('$')) {
|
||||
currentCommand.push(parseEntry.slice(1));
|
||||
// Handle $'...' strings from shell-quote - decode escape sequences
|
||||
currentCommand.push(decodeShellString(parseEntry.slice(1)));
|
||||
} else {
|
||||
currentCommand.push(parseEntry);
|
||||
// Decode escape sequences that shell-quote might not handle
|
||||
currentCommand.push(maybeDecodeEscapeSequences(parseEntry));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -108,7 +138,7 @@ export function convertCurl(rawData: string) {
|
||||
|
||||
if (op?.startsWith('$')) {
|
||||
// Handle the case where literal like -H $'Header: \'Some Quoted Thing\''
|
||||
const str = op.slice(2, op.length - 1).replace(/\\'/g, "'");
|
||||
const str = decodeShellString(op.slice(2, op.length - 1));
|
||||
|
||||
currentCommand.push(str);
|
||||
continue;
|
||||
|
||||
@@ -391,6 +391,56 @@ describe('importer-curl', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Imports data with Unicode escape sequences', () => {
|
||||
expect(
|
||||
convertCurl(
|
||||
`curl 'https://yaak.app' -H 'Content-Type: application/json' --data-raw $'{"query":"SearchQueryInput\\u0021"}' -X POST`,
|
||||
),
|
||||
).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
baseRequest({
|
||||
url: 'https://yaak.app',
|
||||
method: 'POST',
|
||||
headers: [{ name: 'Content-Type', value: 'application/json', enabled: true }],
|
||||
bodyType: 'application/json',
|
||||
body: { text: '{"query":"SearchQueryInput!"}' },
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('Imports data with multiple escape sequences', () => {
|
||||
expect(
|
||||
convertCurl(
|
||||
`curl 'https://yaak.app' --data-raw $'Line1\\nLine2\\tTab\\u0021Exclamation' -X POST`,
|
||||
),
|
||||
).toEqual({
|
||||
resources: {
|
||||
workspaces: [baseWorkspace()],
|
||||
httpRequests: [
|
||||
baseRequest({
|
||||
url: 'https://yaak.app',
|
||||
method: 'POST',
|
||||
bodyType: 'application/x-www-form-urlencoded',
|
||||
body: {
|
||||
form: [{ name: 'Line1\nLine2\tTab!Exclamation', value: '', enabled: true }],
|
||||
},
|
||||
headers: [
|
||||
{
|
||||
enabled: true,
|
||||
name: 'Content-Type',
|
||||
value: 'application/x-www-form-urlencoded',
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const idCount: Partial<Record<string, number>> = {};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"scripts": {
|
||||
"build": "run-s build:*",
|
||||
"build:1-build": "yaakcli build",
|
||||
"build:2-cpywasm": "cp \"../../node_modules/@1password/sdk-core/nodejs/core_bg.wasm\" build/",
|
||||
"build:2-cpywasm": "cpx \"../../node_modules/@1password/sdk-core/nodejs/core_bg.wasm\" build/",
|
||||
"dev": "yaakcli dev"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,26 +1,86 @@
|
||||
import crypto from 'node:crypto';
|
||||
import type { Client } from '@1password/sdk';
|
||||
import { createClient } from '@1password/sdk';
|
||||
import type { PluginDefinition } from '@yaakapp/api';
|
||||
import { createClient, DesktopAuth } from '@1password/sdk';
|
||||
import type { JsonPrimitive, PluginDefinition } from '@yaakapp/api';
|
||||
import type { CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
|
||||
|
||||
const _clients: Record<string, Client> = {};
|
||||
|
||||
async function op(args: CallTemplateFunctionArgs): Promise<Client | null> {
|
||||
const token = args.values.token;
|
||||
if (typeof token !== 'string') return null;
|
||||
async function op(args: CallTemplateFunctionArgs): Promise<{ client?: Client; error?: unknown }> {
|
||||
let authMethod: string | DesktopAuth | null = null;
|
||||
let hash: string | null = null;
|
||||
switch (args.values.authMethod) {
|
||||
case 'desktop': {
|
||||
const account = args.values.token;
|
||||
if (typeof account !== 'string' || !account) return { error: 'Missing account name' };
|
||||
|
||||
hash = crypto.createHash('sha256').update(`desktop:${account}`).digest('hex');
|
||||
authMethod = new DesktopAuth(account);
|
||||
break;
|
||||
}
|
||||
case 'token': {
|
||||
const token = args.values.token;
|
||||
if (typeof token !== 'string' || !token) return { error: 'Missing service token' };
|
||||
|
||||
hash = crypto.createHash('sha256').update(`token:${token}`).digest('hex');
|
||||
authMethod = token;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hash == null || authMethod == null) return { error: 'Invalid authentication method' };
|
||||
|
||||
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
|
||||
try {
|
||||
_clients[tokenHash] ??= await createClient({
|
||||
auth: token,
|
||||
_clients[hash] ??= await createClient({
|
||||
auth: authMethod,
|
||||
integrationName: 'Yaak 1Password Plugin',
|
||||
integrationVersion: 'v1.0.0',
|
||||
});
|
||||
} catch {
|
||||
return null;
|
||||
} catch (e) {
|
||||
return { error: e };
|
||||
}
|
||||
return _clients[tokenHash];
|
||||
|
||||
return { client: _clients[hash] };
|
||||
}
|
||||
|
||||
async function getValue(
|
||||
args: CallTemplateFunctionArgs,
|
||||
vaultId?: JsonPrimitive,
|
||||
itemId?: JsonPrimitive,
|
||||
fieldId?: JsonPrimitive,
|
||||
): Promise<{ value?: string; error?: unknown }> {
|
||||
const { client, error } = await op(args);
|
||||
if (!client) return { error };
|
||||
|
||||
if (vaultId && typeof vaultId === 'string') {
|
||||
try {
|
||||
await client.vaults.getOverview(vaultId);
|
||||
} catch {
|
||||
return { error: `Vault ${vaultId} not found` };
|
||||
}
|
||||
} else {
|
||||
return { error: 'No vault specified' };
|
||||
}
|
||||
|
||||
if (itemId && typeof itemId === 'string') {
|
||||
try {
|
||||
const item = await client.items.get(vaultId, itemId);
|
||||
if (fieldId && typeof fieldId === 'string') {
|
||||
const field = item.fields.find((f) => f.id === fieldId);
|
||||
if (field) {
|
||||
return { value: field.value };
|
||||
} else {
|
||||
return { error: `Field ${fieldId} not found in item ${itemId} in vault ${vaultId}` };
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return { error: `Item ${itemId} not found in vault ${vaultId}` };
|
||||
}
|
||||
} else {
|
||||
return { error: 'No item specified' };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
@@ -31,14 +91,50 @@ export const plugin: PluginDefinition = {
|
||||
previewArgs: ['field'],
|
||||
args: [
|
||||
{
|
||||
name: 'token',
|
||||
type: 'text',
|
||||
label: '1Password Service Account Token',
|
||||
description:
|
||||
'Token can be generated from the 1Password website by visiting Developer > Service Accounts',
|
||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Yaak template syntax
|
||||
defaultValue: '${[1PASSWORD_TOKEN]}',
|
||||
password: true,
|
||||
type: 'h_stack',
|
||||
inputs: [
|
||||
{
|
||||
name: 'authMethod',
|
||||
type: 'select',
|
||||
label: 'Authentication Method',
|
||||
defaultValue: 'token',
|
||||
options: [
|
||||
{
|
||||
label: 'Service Account',
|
||||
value: 'token',
|
||||
},
|
||||
{
|
||||
label: 'Desktop App',
|
||||
value: 'desktop',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'token',
|
||||
type: 'text',
|
||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Yaak template syntax
|
||||
defaultValue: '${[1PASSWORD_TOKEN]}',
|
||||
dynamic(_ctx, args) {
|
||||
switch (args.values.authMethod) {
|
||||
case 'desktop':
|
||||
return {
|
||||
label: 'Account Name',
|
||||
description:
|
||||
'Account name can be taken from the sidebar of the 1Password App. Make sure you\'re on the BETA version of the 1Password app and have "Integrate with other apps" enabled in Settings > Developer.',
|
||||
};
|
||||
case 'token':
|
||||
return {
|
||||
label: 'Token',
|
||||
description:
|
||||
'Token can be generated from the 1Password website by visiting Developer > Service Accounts',
|
||||
password: true,
|
||||
};
|
||||
}
|
||||
|
||||
return { hidden: true };
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'vault',
|
||||
@@ -46,7 +142,7 @@ export const plugin: PluginDefinition = {
|
||||
type: 'select',
|
||||
options: [],
|
||||
async dynamic(_ctx, args) {
|
||||
const client = await op(args);
|
||||
const { client } = await op(args);
|
||||
if (client == null) return { hidden: true };
|
||||
// Fetches a secret.
|
||||
const vaults = await client.vaults.list({ decryptDetails: true });
|
||||
@@ -64,18 +160,23 @@ export const plugin: PluginDefinition = {
|
||||
type: 'select',
|
||||
options: [],
|
||||
async dynamic(_ctx, args) {
|
||||
const client = await op(args);
|
||||
const { client } = await op(args);
|
||||
if (client == null) return { hidden: true };
|
||||
const vaultId = args.values.vault;
|
||||
if (typeof vaultId !== 'string') return { hidden: true };
|
||||
|
||||
const items = await client.items.list(vaultId);
|
||||
return {
|
||||
options: items.map((item) => ({
|
||||
label: `${item.title} ${item.category}`,
|
||||
value: item.id,
|
||||
})),
|
||||
};
|
||||
try {
|
||||
const items = await client.items.list(vaultId);
|
||||
return {
|
||||
options: items.map((item) => ({
|
||||
label: `${item.title} ${item.category}`,
|
||||
value: item.id,
|
||||
})),
|
||||
};
|
||||
} catch {
|
||||
// Hide as we can't list the items for this vault
|
||||
return { hidden: true };
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -84,7 +185,7 @@ export const plugin: PluginDefinition = {
|
||||
type: 'select',
|
||||
options: [],
|
||||
async dynamic(_ctx, args) {
|
||||
const client = await op(args);
|
||||
const { client } = await op(args);
|
||||
if (client == null) return { hidden: true };
|
||||
const vaultId = args.values.vault;
|
||||
const itemId = args.values.item;
|
||||
@@ -92,34 +193,28 @@ export const plugin: PluginDefinition = {
|
||||
return { hidden: true };
|
||||
}
|
||||
|
||||
const item = await client.items.get(vaultId, itemId);
|
||||
|
||||
return {
|
||||
options: item.fields.map((field) => ({ label: field.title, value: field.id })),
|
||||
};
|
||||
try {
|
||||
const item = await client.items.get(vaultId, itemId);
|
||||
return {
|
||||
options: item.fields.map((field) => ({ label: field.title, value: field.id })),
|
||||
};
|
||||
} catch {
|
||||
// Hide as we can't find the item within this vault
|
||||
return { hidden: true };
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
async onRender(_ctx, args) {
|
||||
const client = await op(args);
|
||||
if (client == null) throw new Error('Invalid token');
|
||||
const vaultId = args.values.vault;
|
||||
const itemId = args.values.item;
|
||||
const fieldId = args.values.field;
|
||||
if (
|
||||
typeof vaultId !== 'string' ||
|
||||
typeof itemId !== 'string' ||
|
||||
typeof fieldId !== 'string'
|
||||
) {
|
||||
return null;
|
||||
const { value, error } = await getValue(args, vaultId, itemId, fieldId);
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const item = await client.items.get(vaultId, itemId);
|
||||
const field = item.fields.find((f) => f.id === fieldId);
|
||||
if (field == null) {
|
||||
throw new Error(`Field not found: ${fieldId}`);
|
||||
}
|
||||
return field.value ?? '';
|
||||
return value ?? '';
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@ if (version.startsWith('wasm-pack ')) {
|
||||
}
|
||||
|
||||
console.log('Installing wasm-pack via cargo...');
|
||||
execSync('cargo install wasm-pack', { stdio: 'inherit' });
|
||||
execSync('cargo install wasm-pack --locked', { stdio: 'inherit' });
|
||||
|
||||
function tryExecSync(cmd) {
|
||||
try {
|
||||
|
||||
68
src-tauri/Cargo.lock
generated
68
src-tauri/Cargo.lock
generated
@@ -3009,9 +3009,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
dependencies = [
|
||||
"value-bag",
|
||||
]
|
||||
@@ -5620,9 +5620,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.9.2"
|
||||
version = "2.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bceb52453e507c505b330afe3398510e87f428ea42b6e76ecb6bd63b15965b5"
|
||||
checksum = "8a3868da5508446a7cd08956d523ac3edf0a8bc20bf7e4038f9a95c2800d2033"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -5672,9 +5672,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "2.5.1"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a924b6c50fe83193f0f8b14072afa7c25b7a72752a2a73d9549b463f5fe91a38"
|
||||
checksum = "17fcb8819fd16463512a12f531d44826ce566f486d7ccd211c9c8cebdaec4e08"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
@@ -5694,9 +5694,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-codegen"
|
||||
version = "2.5.0"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c1fe64c74cc40f90848281a90058a6db931eb400b60205840e09801ee30f190"
|
||||
checksum = "9fa9844cefcf99554a16e0a278156ae73b0d8680bbc0e2ad1e4287aadd8489cf"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"brotli",
|
||||
@@ -5721,9 +5721,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-macros"
|
||||
version = "2.5.0"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "260c5d2eb036b76206b9fca20b7be3614cfd21046c5396f7959e0e64a4b07f2f"
|
||||
checksum = "3764a12f886d8245e66b7ee9b43ccc47883399be2019a61d80cf0f4117446fde"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@@ -5735,9 +5735,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin"
|
||||
version = "2.5.1"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "076c78a474a7247c90cad0b6e87e593c4c620ed4efdb79cbe0214f0021f6c39d"
|
||||
checksum = "0e1d0a4860b7ff570c891e1d2a586bf1ede205ff858fbc305e0b5ae5d14c1377"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"glob",
|
||||
@@ -5752,9 +5752,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-clipboard-manager"
|
||||
version = "2.3.0"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adddd9e9275b20e77af3061d100a25a884cced3c4c9ef680bd94dd0f7e26c1ca"
|
||||
checksum = "206dc20af4ed210748ba945c2774e60fd0acd52b9a73a028402caf809e9b6ecf"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"log",
|
||||
@@ -5767,9 +5767,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-deep-link"
|
||||
version = "2.4.3"
|
||||
version = "2.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd67112fb1131834c2a7398ffcba520dbbf62c17de3b10329acd1a3554b1a9bb"
|
||||
checksum = "6e82759f7c7d51de3cbde51c04b3f2332de52436ed84541182cd8944b04e9e73"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"plist",
|
||||
@@ -5828,9 +5828,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-log"
|
||||
version = "2.7.0"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c1438bc7662acd16d508c919b3c087efd63669a4c75625dff829b1c75975ec"
|
||||
checksum = "d5709c792b8630290b5d9811a1f8fe983dd925fc87c7fc7f4923616458cd00b6"
|
||||
dependencies = [
|
||||
"android_logger",
|
||||
"byte-unit",
|
||||
@@ -5850,9 +5850,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.5.0"
|
||||
version = "2.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5"
|
||||
checksum = "c26b72571d25dee25667940027114e60f569fc3974f8cefbe50c2cbc5fd65e3b"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"glob",
|
||||
@@ -5872,9 +5872,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-os"
|
||||
version = "2.3.1"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a1c77ebf6f20417ab2a74e8c310820ba52151406d0c80fbcea7df232e3f6ba"
|
||||
checksum = "d8f08346c8deb39e96f86973da0e2d76cbb933d7ac9b750f6dc4daf955a6f997"
|
||||
dependencies = [
|
||||
"gethostname 1.0.2",
|
||||
"log",
|
||||
@@ -5911,9 +5911,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-single-instance"
|
||||
version = "2.3.4"
|
||||
version = "2.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb9cac815bf11c4a80fb498666bcdad66d65b89e3ae24669e47806febb76389c"
|
||||
checksum = "dd707f8c86b4e3004e2c141fa24351f1909ba40ce1b8437e30d5ed5277dd3710"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -5959,9 +5959,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-window-state"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d5f6fe3291bfa609c7e0b0ee3bedac294d94c7018934086ce782c1d0f2a468e"
|
||||
checksum = "73736611e14142408d15353e21e3cca2f12a3cfb523ad0ce85999b6d2ef1a704"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"log",
|
||||
@@ -5974,9 +5974,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.9.1"
|
||||
version = "2.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9368f09358496f2229313fccb37682ad116b7f46fa76981efe116994a0628926"
|
||||
checksum = "87f766fe9f3d1efc4b59b17e7a891ad5ed195fa8d23582abb02e6c9a01137892"
|
||||
dependencies = [
|
||||
"cookie",
|
||||
"dpi",
|
||||
@@ -5999,9 +5999,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "2.9.1"
|
||||
version = "2.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "929f5df216f5c02a9e894554401bcdab6eec3e39ec6a4a7731c7067fc8688a93"
|
||||
checksum = "187a3f26f681bdf028f796ccf57cf478c1ee422c50128e5a0a6ebeb3f5910065"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http",
|
||||
@@ -6026,9 +6026,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-utils"
|
||||
version = "2.8.0"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b8bbe426abdbf52d050e52ed693130dbd68375b9ad82a3fb17efb4c8d85673"
|
||||
checksum = "76a423c51176eb3616ee9b516a9fa67fed5f0e78baaba680e44eb5dd2cc37490"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"brotli",
|
||||
@@ -6810,9 +6810,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "value-bag"
|
||||
version = "1.11.1"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
|
||||
checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
|
||||
@@ -28,7 +28,9 @@ name = "tauri_app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "lib"]
|
||||
|
||||
[profile.release]
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
# Currently disabled due to:
|
||||
# Warn Failed to add bundler type to the binary: __TAURI_BUNDLE_TYPE variable not found in binary. Make sure tauri crate and tauri-cli are up to date and that symbol stripping is disabled (https://doc.rust-lang.org/cargo/reference/profiles.html#strip). Updater plugin may not be able to update this package. This shouldn't normally happen, please report it to https://github.com/tauri-apps/tauri/issues
|
||||
strip = false
|
||||
|
||||
[features]
|
||||
cargo-clippy = []
|
||||
@@ -37,7 +39,7 @@ updater = []
|
||||
license = ["yaak-license"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.5.0", features = [] }
|
||||
tauri-build = { version = "2.5.3", features = [] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
openssl-sys = { version = "0.9.105", features = ["vendored"] } # For Ubuntu installation to work
|
||||
@@ -57,17 +59,17 @@ reqwest_cookie_store = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, features = ["raw_value"] }
|
||||
tauri = { workspace = true, features = ["devtools", "protocol-asset"] }
|
||||
tauri-plugin-clipboard-manager = "2.3.0"
|
||||
tauri-plugin-deep-link = "2.4.3"
|
||||
tauri-plugin-clipboard-manager = "2.3.2"
|
||||
tauri-plugin-deep-link = "2.4.5"
|
||||
tauri-plugin-dialog = { workspace = true }
|
||||
tauri-plugin-fs = "2.4.2"
|
||||
tauri-plugin-log = { version = "2.7.0", features = ["colored"] }
|
||||
tauri-plugin-opener = "2.5.0"
|
||||
tauri-plugin-os = "2.3.1"
|
||||
tauri-plugin-fs = "2.4.4"
|
||||
tauri-plugin-log = { version = "2.7.1", features = ["colored"] }
|
||||
tauri-plugin-opener = "2.5.2"
|
||||
tauri-plugin-os = "2.3.2"
|
||||
tauri-plugin-shell = { workspace = true }
|
||||
tauri-plugin-single-instance = { version = "2.3.4", features = ["deep-link"] }
|
||||
tauri-plugin-single-instance = { version = "2.3.6", features = ["deep-link"] }
|
||||
tauri-plugin-updater = "2.9.0"
|
||||
tauri-plugin-window-state = "2.4.0"
|
||||
tauri-plugin-window-state = "2.4.1"
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
tokio-stream = "0.1.17"
|
||||
@@ -99,9 +101,9 @@ rustls-platform-verifier = "0.6.2"
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.145"
|
||||
sha2 = "0.10.9"
|
||||
log = "0.4.28"
|
||||
tauri = "2.9.2"
|
||||
tauri-plugin = "2.5.1"
|
||||
log = "0.4.29"
|
||||
tauri = "2.9.5"
|
||||
tauri-plugin = "2.5.2"
|
||||
tauri-plugin-dialog = "2.4.2"
|
||||
tauri-plugin-shell = "2.3.3"
|
||||
thiserror = "2.0.17"
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
|
||||
<!-- Allow loading 1Password's dylib (signed with different Team ID) -->
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
|
||||
<!-- Re-enable for sandboxing. Currently disabled because auto-updater doesn't work with sandboxing.-->
|
||||
<!-- <key>com.apple.security.app-sandbox</key> <true/>-->
|
||||
<!-- <key>com.apple.security.files.user-selected.read-write</key> <true/>-->
|
||||
|
||||
@@ -31,6 +31,7 @@ use tokio::time;
|
||||
use yaak_common::window::WorkspaceWindowTrait;
|
||||
use yaak_grpc::manager::GrpcHandle;
|
||||
use yaak_grpc::{Code, ServiceDefinition, serialize_message};
|
||||
use yaak_mac_window::AppHandleMacWindowExt;
|
||||
use yaak_models::models::{
|
||||
AnyModel, CookieJar, Environment, GrpcConnection, GrpcConnectionState, GrpcEvent,
|
||||
GrpcEventType, GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, Plugin, Workspace,
|
||||
@@ -155,6 +156,7 @@ async fn cmd_grpc_reflect<R: Runtime>(
|
||||
request_id: &str,
|
||||
environment_id: Option<&str>,
|
||||
proto_files: Vec<String>,
|
||||
skip_cache: Option<bool>,
|
||||
window: WebviewWindow<R>,
|
||||
app_handle: AppHandle<R>,
|
||||
grpc_handle: State<'_, Mutex<GrpcHandle>>,
|
||||
@@ -195,6 +197,7 @@ async fn cmd_grpc_reflect<R: Runtime>(
|
||||
&proto_files.iter().map(|p| PathBuf::from_str(p).unwrap()).collect(),
|
||||
&metadata,
|
||||
workspace.setting_validate_certificates,
|
||||
skip_cache.unwrap_or(false),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| GenericError(e.to_string()))?)
|
||||
@@ -1321,7 +1324,13 @@ pub fn run() {
|
||||
}))
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
// Don't restore StateFlags::DECORATIONS because we want to be able to toggle them on/off on a restart
|
||||
// We could* make this work if we toggled them in the frontend before the window closes, but, this is nicer.
|
||||
.plugin(
|
||||
tauri_plugin_window_state::Builder::new()
|
||||
.with_state_flags(StateFlags::all() - StateFlags::DECORATIONS)
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
@@ -1391,6 +1400,10 @@ pub fn run() {
|
||||
let grpc_handle = GrpcHandle::new(&app.app_handle());
|
||||
app.manage(Mutex::new(grpc_handle));
|
||||
|
||||
// Specific settings
|
||||
let settings = app.db().get_settings();
|
||||
app.app_handle().set_native_titlebar(settings.use_native_titlebar);
|
||||
|
||||
monitor_plugin_events(&app.app_handle().clone());
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::history::get_or_upsert_launch_info;
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::{debug, info};
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Instant;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
|
||||
use ts_rs::TS;
|
||||
use yaak_common::api_client::yaak_api_client;
|
||||
@@ -21,7 +20,7 @@ const KV_KEY: &str = "seen";
|
||||
|
||||
// Create updater struct
|
||||
pub struct YaakNotifier {
|
||||
last_check: SystemTime,
|
||||
last_check: Option<Instant>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
@@ -47,9 +46,7 @@ pub struct YaakNotificationAction {
|
||||
|
||||
impl YaakNotifier {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
last_check: SystemTime::UNIX_EPOCH,
|
||||
}
|
||||
Self { last_check: None }
|
||||
}
|
||||
|
||||
pub async fn seen<R: Runtime>(&mut self, window: &WebviewWindow<R>, id: &str) -> Result<()> {
|
||||
@@ -69,13 +66,13 @@ impl YaakNotifier {
|
||||
|
||||
pub async fn maybe_check<R: Runtime>(&mut self, window: &WebviewWindow<R>) -> Result<()> {
|
||||
let app_handle = window.app_handle();
|
||||
let ignore_check = self.last_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS;
|
||||
|
||||
if ignore_check {
|
||||
if let Some(i) = self.last_check
|
||||
&& i.elapsed().as_secs() < MAX_UPDATE_CHECK_SECONDS
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.last_check = SystemTime::now();
|
||||
self.last_check = Some(Instant::now());
|
||||
|
||||
if !app_handle.db().get_settings().check_notifications {
|
||||
info!("Notifications are disabled. Skipping check.");
|
||||
@@ -88,13 +85,18 @@ impl YaakNotifier {
|
||||
let license_check = {
|
||||
use yaak_license::{LicenseCheckStatus, check_license};
|
||||
match check_license(window).await {
|
||||
Ok(LicenseCheckStatus::PersonalUse { .. }) => "personal".to_string(),
|
||||
Ok(LicenseCheckStatus::CommercialUse) => "commercial".to_string(),
|
||||
Ok(LicenseCheckStatus::InvalidLicense) => "invalid_license".to_string(),
|
||||
Ok(LicenseCheckStatus::Trialing { .. }) => "trialing".to_string(),
|
||||
Err(_) => "unknown".to_string(),
|
||||
Ok(LicenseCheckStatus::PersonalUse { .. }) => "personal",
|
||||
Ok(LicenseCheckStatus::Active { .. }) => "commercial",
|
||||
Ok(LicenseCheckStatus::PastDue { .. }) => "past_due",
|
||||
Ok(LicenseCheckStatus::Inactive { .. }) => "invalid_license",
|
||||
Ok(LicenseCheckStatus::Trialing { .. }) => "trialing",
|
||||
Ok(LicenseCheckStatus::Expired { .. }) => "expired",
|
||||
Ok(LicenseCheckStatus::Error { .. }) => "error",
|
||||
Err(_) => "unknown",
|
||||
}
|
||||
.to_string()
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "license"))]
|
||||
let license_check = "disabled".to_string();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::PathBuf;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::error::Result;
|
||||
use log::{debug, error, info, warn};
|
||||
@@ -24,7 +24,7 @@ const MAX_UPDATE_CHECK_HOURS_ALPHA: u64 = 1;
|
||||
|
||||
// Create updater struct
|
||||
pub struct YaakUpdater {
|
||||
last_update_check: SystemTime,
|
||||
last_check: Option<Instant>,
|
||||
}
|
||||
|
||||
pub enum UpdateMode {
|
||||
@@ -62,9 +62,7 @@ pub enum UpdateTrigger {
|
||||
|
||||
impl YaakUpdater {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
last_update_check: SystemTime::UNIX_EPOCH,
|
||||
}
|
||||
Self { last_check: None }
|
||||
}
|
||||
|
||||
pub async fn check_now<R: Runtime>(
|
||||
@@ -84,7 +82,7 @@ impl YaakUpdater {
|
||||
|
||||
let settings = window.db().get_settings();
|
||||
let update_key = format!("{:x}", md5::compute(settings.id));
|
||||
self.last_update_check = SystemTime::now();
|
||||
self.last_check = Some(Instant::now());
|
||||
|
||||
info!("Checking for updates mode={} autodl={}", mode, auto_download);
|
||||
|
||||
@@ -176,9 +174,10 @@ impl YaakUpdater {
|
||||
UpdateMode::Beta => MAX_UPDATE_CHECK_HOURS_BETA,
|
||||
UpdateMode::Alpha => MAX_UPDATE_CHECK_HOURS_ALPHA,
|
||||
} * (60 * 60);
|
||||
let seconds_since_last_check = self.last_update_check.elapsed().unwrap().as_secs();
|
||||
let ignore_check = seconds_since_last_check < update_period_seconds;
|
||||
if ignore_check {
|
||||
|
||||
if let Some(i) = self.last_check
|
||||
&& i.elapsed().as_secs() < update_period_seconds
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use tauri::{
|
||||
};
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
use tokio::sync::mpsc;
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
|
||||
const DEFAULT_WINDOW_WIDTH: f64 = 1100.0;
|
||||
const DEFAULT_WINDOW_HEIGHT: f64 = 600.0;
|
||||
@@ -94,7 +95,8 @@ pub(crate) fn create_window<R: Runtime>(
|
||||
});
|
||||
}
|
||||
|
||||
if config.hide_titlebar {
|
||||
let settings = handle.db().get_settings();
|
||||
if config.hide_titlebar && !settings.use_native_titlebar {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use tauri::TitleBarStyle;
|
||||
@@ -102,7 +104,6 @@ pub(crate) fn create_window<R: Runtime>(
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
// Doesn't seem to work from Rust, here, so we do it in main.tsx
|
||||
win_builder = win_builder.decorations(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ pub fn app_menu<R: Runtime>(app_handle: &AppHandle<R>) -> tauri::Result<Menu<R>>
|
||||
],
|
||||
)?;
|
||||
|
||||
#[cfg(target_os = "macos")] {
|
||||
window_menu.set_as_windows_menu_for_nsapp()?;
|
||||
}
|
||||
|
||||
let help_menu = Submenu::with_id_and_items(
|
||||
app_handle,
|
||||
HELP_SUBMENU_ID,
|
||||
@@ -44,6 +48,10 @@ pub fn app_menu<R: Runtime>(app_handle: &AppHandle<R>) -> tauri::Result<Menu<R>>
|
||||
],
|
||||
)?;
|
||||
|
||||
#[cfg(target_os = "macos")] {
|
||||
help_menu.set_as_windows_menu_for_nsapp()?;
|
||||
}
|
||||
|
||||
let menu = Menu::with_items(
|
||||
app_handle,
|
||||
&[
|
||||
|
||||
@@ -1,16 +1,38 @@
|
||||
use crate::error::Error::GitNotFound;
|
||||
use crate::error::Result;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::process::{Command, Stdio};
|
||||
use crate::error::Result;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::os::windows::process::CommandExt;
|
||||
use crate::error::Error::GitNotFound;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
|
||||
pub(crate) fn new_binary_command(dir: &Path) -> Result<Command> {
|
||||
let status = Command::new("git").arg("--version").status();
|
||||
// 1. Probe that `git` exists and is runnable
|
||||
let mut probe = Command::new("git");
|
||||
probe.arg("--version").stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
|
||||
|
||||
if let Err(_) = status {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
probe.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
let status = probe.status().map_err(|_| GitNotFound)?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(GitNotFound);
|
||||
}
|
||||
|
||||
// 2. Build the reusable git command
|
||||
let mut cmd = Command::new("git");
|
||||
cmd.arg("-C").arg(dir);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{MethodDefinition, ServiceDefinition, json_schema};
|
||||
use hyper_rustls::HttpsConnector;
|
||||
use hyper_util::client::legacy::Client;
|
||||
use hyper_util::client::legacy::connect::HttpConnector;
|
||||
use log::warn;
|
||||
use log::{info, warn};
|
||||
pub use prost_reflect::DynamicMessage;
|
||||
use prost_reflect::{DescriptorPool, MethodDescriptor, ServiceDescriptor};
|
||||
use serde_json::Deserializer;
|
||||
@@ -244,6 +244,12 @@ impl GrpcHandle {
|
||||
}
|
||||
|
||||
impl GrpcHandle {
|
||||
/// Remove cached descriptor pool for the given key, if present.
|
||||
pub fn invalidate_pool(&mut self, id: &str, uri: &str, proto_files: &Vec<PathBuf>) {
|
||||
let key = make_pool_key(id, uri, proto_files);
|
||||
self.pools.remove(&key);
|
||||
}
|
||||
|
||||
pub async fn reflect(
|
||||
&mut self,
|
||||
id: &str,
|
||||
@@ -253,6 +259,13 @@ impl GrpcHandle {
|
||||
validate_certificates: bool,
|
||||
) -> Result<bool, String> {
|
||||
let server_reflection = proto_files.is_empty();
|
||||
let key = make_pool_key(id, uri, proto_files);
|
||||
|
||||
// If we already have a pool for this key, reuse it and avoid re-reflection
|
||||
if self.pools.contains_key(&key) {
|
||||
return Ok(server_reflection);
|
||||
}
|
||||
|
||||
let pool = if server_reflection {
|
||||
let full_uri = uri_from_str(uri)?;
|
||||
fill_pool_from_reflection(&full_uri, metadata, validate_certificates).await
|
||||
@@ -260,7 +273,7 @@ impl GrpcHandle {
|
||||
fill_pool_from_files(&self.app_handle, proto_files).await
|
||||
}?;
|
||||
|
||||
self.pools.insert(make_pool_key(id, uri, proto_files), pool.clone());
|
||||
self.pools.insert(key, pool.clone());
|
||||
Ok(server_reflection)
|
||||
}
|
||||
|
||||
@@ -271,9 +284,13 @@ impl GrpcHandle {
|
||||
proto_files: &Vec<PathBuf>,
|
||||
metadata: &BTreeMap<String, String>,
|
||||
validate_certificates: bool,
|
||||
skip_cache: bool,
|
||||
) -> Result<Vec<ServiceDefinition>, String> {
|
||||
// Ensure reflection is up-to-date
|
||||
self.reflect(id, uri, proto_files, metadata, validate_certificates).await?;
|
||||
// Ensure we have a pool; reflect only if missing
|
||||
if skip_cache || self.get_pool(id, uri, proto_files).is_none() {
|
||||
info!("Reflecting gRPC services for {} at {}", id, uri);
|
||||
self.reflect(id, uri, proto_files, metadata, validate_certificates).await?;
|
||||
}
|
||||
|
||||
let pool = self.get_pool(id, uri, proto_files).ok_or("Failed to get pool".to_string())?;
|
||||
Ok(self.services_from_pool(&pool))
|
||||
@@ -312,8 +329,10 @@ impl GrpcHandle {
|
||||
metadata: &BTreeMap<String, String>,
|
||||
validate_certificates: bool,
|
||||
) -> Result<GrpcConnection, String> {
|
||||
let use_reflection =
|
||||
let use_reflection = proto_files.is_empty();
|
||||
if self.get_pool(id, uri, proto_files).is_none() {
|
||||
self.reflect(id, uri, proto_files, metadata, validate_certificates).await?;
|
||||
}
|
||||
let pool = self.get_pool(id, uri, proto_files).ok_or("Failed to get pool")?.clone();
|
||||
let uri = uri_from_str(uri)?;
|
||||
let conn = get_transport(validate_certificates);
|
||||
|
||||
@@ -6,8 +6,6 @@ export type ActivateLicenseRequestPayload = { licenseKey: string, appVersion: st
|
||||
|
||||
export type ActivateLicenseResponsePayload = { activationId: string, };
|
||||
|
||||
export type CheckActivationResponsePayload = { active: boolean, };
|
||||
|
||||
export type DeactivateLicenseRequestPayload = { appVersion: string, appPlatform: string, };
|
||||
|
||||
export type LicenseCheckStatus = { "type": "personal_use", trial_ended: string, } | { "type": "commercial_use" } | { "type": "invalid_license" } | { "type": "trialing", end: string, };
|
||||
export type LicenseCheckStatus = { "status": "personal_use", "data": { trial_ended: string, } } | { "status": "trialing", "data": { end: string, } } | { "status": "error", "data": { message: string, code: string, } } | { "status": "active", "data": { periodEnd: string, cancelAt: string | null, } } | { "status": "inactive", "data": { status: string, } } | { "status": "expired", "data": { changes: number, changesUrl: string | null, billingUrl: string, periodEnd: string, } } | { "status": "past_due", "data": { billingUrl: string, periodEnd: string, } };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::error::Error::{ClientError, ServerError};
|
||||
use crate::error::Error::{ClientError, JsonError, ServerError};
|
||||
use crate::error::Result;
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::{info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Add;
|
||||
@@ -24,13 +24,6 @@ pub struct CheckActivationRequestPayload {
|
||||
pub app_platform: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "license.ts")]
|
||||
pub struct CheckActivationResponsePayload {
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "license.ts")]
|
||||
@@ -63,6 +56,49 @@ pub struct APIErrorResponsePayload {
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "status", content = "data")]
|
||||
#[ts(export, export_to = "license.ts")]
|
||||
pub enum LicenseCheckStatus {
|
||||
// Local Types
|
||||
PersonalUse {
|
||||
trial_ended: DateTime<Utc>,
|
||||
},
|
||||
Trialing {
|
||||
end: DateTime<Utc>,
|
||||
},
|
||||
Error {
|
||||
message: String,
|
||||
code: String,
|
||||
},
|
||||
|
||||
// Server Types
|
||||
Active {
|
||||
#[serde(rename = "periodEnd")]
|
||||
period_end: DateTime<Utc>,
|
||||
#[serde(rename = "cancelAt")]
|
||||
cancel_at: Option<DateTime<Utc>>,
|
||||
},
|
||||
Inactive {
|
||||
status: String,
|
||||
},
|
||||
Expired {
|
||||
changes: i32,
|
||||
#[serde(rename = "changesUrl")]
|
||||
changes_url: Option<String>,
|
||||
#[serde(rename = "billingUrl")]
|
||||
billing_url: String,
|
||||
#[serde(rename = "periodEnd")]
|
||||
period_end: DateTime<Utc>,
|
||||
},
|
||||
PastDue {
|
||||
#[serde(rename = "billingUrl")]
|
||||
billing_url: String,
|
||||
#[serde(rename = "periodEnd")]
|
||||
period_end: DateTime<Utc>,
|
||||
},
|
||||
}
|
||||
|
||||
pub async fn activate_license<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
license_key: &str,
|
||||
@@ -141,16 +177,6 @@ pub async fn deactivate_license<R: Runtime>(window: &WebviewWindow<R>) -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export, export_to = "license.ts")]
|
||||
pub enum LicenseCheckStatus {
|
||||
PersonalUse { trial_ended: NaiveDateTime },
|
||||
CommercialUse,
|
||||
InvalidLicense,
|
||||
Trialing { end: NaiveDateTime },
|
||||
}
|
||||
|
||||
pub async fn check_license<R: Runtime>(window: &WebviewWindow<R>) -> Result<LicenseCheckStatus> {
|
||||
let payload = CheckActivationRequestPayload {
|
||||
app_platform: get_os_str().to_string(),
|
||||
@@ -159,10 +185,10 @@ pub async fn check_license<R: Runtime>(window: &WebviewWindow<R>) -> Result<Lice
|
||||
let activation_id = get_activation_id(window.app_handle()).await;
|
||||
|
||||
let settings = window.db().get_settings();
|
||||
let trial_end = settings.created_at.add(Duration::from_secs(TRIAL_SECONDS));
|
||||
let trial_end = settings.created_at.add(Duration::from_secs(TRIAL_SECONDS)).and_utc();
|
||||
|
||||
let has_activation_id = !activation_id.is_empty();
|
||||
let trial_period_active = Utc::now().naive_utc() < trial_end;
|
||||
let trial_period_active = Utc::now() < trial_end;
|
||||
|
||||
match (has_activation_id, trial_period_active) {
|
||||
(false, true) => Ok(LicenseCheckStatus::Trialing { end: trial_end }),
|
||||
@@ -173,7 +199,7 @@ pub async fn check_license<R: Runtime>(window: &WebviewWindow<R>) -> Result<Lice
|
||||
info!("Checking license activation");
|
||||
// A license has been activated, so let's check the license server
|
||||
let client = yaak_api_client(window.app_handle())?;
|
||||
let path = format!("/licenses/activations/{activation_id}/check");
|
||||
let path = format!("/licenses/activations/{activation_id}/check-v2");
|
||||
let response = client.post(build_url(&path)).json(&payload).send().await?;
|
||||
|
||||
if response.status().is_client_error() {
|
||||
@@ -189,13 +215,14 @@ pub async fn check_license<R: Runtime>(window: &WebviewWindow<R>) -> Result<Lice
|
||||
return Err(ServerError);
|
||||
}
|
||||
|
||||
let body: CheckActivationResponsePayload = response.json().await?;
|
||||
if !body.active {
|
||||
info!("Inactive License {:?}", body);
|
||||
return Ok(LicenseCheckStatus::InvalidLicense);
|
||||
let body_text = response.text().await?;
|
||||
match serde_json::from_str::<LicenseCheckStatus>(&body_text) {
|
||||
Ok(b) => Ok(b),
|
||||
Err(e) => {
|
||||
warn!("Failed to decode server response: {} {:?}", body_text, e);
|
||||
Err(JsonError(e))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(LicenseCheckStatus::CommercialUse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,32 @@ mod commands;
|
||||
mod mac;
|
||||
|
||||
use crate::commands::{set_theme, set_title};
|
||||
use tauri::{
|
||||
Runtime, generate_handler,
|
||||
plugin::{Builder, TauriPlugin},
|
||||
};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use tauri::{generate_handler, plugin, plugin::TauriPlugin, Manager, Runtime};
|
||||
|
||||
pub trait AppHandleMacWindowExt {
|
||||
/// Sets whether to use the native titlebar
|
||||
fn set_native_titlebar(&self, enable: bool);
|
||||
}
|
||||
|
||||
impl<R: Runtime> AppHandleMacWindowExt for tauri::AppHandle<R> {
|
||||
fn set_native_titlebar(&self, enable: bool) {
|
||||
self.state::<PluginState>().native_titlebar.store(enable, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PluginState {
|
||||
native_titlebar: AtomicBool,
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
#[allow(unused)]
|
||||
Builder::new("yaak-mac-window")
|
||||
plugin::Builder::new("yaak-mac-window")
|
||||
.setup(move |app, _| {
|
||||
app.manage(PluginState { native_titlebar: AtomicBool::new(false) });
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(generate_handler![set_title, set_theme])
|
||||
.on_window_ready(|window| {
|
||||
.on_window_ready(move |window| {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
mac::setup_traffic_light_positioner(&window);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#![allow(deprecated)]
|
||||
use crate::PluginState;
|
||||
use csscolorparser::Color;
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use tauri::{Emitter, Runtime, Window};
|
||||
use tauri::{Emitter, Manager, Runtime, State, Window};
|
||||
|
||||
struct UnsafeWindowHandle(*mut std::ffi::c_void);
|
||||
|
||||
@@ -16,6 +17,8 @@ const MAIN_WINDOW_PREFIX: &str = "main_";
|
||||
pub(crate) fn update_window_title<R: Runtime>(window: Window<R>, title: String) {
|
||||
use cocoa::{appkit::NSWindow, base::nil, foundation::NSString};
|
||||
|
||||
let state: State<PluginState> = window.state();
|
||||
let native_titlebar = state.native_titlebar.load(std::sync::atomic::Ordering::Relaxed);
|
||||
unsafe {
|
||||
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
|
||||
|
||||
@@ -25,12 +28,16 @@ pub(crate) fn update_window_title<R: Runtime>(window: Window<R>, title: String)
|
||||
let win_title = NSString::alloc(nil).init_str(&title);
|
||||
let handle = window_handle;
|
||||
NSWindow::setTitle_(handle.0 as cocoa::base::id, win_title);
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(window2.ns_window().expect("Failed to create window handle")),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
label,
|
||||
);
|
||||
if !native_titlebar {
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(
|
||||
window2.ns_window().expect("Failed to create window handle"),
|
||||
),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
label,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -42,6 +49,8 @@ pub(crate) fn update_window_theme<R: Runtime>(window: Window<R>, color: Color) {
|
||||
|
||||
let brightness = (color.r as f64 + color.g as f64 + color.b as f64) / 3.0;
|
||||
let label = window.label().to_string();
|
||||
let state: State<PluginState> = window.state();
|
||||
let native_titlebar = state.native_titlebar.load(std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
unsafe {
|
||||
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
|
||||
@@ -56,12 +65,16 @@ pub(crate) fn update_window_theme<R: Runtime>(window: Window<R>, color: Color) {
|
||||
};
|
||||
|
||||
NSWindow::setAppearance(handle.0 as cocoa::base::id, selected_appearance);
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(window2.ns_window().expect("Failed to create window handle")),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
label,
|
||||
);
|
||||
if !native_titlebar {
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(
|
||||
window2.ns_window().expect("Failed to create window handle"),
|
||||
),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
label,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -119,6 +132,11 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
|
||||
use rand::distr::Alphanumeric;
|
||||
use std::ffi::c_void;
|
||||
|
||||
let state: State<PluginState> = window.state();
|
||||
if state.native_titlebar.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
|
||||
@@ -62,7 +62,7 @@ export type ProxySetting = { "type": "enabled", http: string, https: string, aut
|
||||
|
||||
export type ProxySettingAuth = { user: string, password: string, };
|
||||
|
||||
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, hideLicenseBadge: boolean, autoupdate: boolean, autoDownloadUpdates: boolean, checkNotifications: boolean, };
|
||||
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, useNativeTitlebar: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, hideLicenseBadge: boolean, autoupdate: boolean, autoDownloadUpdates: boolean, checkNotifications: boolean, };
|
||||
|
||||
export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, };
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add a setting to force native window title bar / controls
|
||||
ALTER TABLE settings
|
||||
ADD COLUMN use_native_titlebar BOOLEAN DEFAULT FALSE NOT NULL;
|
||||
@@ -112,6 +112,8 @@ pub struct Settings {
|
||||
pub editor_keymap: EditorKeymap,
|
||||
pub editor_soft_wrap: bool,
|
||||
pub hide_window_controls: bool,
|
||||
// When true (primarily on Windows/Linux), use the native OS window title bar and controls
|
||||
pub use_native_titlebar: bool,
|
||||
pub interface_font: Option<String>,
|
||||
pub interface_font_size: i32,
|
||||
pub interface_scale: f32,
|
||||
@@ -168,6 +170,7 @@ impl UpsertModelInfo for Settings {
|
||||
(InterfaceFontSize, self.interface_font_size.into()),
|
||||
(InterfaceScale, self.interface_scale.into()),
|
||||
(HideWindowControls, self.hide_window_controls.into()),
|
||||
(UseNativeTitlebar, self.use_native_titlebar.into()),
|
||||
(OpenWorkspaceNewWindow, self.open_workspace_new_window.into()),
|
||||
(ThemeDark, self.theme_dark.as_str().into()),
|
||||
(ThemeLight, self.theme_light.as_str().into()),
|
||||
@@ -193,6 +196,7 @@ impl UpsertModelInfo for Settings {
|
||||
SettingsIden::InterfaceScale,
|
||||
SettingsIden::InterfaceFont,
|
||||
SettingsIden::HideWindowControls,
|
||||
SettingsIden::UseNativeTitlebar,
|
||||
SettingsIden::OpenWorkspaceNewWindow,
|
||||
SettingsIden::Proxy,
|
||||
SettingsIden::ThemeDark,
|
||||
@@ -225,6 +229,7 @@ impl UpsertModelInfo for Settings {
|
||||
interface_font_size: row.get("interface_font_size")?,
|
||||
interface_scale: row.get("interface_scale")?,
|
||||
interface_font: row.get("interface_font")?,
|
||||
use_native_titlebar: row.get("use_native_titlebar")?,
|
||||
open_workspace_new_window: row.get("open_workspace_new_window")?,
|
||||
proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }),
|
||||
theme_dark: row.get("theme_dark")?,
|
||||
|
||||
@@ -26,6 +26,7 @@ impl<'a> DbContext<'a> {
|
||||
interface_scale: 1.0,
|
||||
interface_font: None,
|
||||
hide_window_controls: false,
|
||||
use_native_titlebar: false,
|
||||
open_workspace_new_window: None,
|
||||
proxy: None,
|
||||
theme_dark: "yaak-dark".to_string(),
|
||||
|
||||
@@ -622,6 +622,14 @@ impl PluginManager {
|
||||
values: HashMap<String, JsonPrimitive>,
|
||||
model_id: &str,
|
||||
) -> Result<GetHttpAuthenticationConfigResponse> {
|
||||
if auth_name == "none" {
|
||||
return Ok(GetHttpAuthenticationConfigResponse {
|
||||
args: Vec::new(),
|
||||
plugin_ref_id: "auth-none".to_string(),
|
||||
actions: None,
|
||||
});
|
||||
}
|
||||
|
||||
let results = self.get_http_authentication_summaries(window).await?;
|
||||
let plugin = results
|
||||
.iter()
|
||||
|
||||
@@ -27,6 +27,7 @@ export function HeaderSize({
|
||||
}: HeaderSizeProps) {
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
const isFullscreen = useIsFullscreen();
|
||||
const nativeTitlebar = settings.useNativeTitlebar;
|
||||
const finalStyle = useMemo<CSSProperties>(() => {
|
||||
const s = { ...style };
|
||||
|
||||
@@ -34,7 +35,9 @@ export function HeaderSize({
|
||||
if (size === 'md') s.minHeight = HEADER_SIZE_MD;
|
||||
if (size === 'lg') s.minHeight = HEADER_SIZE_LG;
|
||||
|
||||
if (type() === 'macos') {
|
||||
if (nativeTitlebar) {
|
||||
// No style updates when using native titlebar
|
||||
} else if (type() === 'macos') {
|
||||
if (!isFullscreen) {
|
||||
// Add large padding for window controls
|
||||
s.paddingLeft = 72 / settings.interfaceScale;
|
||||
@@ -51,6 +54,7 @@ export function HeaderSize({
|
||||
settings.interfaceScale,
|
||||
size,
|
||||
style,
|
||||
nativeTitlebar,
|
||||
]);
|
||||
|
||||
return (
|
||||
@@ -73,7 +77,7 @@ export function HeaderSize({
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{!hideControls && <WindowControls onlyX={onlyXWindowControl} />}
|
||||
{!hideControls && !nativeTitlebar && <WindowControls onlyX={onlyXWindowControl} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,86 @@
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import type { LicenseCheckStatus } from '@yaakapp-internal/license';
|
||||
import { useLicense } from '@yaakapp-internal/license';
|
||||
import { settingsAtom } from '@yaakapp-internal/models';
|
||||
import { differenceInCalendarDays } from 'date-fns';
|
||||
import { formatDate } from 'date-fns/format';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { ReactNode } from 'react';
|
||||
import { openSettings } from '../commands/openSettings';
|
||||
import { atomWithKVStorage } from '../lib/atoms/atomWithKVStorage';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { CargoFeature } from './CargoFeature';
|
||||
import type { ButtonProps } from './core/Button';
|
||||
import { Dropdown, type DropdownItem } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { PillButton } from './core/PillButton';
|
||||
|
||||
const details: Record<
|
||||
LicenseCheckStatus['type'],
|
||||
{ label: ReactNode; color: ButtonProps['color'] } | null
|
||||
> = {
|
||||
commercial_use: null,
|
||||
invalid_license: { label: 'License Error', color: 'danger' },
|
||||
personal_use: { label: 'Personal Use', color: 'notice' },
|
||||
trialing: { label: 'Commercial Trial', color: 'secondary' },
|
||||
};
|
||||
const dismissedAtom = atomWithKVStorage<string | null>('dismissed_license_expired', null);
|
||||
|
||||
function getDetail(
|
||||
data: LicenseCheckStatus,
|
||||
dismissedExpired: string | null,
|
||||
): { label: ReactNode; color: ButtonProps['color']; options?: DropdownItem[] } | null | undefined {
|
||||
const dismissedAt = dismissedExpired ? new Date(dismissedExpired).getTime() : null;
|
||||
|
||||
switch (data.status) {
|
||||
case 'active':
|
||||
return null;
|
||||
case 'personal_use':
|
||||
return { label: 'Personal Use', color: 'notice' };
|
||||
case 'trialing':
|
||||
return { label: 'Commercial Trial', color: 'secondary' };
|
||||
case 'error':
|
||||
return { label: 'Error', color: 'danger' };
|
||||
case 'inactive':
|
||||
return { label: 'Personal Use', color: 'notice' };
|
||||
case 'past_due':
|
||||
return { label: 'Past Due', color: 'danger' };
|
||||
case 'expired':
|
||||
// Don't show the expired message if it's been less than 14 days since the last dismissal
|
||||
if (dismissedAt && differenceInCalendarDays(new Date(), dismissedAt) < 14) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
color: 'notice',
|
||||
label: data.data.changes > 0 ? 'Updates Paused' : 'License Expired',
|
||||
options: [
|
||||
{
|
||||
label: `${data.data.changes} New Updates`,
|
||||
color: 'success',
|
||||
leftSlot: <Icon icon="gift" />,
|
||||
rightSlot: <Icon icon="external_link" size="sm" className="opacity-disabled" />,
|
||||
hidden: data.data.changes === 0 || data.data.changesUrl == null,
|
||||
onSelect: () => openUrl(data.data.changesUrl ?? ''),
|
||||
},
|
||||
{
|
||||
type: 'separator',
|
||||
label: `License expired ${formatDate(data.data.periodEnd, 'MMM dd, yyyy')}`,
|
||||
},
|
||||
{
|
||||
label: <div className="min-w-[12rem]">Renew License</div>,
|
||||
leftSlot: <Icon icon="refresh" />,
|
||||
rightSlot: <Icon icon="external_link" size="sm" className="opacity-disabled" />,
|
||||
hidden: data.data.changesUrl == null,
|
||||
onSelect: () => openUrl(data.data.billingUrl),
|
||||
},
|
||||
{
|
||||
label: 'Enter License Key',
|
||||
leftSlot: <Icon icon="key_round" />,
|
||||
hidden: data.data.changesUrl == null,
|
||||
onSelect: openLicenseDialog,
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: <span className="text-text-subtle">Remind me Later</span>,
|
||||
leftSlot: <Icon icon="alarm_clock" className="text-text-subtle" />,
|
||||
onSelect: () => jotaiStore.set(dismissedAtom, new Date().toISOString()),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function LicenseBadge() {
|
||||
return (
|
||||
@@ -29,10 +93,15 @@ export function LicenseBadge() {
|
||||
function LicenseBadgeCmp() {
|
||||
const { check } = useLicense();
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
const dismissed = useAtomValue(dismissedAtom);
|
||||
|
||||
// Dismissed license badge
|
||||
if (settings.hideLicenseBadge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (check.error) {
|
||||
// Failed to check for license. Probably a network or server error so just don't
|
||||
// show anything.
|
||||
// Failed to check for license. Probably a network or server error, so just don't show anything.
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -41,19 +110,30 @@ function LicenseBadgeCmp() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Dismissed license badge
|
||||
if (settings.hideLicenseBadge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const detail = details[check.data.type];
|
||||
const detail = getDetail(check.data, dismissed);
|
||||
if (detail == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (detail.options && detail.options.length > 0) {
|
||||
return (
|
||||
<Dropdown items={detail.options}>
|
||||
<PillButton color={detail.color}>
|
||||
<div className="flex items-center gap-0.5">
|
||||
{detail.label} <Icon icon="chevron_down" className="opacity-60" />
|
||||
</div>
|
||||
</PillButton>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<PillButton color={detail.color} onClick={() => openSettings.mutate('license')}>
|
||||
<PillButton color={detail.color} onClick={openLicenseDialog}>
|
||||
{detail.label}
|
||||
</PillButton>
|
||||
);
|
||||
}
|
||||
|
||||
function openLicenseDialog() {
|
||||
openSettings.mutate('license');
|
||||
}
|
||||
|
||||
@@ -4,11 +4,14 @@ import { useLicense } from '@yaakapp-internal/license';
|
||||
import type { EditorKeymap, Settings } from '@yaakapp-internal/models';
|
||||
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { clamp } from '../../lib/clamp';
|
||||
import { showConfirm } from '../../lib/confirm';
|
||||
import { invokeCmd } from '../../lib/tauri';
|
||||
import { CargoFeature } from '../CargoFeature';
|
||||
import { Button } from '../core/Button';
|
||||
import { Checkbox } from '../core/Checkbox';
|
||||
import { Icon } from '../core/Icon';
|
||||
import { Link } from '../core/Link';
|
||||
@@ -154,6 +157,8 @@ export function SettingsInterface() {
|
||||
<LicenseSettings settings={settings} />
|
||||
</CargoFeature>
|
||||
|
||||
<NativeTitlebarSetting settings={settings} />
|
||||
|
||||
{type() !== 'macos' && (
|
||||
<Checkbox
|
||||
checked={settings.hideWindowControls}
|
||||
@@ -165,9 +170,36 @@ export function SettingsInterface() {
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
function NativeTitlebarSetting({ settings }: { settings: Settings }) {
|
||||
const [nativeTitlebar, setNativeTitlebar] = useState(settings.useNativeTitlebar);
|
||||
return (
|
||||
<div className="flex gap-1 overflow-hidden h-2xs">
|
||||
<Checkbox
|
||||
checked={nativeTitlebar}
|
||||
title="Native title bar"
|
||||
help="Use the operating system's standard title bar and window controls"
|
||||
onChange={setNativeTitlebar}
|
||||
/>
|
||||
{settings.useNativeTitlebar !== nativeTitlebar && (
|
||||
<Button
|
||||
color="primary"
|
||||
size="2xs"
|
||||
onClick={async () => {
|
||||
await patchModel(settings, { useNativeTitlebar: nativeTitlebar });
|
||||
await invokeCmd('cmd_restart');
|
||||
}}
|
||||
>
|
||||
Apply and Restart
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LicenseSettings({ settings }: { settings: Settings }) {
|
||||
const license = useLicense();
|
||||
if (license.check.data?.type !== 'personal_use') {
|
||||
if (license.check.data?.status !== 'personal_use') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||
import { useLicense } from '@yaakapp-internal/license';
|
||||
import { differenceInDays } from 'date-fns';
|
||||
import { formatDate } from 'date-fns/format';
|
||||
import { useState } from 'react';
|
||||
import { useToggle } from '../../hooks/useToggle';
|
||||
import { pluralizeCount } from '../../lib/pluralize';
|
||||
@@ -31,71 +32,120 @@ function SettingsLicenseCmp() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderBanner = () => {
|
||||
if (!check.data) return null;
|
||||
|
||||
switch (check.data.status) {
|
||||
case 'active':
|
||||
return <Banner color="success">Your license is active 🥳</Banner>;
|
||||
|
||||
case 'trialing':
|
||||
return (
|
||||
<Banner color="info" className="@container flex items-center gap-x-5 max-w-xl">
|
||||
<LocalImage src="static/greg.jpeg" className="hidden @sm:block rounded-full h-14 w-14" />
|
||||
<p className="w-full">
|
||||
<strong>
|
||||
{pluralizeCount('day', differenceInDays(check.data.data.end, new Date()))}
|
||||
</strong>{' '}
|
||||
left to evaluate Yaak for commercial use.
|
||||
<br />
|
||||
<span className="opacity-50">Personal use is always free, forever.</span>
|
||||
<Separator className="my-2" />
|
||||
<div className="flex flex-wrap items-center gap-x-2 text-sm text-notice">
|
||||
<Link noUnderline href="mailto:support@yaak.app">
|
||||
Contact Support
|
||||
</Link>
|
||||
<Icon icon="dot" size="sm" color="secondary" />
|
||||
<Link
|
||||
noUnderline
|
||||
href={`https://yaak.app/pricing?s=learn&t=${check.data.status}`}
|
||||
>
|
||||
Learn More
|
||||
</Link>
|
||||
</div>
|
||||
</p>
|
||||
</Banner>
|
||||
);
|
||||
|
||||
case 'personal_use':
|
||||
return (
|
||||
<Banner color="notice" className="@container flex items-center gap-x-5 max-w-xl">
|
||||
<LocalImage src="static/greg.jpeg" className="hidden @sm:block rounded-full h-14 w-14" />
|
||||
<p className="w-full">
|
||||
Your commercial-use trial has ended.
|
||||
<br />
|
||||
<span className="opacity-50">
|
||||
You may continue using Yaak for personal use free, forever.
|
||||
<br />A license is required for commercial use.
|
||||
</span>
|
||||
<Separator className="my-2" />
|
||||
<div className="flex flex-wrap items-center gap-x-2 text-sm text-notice">
|
||||
<Link noUnderline href="mailto:support@yaak.app">
|
||||
Contact Support
|
||||
</Link>
|
||||
<Icon icon="dot" size="sm" color="secondary" />
|
||||
<Link
|
||||
noUnderline
|
||||
href={`https://yaak.app/pricing?s=learn&t=${check.data.status}`}
|
||||
>
|
||||
Learn More
|
||||
</Link>
|
||||
</div>
|
||||
</p>
|
||||
</Banner>
|
||||
);
|
||||
|
||||
case 'inactive':
|
||||
return (
|
||||
<Banner color="danger">
|
||||
Your license is invalid. Please <Link href="https://yaak.app/dashboard">Sign In</Link>{' '}
|
||||
for more details
|
||||
</Banner>
|
||||
);
|
||||
|
||||
case 'expired':
|
||||
return (
|
||||
<Banner color="notice">
|
||||
Your license expired{' '}
|
||||
<strong>{formatDate(check.data.data.periodEnd, 'MMMM dd, yyyy')}</strong>. Please{' '}
|
||||
<Link href="https://yaak.app/dashboard">Resubscribe</Link> to continue receiving
|
||||
updates.
|
||||
{check.data.data.changesUrl && (
|
||||
<>
|
||||
<br />
|
||||
<Link href={check.data.data.changesUrl}>What's new in latest builds</Link>
|
||||
</>
|
||||
)}
|
||||
</Banner>
|
||||
);
|
||||
|
||||
case 'past_due':
|
||||
return (
|
||||
<Banner color="danger">
|
||||
<strong>Your payment method needs attention.</strong>
|
||||
<br />
|
||||
To re-activate your license, please{' '}
|
||||
<Link href={check.data.data.billingUrl}>update your billing info</Link>.
|
||||
</Banner>
|
||||
);
|
||||
|
||||
case 'error':
|
||||
return (
|
||||
<Banner color="danger">
|
||||
License check failed: {check.data.data.message} (Code: {check.data.data.code})
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 max-w-xl">
|
||||
{check.data?.type === 'commercial_use' ? (
|
||||
<Banner color="success">Your license is active 🥳</Banner>
|
||||
) : check.data?.type === 'trialing' ? (
|
||||
<Banner color="info" className="@container flex items-center gap-x-5 max-w-xl">
|
||||
<LocalImage src="static/greg.jpeg" className="hidden @sm:block rounded-full h-14 w-14" />
|
||||
<p className="w-full">
|
||||
<strong>{pluralizeCount('day', differenceInDays(check.data.end, new Date()))}</strong>{' '}
|
||||
left to evaluate Yaak for commercial use.
|
||||
<br />
|
||||
<span className="opacity-50">Personal use is always free, forever.</span>
|
||||
<Separator className="my-2" />
|
||||
<div className="flex flex-wrap items-center gap-x-2 text-sm text-notice">
|
||||
<Link noUnderline href="mailto:support@yaak.app">
|
||||
Contact Support
|
||||
</Link>
|
||||
<Icon icon="dot" size="sm" color="secondary" />
|
||||
<Link
|
||||
noUnderline
|
||||
href={`https://yaak.app/pricing?s=learn&t=${check.data?.type ?? ''}`}
|
||||
>
|
||||
Learn More
|
||||
</Link>
|
||||
</div>
|
||||
</p>
|
||||
</Banner>
|
||||
) : check.data?.type === 'personal_use' ? (
|
||||
<Banner color="notice" className="@container flex items-center gap-x-5 max-w-xl">
|
||||
<LocalImage src="static/greg.jpeg" className="hidden @sm:block rounded-full h-14 w-14" />
|
||||
<p className="w-full">
|
||||
Your commercial-use trial has ended.
|
||||
<br />
|
||||
<span className="opacity-50">
|
||||
You may continue using Yaak for personal use free, forever.
|
||||
<br />A license is required for commercial use.
|
||||
</span>
|
||||
<Separator className="my-2" />
|
||||
<div className="flex flex-wrap items-center gap-x-2 text-sm text-notice">
|
||||
<Link noUnderline href="mailto:support@yaak.app">
|
||||
Contact Support
|
||||
</Link>
|
||||
<Icon icon="dot" size="sm" color="secondary" />
|
||||
<Link
|
||||
noUnderline
|
||||
href={`https://yaak.app/pricing?s=learn&t=${check.data?.type ?? ''}`}
|
||||
>
|
||||
Learn More
|
||||
</Link>
|
||||
</div>
|
||||
</p>
|
||||
</Banner>
|
||||
) : null}
|
||||
{renderBanner()}
|
||||
|
||||
{check.error && <Banner color="danger">{check.error}</Banner>}
|
||||
{activate.error && <Banner color="danger">{activate.error}</Banner>}
|
||||
|
||||
{check.data?.type === 'invalid_license' && (
|
||||
<Banner color="danger">
|
||||
Your license is invalid. Please <Link href="https://yaak.app/dashboard">Sign In</Link> for
|
||||
more details
|
||||
</Banner>
|
||||
)}
|
||||
|
||||
{check.data?.type === 'commercial_use' ? (
|
||||
{check.data?.status === 'active' ? (
|
||||
<HStack space={2}>
|
||||
<Button variant="border" color="secondary" size="sm" onClick={() => deactivate.mutate()}>
|
||||
Deactivate License
|
||||
@@ -120,7 +170,7 @@ function SettingsLicenseCmp() {
|
||||
rightSlot={<Icon icon="external_link" />}
|
||||
onClick={() =>
|
||||
openUrl(
|
||||
`https://yaak.app/pricing?s=purchase&ref=app.yaak.desktop&t=${check.data?.type ?? ''}`,
|
||||
`https://yaak.app/pricing?s=purchase&ref=app.yaak.desktop&t=${check.data?.status ?? ''}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
@@ -67,7 +67,7 @@ export function SettingsDropdown() {
|
||||
{
|
||||
label: 'Purchase License',
|
||||
color: 'success',
|
||||
hidden: check.data == null || check.data.type === 'commercial_use',
|
||||
hidden: check.data == null || check.data.status === 'active',
|
||||
leftSlot: <Icon icon="circle_dollar_sign" />,
|
||||
onSelect: () => openSettings.mutate('license'),
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ export function WindowControls({ className, onlyX }: Props) {
|
||||
const [maximized, setMaximized] = useState<boolean>(false);
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
// Never show controls on macOS or if hideWindowControls is true
|
||||
if (type() === 'macos' || settings.hideWindowControls) {
|
||||
if (type() === 'macos' || settings.hideWindowControls || settings.useNativeTitlebar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,10 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
||||
const workspace = useAtomValue(activeWorkspaceAtom);
|
||||
const workspaceMeta = useAtomValue(activeWorkspaceMetaAtom);
|
||||
const showEncryptionSetup =
|
||||
workspace?.encryptionKeyChallenge != null && workspaceMeta?.encryptionKey == null;
|
||||
workspace != null &&
|
||||
workspaceMeta != null &&
|
||||
workspace.encryptionKeyChallenge != null &&
|
||||
workspaceMeta.encryptionKey == null;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Color } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
AlarmClockIcon,
|
||||
AlertTriangleIcon,
|
||||
ArchiveIcon,
|
||||
ArrowBigDownDashIcon,
|
||||
@@ -67,6 +68,7 @@ import {
|
||||
FolderSymlinkIcon,
|
||||
FolderSyncIcon,
|
||||
FolderUpIcon,
|
||||
GiftIcon,
|
||||
GitBranchIcon,
|
||||
GitBranchPlusIcon,
|
||||
GitCommitIcon,
|
||||
@@ -129,6 +131,7 @@ import type { CSSProperties, HTMLAttributes } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
const icons = {
|
||||
alarm_clock: AlarmClockIcon,
|
||||
alert_triangle: AlertTriangleIcon,
|
||||
archive: ArchiveIcon,
|
||||
arrow_big_down_dash: ArrowBigDownDashIcon,
|
||||
@@ -194,6 +197,7 @@ const icons = {
|
||||
folder_symlink: FolderSymlinkIcon,
|
||||
folder_sync: FolderSyncIcon,
|
||||
folder_up: FolderUpIcon,
|
||||
gift: GiftIcon,
|
||||
git_branch: GitBranchIcon,
|
||||
git_branch_plus: GitBranchPlusIcon,
|
||||
git_commit: GitCommitIcon,
|
||||
|
||||
@@ -47,10 +47,14 @@ export function useGrpc(
|
||||
const reflect = useQuery<ReflectResponseService[], string>({
|
||||
enabled: req != null,
|
||||
queryKey: ['grpc_reflect', req?.id ?? 'n/a', debouncedUrl, protoFiles],
|
||||
staleTime: Infinity,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
queryFn: () => {
|
||||
const environmentId = jotaiStore.get(activeEnvironmentIdAtom);
|
||||
return minPromiseMillis<ReflectResponseService[]>(
|
||||
invokeCmd('cmd_grpc_reflect', { requestId, protoFiles, environmentId }),
|
||||
invokeCmd('cmd_grpc_reflect', { requestId, protoFiles, environmentId, skipCache: true }),
|
||||
300,
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { setWindowTitle } from '@yaakapp-internal/mac-window';
|
||||
import { settingsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useEffect } from 'react';
|
||||
import { appInfo } from '../lib/appInfo';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { useActiveEnvironment } from './useActiveEnvironment';
|
||||
import { activeRequestAtom } from './useActiveRequest';
|
||||
@@ -13,11 +15,13 @@ export function useSyncWorkspaceRequestTitle() {
|
||||
const activeRequest = useAtomValue(activeRequestAtom);
|
||||
|
||||
useEffect(() => {
|
||||
const settings = jotaiStore.get(settingsAtom);
|
||||
let newTitle = activeWorkspace ? activeWorkspace.name : 'Yaak';
|
||||
if (activeEnvironment) {
|
||||
newTitle += ` [${activeEnvironment.name}]`;
|
||||
newTitle += ` (${activeEnvironment.name})`;
|
||||
}
|
||||
if (activeRequest) {
|
||||
|
||||
if (!settings.useNativeTitlebar && activeRequest) {
|
||||
newTitle += ` › ${resolvedModelName(activeRequest)}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type {
|
||||
Folder,
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
WebsocketRequest,
|
||||
Workspace,
|
||||
import {
|
||||
environmentsAtom,
|
||||
type Folder,
|
||||
type GrpcRequest,
|
||||
type HttpRequest,
|
||||
httpResponsesAtom,
|
||||
pluginsAtom,
|
||||
type WebsocketRequest,
|
||||
type Workspace,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { environmentsAtom, httpResponsesAtom } from '@yaakapp-internal/models';
|
||||
import type { GetTemplateFunctionConfigResponse, JsonPrimitive } from '@yaakapp-internal/plugins';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { md5 } from 'js-md5';
|
||||
@@ -19,6 +21,7 @@ export function useTemplateFunctionConfig(
|
||||
values: Record<string, JsonPrimitive>,
|
||||
model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace,
|
||||
) {
|
||||
const pluginsKey = useAtomValue(pluginsAtom);
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||
const environmentId = useAtomValue(activeEnvironmentIdAtom);
|
||||
const responses = useAtomValue(httpResponsesAtom);
|
||||
@@ -40,10 +43,11 @@ export function useTemplateFunctionConfig(
|
||||
model,
|
||||
functionName,
|
||||
values,
|
||||
responseKey,
|
||||
workspaceId,
|
||||
environmentId,
|
||||
environmentsKey,
|
||||
workspaceId, // Refresh when the active workspace changes
|
||||
environmentId, // Refresh when the active environment changes
|
||||
environmentsKey, // Refresh when environments change
|
||||
responseKey, // Refresh when responses change
|
||||
pluginsKey, // Refresh when plugins reload
|
||||
],
|
||||
placeholderData: (prev) => prev, // Keep previous data on refetch
|
||||
queryFn: async () => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import './main.css';
|
||||
import { RouterProvider } from '@tanstack/react-router';
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
import { changeModelStoreWorkspace, initModelStore } from '@yaakapp-internal/models';
|
||||
import { StrictMode } from 'react';
|
||||
@@ -10,12 +9,7 @@ import { initGlobalListeners } from './lib/initGlobalListeners';
|
||||
import { jotaiStore } from './lib/jotai';
|
||||
import { router } from './lib/router';
|
||||
|
||||
// Hide decorations here because it doesn't work in Rust for some reason (bug?)
|
||||
const osType = type();
|
||||
if (osType !== 'macos') {
|
||||
await getCurrentWebviewWindow().setDecorations(false);
|
||||
}
|
||||
|
||||
document.documentElement.setAttribute('data-platform', osType);
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
|
||||
@@ -27,14 +27,14 @@
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@tanstack/react-router": "^1.133.13",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@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",
|
||||
"@tauri-apps/plugin-log": "^2.7.0",
|
||||
"@tauri-apps/plugin-opener": "^2.5.0",
|
||||
"@tauri-apps/plugin-os": "^2.3.1",
|
||||
"@tauri-apps/plugin-shell": "^2.3.1",
|
||||
"@tauri-apps/api": "^2.9.1",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||
"@tauri-apps/plugin-dialog": "^2.4.2",
|
||||
"@tauri-apps/plugin-fs": "^2.4.4",
|
||||
"@tauri-apps/plugin-log": "^2.7.1",
|
||||
"@tauri-apps/plugin-opener": "^2.5.2",
|
||||
"@tauri-apps/plugin-os": "^2.3.2",
|
||||
"@tauri-apps/plugin-shell": "^2.3.3",
|
||||
"buffer": "^6.0.3",
|
||||
"classnames": "^2.5.1",
|
||||
"cm6-graphql": "^0.2.1",
|
||||
|
||||
Reference in New Issue
Block a user