Merge remote-tracking branch 'origin/v2' into plugin/secure-storage

This commit is contained in:
FabianLars committed 2025-10-14 17:52:34 +02:00
commit 1771c6ed36
143 files changed
+2632 -1262

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
---
"deep-link": patch
"deep-link-js": patch
---
Fix Exec= field in desktop handler if executable path changes
+6
View File
@@ -0,0 +1,6 @@
---
"fs": patch
"fs-js": patch
---
Enhance error messages.
+6
View File
@@ -0,0 +1,6 @@
---
geolocation: patch
geolocation-js: patch
---
On Android, use the `timeout` value for `setMinUpdateIntervalMillis`, `setMaxUpdateDelayMillis` and `setIntervalMillis` instead of just `minUpdateInterval`.
-6
View File
@@ -1,6 +0,0 @@
---
nfc: patch
nfc-js: patch
---
On iOS, the reader session will now get closed properly on errors, preventing dangling invalid sessions that could prevent subsequent write attempts.
+6
View File
@@ -0,0 +1,6 @@
---
"opener": patch
"opener-js": patch
---
Fix opener doesn't open same origin links in the browser
-6
View File
@@ -1,6 +0,0 @@
---
store: minor
store-js: minor
---
Allow setting defaults from the JavaScript API
-6
View File
@@ -1,6 +0,0 @@
---
store: minor
store-js: minor
---
Add an new option `overrideDefaults` for creating/loading and reloading the store that overrides the store with the on-disk state, ignoring defaults
+6
View File
@@ -0,0 +1,6 @@
---
"updater": minor
"updater-js": minor
---
Updater plugin now supports all bundle types: Deb, Rpm and AppImage for Linux; NSiS, MSI for Windows.
+1 -1
View File
@@ -58,4 +58,4 @@ pids
.idea .idea
debug.log debug.log
TODO.md TODO.md
.aider* .aider.*
Generated
+137 -69
View File
@@ -220,7 +220,7 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]] [[package]]
name = "api" name = "api"
version = "2.0.32" version = "2.0.36"
dependencies = [ dependencies = [
"log", "log",
"serde", "serde",
@@ -903,7 +903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257"
dependencies = [ dependencies = [
"serde", "serde",
"toml", "toml 0.8.20",
] ]
[[package]] [[package]]
@@ -1608,9 +1608,9 @@ dependencies = [
[[package]] [[package]]
name = "dlopen2" name = "dlopen2"
version = "0.7.0" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff"
dependencies = [ dependencies = [
"dlopen2_derive", "dlopen2_derive",
"libc", "libc",
@@ -1770,7 +1770,7 @@ dependencies = [
"cc", "cc",
"memchr", "memchr",
"rustc_version", "rustc_version",
"toml", "toml 0.8.20",
"vswhom", "vswhom",
"winreg 0.52.0", "winreg 0.52.0",
] ]
@@ -3648,9 +3648,9 @@ dependencies = [
[[package]] [[package]]
name = "muda" name = "muda"
version = "0.16.1" version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492" checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dpi", "dpi",
@@ -3664,7 +3664,7 @@ dependencies = [
"png", "png",
"serde", "serde",
"thiserror 2.0.12", "thiserror 2.0.12",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -4140,6 +4140,17 @@ dependencies = [
"objc2-foundation 0.3.0", "objc2-foundation 0.3.0",
] ]
[[package]]
name = "objc2-security"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3126341c65c5d5728423ae95d788e1b660756486ad0592307ab87ba02d9a7268"
dependencies = [
"bitflags 2.9.0",
"objc2 0.6.0",
"objc2-core-foundation",
]
[[package]] [[package]]
name = "objc2-ui-kit" name = "objc2-ui-kit"
version = "0.3.0" version = "0.3.0"
@@ -4164,6 +4175,7 @@ dependencies = [
"objc2-app-kit", "objc2-app-kit",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-foundation 0.3.0", "objc2-foundation 0.3.0",
"objc2-security",
] ]
[[package]] [[package]]
@@ -5668,6 +5680,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_spanned"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@@ -5712,9 +5733,9 @@ dependencies = [
[[package]] [[package]]
name = "serialize-to-javascript" name = "serialize-to-javascript"
version = "0.1.1" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
@@ -5723,13 +5744,13 @@ dependencies = [
[[package]] [[package]]
name = "serialize-to-javascript-impl" name = "serialize-to-javascript-impl"
version = "0.1.1" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.109", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -6364,17 +6385,18 @@ dependencies = [
"cfg-expr", "cfg-expr",
"heck 0.5.0", "heck 0.5.0",
"pkg-config", "pkg-config",
"toml", "toml 0.8.20",
"version-compare", "version-compare",
] ]
[[package]] [[package]]
name = "tao" name = "tao"
version = "0.34.0" version = "0.34.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a" checksum = "4daa814018fecdfb977b59a094df4bd43b42e8e21f88fddfc05807e6f46efaaf"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.0",
"block2 0.6.0",
"core-foundation 0.10.0", "core-foundation 0.10.0",
"core-graphics", "core-graphics",
"crossbeam-channel", "crossbeam-channel",
@@ -6443,16 +6465,17 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]] [[package]]
name = "tauri" name = "tauri"
version = "2.6.0" version = "2.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7a0f4019c80391d143ee26cd7cd1ed271ac241d3087d333f99f3269ba90812" checksum = "5d545ccf7b60dcd44e07c6fb5aeb09140966f0aabd5d2aa14a6821df7bc99348"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
"cookie",
"dirs 6.0.0", "dirs 6.0.0",
"dunce", "dunce",
"embed_plist", "embed_plist",
"getrandom 0.2.15", "getrandom 0.3.2",
"glob", "glob",
"gtk", "gtk",
"heck 0.5.0", "heck 0.5.0",
@@ -6468,6 +6491,7 @@ dependencies = [
"objc2-app-kit", "objc2-app-kit",
"objc2-foundation 0.3.0", "objc2-foundation 0.3.0",
"objc2-ui-kit", "objc2-ui-kit",
"objc2-web-kit",
"percent-encoding", "percent-encoding",
"plist", "plist",
"raw-window-handle", "raw-window-handle",
@@ -6497,9 +6521,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-build" name = "tauri-build"
version = "2.3.0" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83" checksum = "67945dbaf8920dbe3a1e56721a419a0c3d085254ab24cff5b9ad55e2b0016e0b"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cargo_toml", "cargo_toml",
@@ -6515,15 +6539,15 @@ dependencies = [
"tauri-codegen", "tauri-codegen",
"tauri-utils", "tauri-utils",
"tauri-winres", "tauri-winres",
"toml", "toml 0.9.5",
"walkdir", "walkdir",
] ]
[[package]] [[package]]
name = "tauri-codegen" name = "tauri-codegen"
version = "2.3.0" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406" checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"ico", "ico",
@@ -6547,9 +6571,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-macros" name = "tauri-macros"
version = "2.3.0" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f59e1d1fa9651212dcb890a0c66226d819b716490b0cf43c078514da3591705" checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@@ -6561,9 +6585,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin" name = "tauri-plugin"
version = "2.3.0" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3" checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"glob", "glob",
@@ -6572,7 +6596,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tauri-utils", "tauri-utils",
"toml", "toml 0.9.5",
"walkdir", "walkdir",
] ]
@@ -6641,9 +6665,10 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-deep-link" name = "tauri-plugin-deep-link"
version = "2.4.1" version = "2.4.3"
dependencies = [ dependencies = [
"dunce", "dunce",
"plist",
"rust-ini", "rust-ini",
"serde", "serde",
"serde_json", "serde_json",
@@ -6659,7 +6684,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-dialog" name = "tauri-plugin-dialog"
version = "2.3.2" version = "2.4.0"
dependencies = [ dependencies = [
"log", "log",
"raw-window-handle", "raw-window-handle",
@@ -6675,7 +6700,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-fs" name = "tauri-plugin-fs"
version = "2.4.1" version = "2.4.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"dunce", "dunce",
@@ -6691,7 +6716,7 @@ dependencies = [
"tauri-plugin", "tauri-plugin",
"tauri-utils", "tauri-utils",
"thiserror 2.0.12", "thiserror 2.0.12",
"toml", "toml 0.9.5",
"url", "url",
] ]
@@ -6736,7 +6761,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-http" name = "tauri-plugin-http"
version = "2.5.1" version = "2.5.2"
dependencies = [ dependencies = [
"bytes", "bytes",
"cookie_store", "cookie_store",
@@ -6772,7 +6797,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-log" name = "tauri-plugin-log"
version = "2.6.0" version = "2.7.0"
dependencies = [ dependencies = [
"android_logger", "android_logger",
"byte-unit", "byte-unit",
@@ -6793,7 +6818,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-nfc" name = "tauri-plugin-nfc"
version = "2.3.0" version = "2.3.1"
dependencies = [ dependencies = [
"log", "log",
"serde", "serde",
@@ -6806,7 +6831,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-notification" name = "tauri-plugin-notification"
version = "2.3.0" version = "2.3.1"
dependencies = [ dependencies = [
"color-backtrace", "color-backtrace",
"ctor", "ctor",
@@ -6828,7 +6853,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-opener" name = "tauri-plugin-opener"
version = "2.4.0" version = "2.5.0"
dependencies = [ dependencies = [
"dunce", "dunce",
"glob", "glob",
@@ -6848,7 +6873,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-os" name = "tauri-plugin-os"
version = "2.3.0" version = "2.3.1"
dependencies = [ dependencies = [
"gethostname 1.0.1", "gethostname 1.0.1",
"log", "log",
@@ -6864,7 +6889,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-persisted-scope" name = "tauri-plugin-persisted-scope"
version = "2.3.1" version = "2.3.2"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"bincode", "bincode",
@@ -6916,7 +6941,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-shell" name = "tauri-plugin-shell"
version = "2.3.0" version = "2.3.1"
dependencies = [ dependencies = [
"encoding_rs", "encoding_rs",
"log", "log",
@@ -6935,7 +6960,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-single-instance" name = "tauri-plugin-single-instance"
version = "2.3.2" version = "2.3.4"
dependencies = [ dependencies = [
"semver", "semver",
"serde", "serde",
@@ -6967,7 +6992,7 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-store" name = "tauri-plugin-store"
version = "2.3.0" version = "2.4.0"
dependencies = [ dependencies = [
"dunce", "dunce",
"serde", "serde",
@@ -7080,9 +7105,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "2.7.0" version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4" checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846"
dependencies = [ dependencies = [
"cookie", "cookie",
"dpi", "dpi",
@@ -7091,20 +7116,23 @@ dependencies = [
"jni", "jni",
"objc2 0.6.0", "objc2 0.6.0",
"objc2-ui-kit", "objc2-ui-kit",
"objc2-web-kit",
"raw-window-handle", "raw-window-handle",
"serde", "serde",
"serde_json", "serde_json",
"tauri-utils", "tauri-utils",
"thiserror 2.0.12", "thiserror 2.0.12",
"url", "url",
"webkit2gtk",
"webview2-com",
"windows 0.61.1", "windows 0.61.1",
] ]
[[package]] [[package]]
name = "tauri-runtime-wry" name = "tauri-runtime-wry"
version = "2.7.0" version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe52ed0ef40fd7ad51a620ecb3018e32eba3040bb95025216a962a37f6f050c5" checksum = "c1fe9d48bd122ff002064e88cfcd7027090d789c4302714e68fcccba0f4b7807"
dependencies = [ dependencies = [
"gtk", "gtk",
"http", "http",
@@ -7129,16 +7157,16 @@ dependencies = [
[[package]] [[package]]
name = "tauri-utils" name = "tauri-utils"
version = "2.5.0" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e" checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"anyhow", "anyhow",
"cargo_metadata", "cargo_metadata",
"ctor", "ctor",
"dunce", "dunce",
"getrandom 0.2.15", "getrandom 0.3.2",
"glob", "glob",
"html5ever", "html5ever",
"http", "http",
@@ -7160,7 +7188,7 @@ dependencies = [
"serialize-to-javascript", "serialize-to-javascript",
"swift-rs", "swift-rs",
"thiserror 2.0.12", "thiserror 2.0.12",
"toml", "toml 0.9.5",
"url", "url",
"urlpattern", "urlpattern",
"uuid", "uuid",
@@ -7174,7 +7202,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56eaa45f707bedf34d19312c26d350bc0f3c59a47e58e8adbeecdc850d2c13a0" checksum = "56eaa45f707bedf34d19312c26d350bc0f3c59a47e58e8adbeecdc850d2c13a0"
dependencies = [ dependencies = [
"embed-resource", "embed-resource",
"toml", "toml 0.8.20",
] ]
[[package]] [[package]]
@@ -7476,11 +7504,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned 0.6.8",
"toml_datetime", "toml_datetime 0.6.8",
"toml_edit 0.22.24", "toml_edit 0.22.24",
] ]
[[package]]
name = "toml"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
dependencies = [
"indexmap 2.9.0",
"serde",
"serde_spanned 1.0.0",
"toml_datetime 0.7.0",
"toml_parser",
"toml_writer",
"winnow 0.7.12",
]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.8" version = "0.6.8"
@@ -7490,6 +7533,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "toml_datetime"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.19.15" version = "0.19.15"
@@ -7497,7 +7549,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [ dependencies = [
"indexmap 2.9.0", "indexmap 2.9.0",
"toml_datetime", "toml_datetime 0.6.8",
"winnow 0.5.40", "winnow 0.5.40",
] ]
@@ -7508,7 +7560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
dependencies = [ dependencies = [
"indexmap 2.9.0", "indexmap 2.9.0",
"toml_datetime", "toml_datetime 0.6.8",
"winnow 0.5.40", "winnow 0.5.40",
] ]
@@ -7520,11 +7572,26 @@ checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [ dependencies = [
"indexmap 2.9.0", "indexmap 2.9.0",
"serde", "serde",
"serde_spanned", "serde_spanned 0.6.8",
"toml_datetime", "toml_datetime 0.6.8",
"winnow 0.7.6", "winnow 0.7.12",
] ]
[[package]]
name = "toml_parser"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
dependencies = [
"winnow 0.7.12",
]
[[package]]
name = "toml_writer"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.5.2" version = "0.5.2"
@@ -7586,9 +7653,9 @@ dependencies = [
[[package]] [[package]]
name = "tray-icon" name = "tray-icon"
version = "0.20.0" version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d433764348e7084bad2c5ea22c96c71b61b17afe3a11645710f533bd72b6a2b5" checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dirs 6.0.0", "dirs 6.0.0",
@@ -8833,9 +8900,9 @@ dependencies = [
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.7.6" version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -8901,14 +8968,15 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]] [[package]]
name = "wry" name = "wry"
version = "0.52.0" version = "0.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b08db04817a654a7e3339647d9cf8b497ed9ddcd4ec7cfda5a3a220c10a3bba3" checksum = "e3b6763512fe4b51c80b3ce9b50939d682acb4de335dfabbdb20d7a2642199b7"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"block2 0.6.0", "block2 0.6.0",
"cookie", "cookie",
"crossbeam-channel", "crossbeam-channel",
"dirs 6.0.0",
"dpi", "dpi",
"dunce", "dunce",
"gdkx11", "gdkx11",
@@ -9069,7 +9137,7 @@ dependencies = [
"tracing", "tracing",
"uds_windows", "uds_windows",
"windows-sys 0.60.2", "windows-sys 0.60.2",
"winnow 0.7.6", "winnow 0.7.12",
"zbus_macros", "zbus_macros",
"zbus_names", "zbus_names",
"zvariant", "zvariant",
@@ -9108,7 +9176,7 @@ checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97"
dependencies = [ dependencies = [
"serde", "serde",
"static_assertions", "static_assertions",
"winnow 0.7.6", "winnow 0.7.12",
"zvariant", "zvariant",
] ]
@@ -9295,7 +9363,7 @@ dependencies = [
"enumflags2", "enumflags2",
"serde", "serde",
"url", "url",
"winnow 0.7.6", "winnow 0.7.12",
"zvariant_derive", "zvariant_derive",
"zvariant_utils", "zvariant_utils",
] ]
@@ -9324,5 +9392,5 @@ dependencies = [
"serde", "serde",
"static_assertions", "static_assertions",
"syn 2.0.100", "syn 2.0.100",
"winnow 0.7.6", "winnow 0.7.12",
] ]
+4 -4
View File
@@ -12,10 +12,10 @@ resolver = "2"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
tracing = "0.1" tracing = "0.1"
log = "0.4" log = "0.4"
tauri = { version = "2.6", default-features = false } tauri = { version = "2.8.2", default-features = false }
tauri-build = "2.3" tauri-build = "2.4"
tauri-plugin = "2.3" tauri-plugin = "2.4"
tauri-utils = "2.5" tauri-utils = "2.7"
serde_json = "1" serde_json = "1"
thiserror = "2" thiserror = "2"
url = "2" url = "2"
+2 -1
View File
@@ -3,11 +3,12 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import eslint from '@eslint/js' import eslint from '@eslint/js'
import { defineConfig } from 'eslint/config'
import eslintConfigPrettier from 'eslint-config-prettier' import eslintConfigPrettier from 'eslint-config-prettier'
import eslintPluginSecurity from 'eslint-plugin-security' import eslintPluginSecurity from 'eslint-plugin-security'
import tseslint from 'typescript-eslint' import tseslint from 'typescript-eslint'
export default tseslint.config( export default defineConfig(
{ {
ignores: [ ignores: [
'**/target', '**/target',
+31
View File
@@ -1,5 +1,36 @@
# Changelog # Changelog
## \[2.0.32]
### Dependencies
- Upgraded to `dialog-js@2.4.0`
- Upgraded to `log-js@2.7.0`
## \[2.0.31]
### Dependencies
- Upgraded to `shell-js@2.3.1`
## \[2.0.30]
### Dependencies
- Upgraded to `notification-js@2.3.1`
## \[2.0.29]
### Dependencies
- Upgraded to `fs-js@2.4.2`
- Upgraded to `nfc-js@2.3.1`
- Upgraded to `opener-js@2.5.0`
- Upgraded to `os-js@2.3.1`
- Upgraded to `store-js@2.4.0`
- Upgraded to `dialog-js@2.3.3`
- Upgraded to `http-js@2.5.2`
## \[2.0.28] ## \[2.0.28]
### Dependencies ### Dependencies
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta <meta
name="viewport" name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover" content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=0"
/> />
<title>Svelte + Vite App</title> <title>Svelte + Vite App</title>
</head> </head>
+13 -13
View File
@@ -1,7 +1,7 @@
{ {
"name": "api", "name": "api",
"private": true, "private": true,
"version": "2.0.28", "version": "2.0.32",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --clearScreen false", "dev": "vite --clearScreen false",
@@ -10,25 +10,25 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.7.0", "@tauri-apps/api": "2.8.0",
"@tauri-apps/plugin-barcode-scanner": "^2.4.0", "@tauri-apps/plugin-barcode-scanner": "^2.4.0",
"@tauri-apps/plugin-biometric": "^2.3.0", "@tauri-apps/plugin-biometric": "^2.3.0",
"@tauri-apps/plugin-cli": "^2.4.0", "@tauri-apps/plugin-cli": "^2.4.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0", "@tauri-apps/plugin-clipboard-manager": "^2.3.0",
"@tauri-apps/plugin-dialog": "^2.3.2", "@tauri-apps/plugin-dialog": "^2.4.0",
"@tauri-apps/plugin-fs": "^2.4.1", "@tauri-apps/plugin-fs": "^2.4.2",
"@tauri-apps/plugin-geolocation": "^2.2.0", "@tauri-apps/plugin-geolocation": "^2.2.0",
"@tauri-apps/plugin-global-shortcut": "^2.3.0", "@tauri-apps/plugin-global-shortcut": "^2.3.0",
"@tauri-apps/plugin-haptics": "^2.2.0", "@tauri-apps/plugin-haptics": "^2.2.0",
"@tauri-apps/plugin-http": "^2.5.1", "@tauri-apps/plugin-http": "^2.5.2",
"@tauri-apps/plugin-nfc": "^2.3.0", "@tauri-apps/plugin-nfc": "^2.3.1",
"@tauri-apps/plugin-notification": "^2.3.0", "@tauri-apps/plugin-notification": "^2.3.1",
"@tauri-apps/plugin-opener": "^2.4.0", "@tauri-apps/plugin-opener": "^2.5.0",
"@tauri-apps/plugin-os": "^2.3.0", "@tauri-apps/plugin-os": "^2.3.1",
"@tauri-apps/plugin-process": "^2.3.0", "@tauri-apps/plugin-process": "^2.3.0",
"@tauri-apps/plugin-shell": "^2.3.0",
"@tauri-apps/plugin-secure-storage": "file:../../plugins/secure-storage", "@tauri-apps/plugin-secure-storage": "file:../../plugins/secure-storage",
"@tauri-apps/plugin-store": "^2.3.0", "@tauri-apps/plugin-shell": "^2.3.1",
"@tauri-apps/plugin-store": "^2.4.0",
"@tauri-apps/plugin-updater": "^2.9.0", "@tauri-apps/plugin-updater": "^2.9.0",
"@tauri-apps/plugin-upload": "^2.3.0", "@tauri-apps/plugin-upload": "^2.3.0",
"@zerodevx/svelte-json-view": "1.0.11" "@zerodevx/svelte-json-view": "1.0.11"
@@ -37,10 +37,10 @@
"@iconify-json/codicon": "^1.2.12", "@iconify-json/codicon": "^1.2.12",
"@iconify-json/ph": "^1.2.2", "@iconify-json/ph": "^1.2.2",
"@sveltejs/vite-plugin-svelte": "^6.0.0", "@sveltejs/vite-plugin-svelte": "^6.0.0",
"@tauri-apps/cli": "2.7.1", "@tauri-apps/cli": "2.8.4",
"@unocss/extractor-svelte": "^66.3.3", "@unocss/extractor-svelte": "^66.3.3",
"svelte": "^5.20.4", "svelte": "^5.20.4",
"unocss": "^66.3.3", "unocss": "^66.3.3",
"vite": "^7.0.4" "vite": "^7.0.7"
} }
} }
+31
View File
@@ -1,5 +1,36 @@
# Changelog # Changelog
## \[2.0.36]
### Dependencies
- Upgraded to `dialog@2.4.0`
- Upgraded to `log@2.7.0`
## \[2.0.35]
### Dependencies
- Upgraded to `shell@2.3.1`
## \[2.0.34]
### Dependencies
- Upgraded to `notification@2.3.1`
## \[2.0.33]
### Dependencies
- Upgraded to `fs@2.4.2`
- Upgraded to `nfc@2.3.1`
- Upgraded to `opener@2.5.0`
- Upgraded to `os@2.3.1`
- Upgraded to `store@2.4.0`
- Upgraded to `dialog@2.3.3`
- Upgraded to `http@2.5.2`
## \[2.0.32] ## \[2.0.32]
### Dependencies ### Dependencies
+11 -11
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "api" name = "api"
publish = false publish = false
version = "2.0.32" version = "2.0.36"
description = "An example Tauri Application showcasing the api" description = "An example Tauri Application showcasing the api"
edition = "2021" edition = "2021"
rust-version = { workspace = true } rust-version = { workspace = true }
@@ -20,25 +20,25 @@ serde = { workspace = true }
tiny_http = "0.12" tiny_http = "0.12"
time = "0.3" time = "0.3"
log = { workspace = true } log = { workspace = true }
tauri-plugin-log = { path = "../../../plugins/log", version = "2.6.0" } tauri-plugin-log = { path = "../../../plugins/log", version = "2.7.0" }
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.1", features = [ tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.2", features = [
"watch", "watch",
] } ] }
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.0" } tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.0" }
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.3.2" } tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.4.0" }
tauri-plugin-http = { path = "../../../plugins/http", features = [ tauri-plugin-http = { path = "../../../plugins/http", features = [
"multipart", "multipart",
"cookies", "cookies",
], version = "2.5.1" } ], version = "2.5.2" }
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.0", features = [ tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.1", features = [
"windows7-compat", "windows7-compat",
] } ] }
tauri-plugin-os = { path = "../../../plugins/os", version = "2.3.0" } tauri-plugin-os = { path = "../../../plugins/os", version = "2.3.1" }
tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.0" } tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.0" }
tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.4.0" } tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.5.0" }
tauri-plugin-secure-storage = { path = "../../../plugins/secure-storage" } tauri-plugin-secure-storage = { path = "../../../plugins/secure-storage" }
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.0" } tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.1" }
tauri-plugin-store = { path = "../../../plugins/store", version = "2.3.0" } tauri-plugin-store = { path = "../../../plugins/store", version = "2.4.0" }
tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" } tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" }
[dependencies.tauri] [dependencies.tauri]
@@ -63,7 +63,7 @@ tauri-plugin-window-state = { path = "../../../plugins/window-state", version =
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies] [target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.0" } tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.0" }
tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.0" } tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.1" }
tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.3.0" } tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.3.0" }
tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.0" } tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.0" }
tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.3.0" } tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.3.0" }
@@ -68,6 +68,9 @@
"fs:allow-rename", "fs:allow-rename",
"fs:allow-mkdir", "fs:allow-mkdir",
"fs:allow-remove", "fs:allow-remove",
"fs:allow-stat",
"fs:allow-fstat",
"fs:allow-lstat",
"fs:allow-write-text-file", "fs:allow-write-text-file",
"fs:read-meta", "fs:read-meta",
"fs:scope-download-recursive", "fs:scope-download-recursive",
@@ -75,6 +78,9 @@
{ {
"identifier": "fs:scope-appdata-recursive", "identifier": "fs:scope-appdata-recursive",
"allow": [ "allow": [
{
"path": "$APPDATA/db/"
},
{ {
"path": "$APPDATA/db/**" "path": "$APPDATA/db/**"
} }
+21 -9
View File
@@ -44,6 +44,13 @@
await message("Tauri is awesome!"); await message("Tauri is awesome!");
} }
async function msgCustom(result) {
const buttons = { yes: "awesome", no: "amazing", cancel: "stunning" };
await message(`Tauri is: `, { buttons })
.then((res) => onMessage(`Tauri is ${res}`))
.catch(onMessage);
}
function openDialog() { function openDialog() {
open({ open({
title: "My wonderful open dialog", title: "My wonderful open dialog",
@@ -136,12 +143,17 @@
<label for="dialog-directory">Directory</label> <label for="dialog-directory">Directory</label>
</div> </div>
<br /> <br />
<button class="btn" id="open-dialog" on:click={openDialog}>Open dialog</button>
<button class="btn" id="save-dialog" on:click={saveDialog} <div class="flex flex-wrap flex-col md:flex-row gap-2 children:flex-shrink-0">
>Open save dialog</button <button class="btn" id="open-dialog" on:click={openDialog}>Open dialog</button>
> <button class="btn" id="save-dialog" on:click={saveDialog}
<button class="btn" id="prompt-dialog" on:click={prompt}>Prompt</button> >Open save dialog</button
<button class="btn" id="custom-prompt-dialog" on:click={promptCustom} >
>Prompt (custom)</button <button class="btn" id="prompt-dialog" on:click={prompt}>Prompt</button>
> <button class="btn" id="custom-prompt-dialog" on:click={promptCustom}
<button class="btn" id="message-dialog" on:click={msg}>Message</button> >Prompt (custom)</button
>
<button class="btn" id="message-dialog" on:click={msg}>Message</button>
<button class="btn" id="message-dialog" on:click={msgCustom}>Message (custom)</button>
</div>
+114 -93
View File
@@ -1,194 +1,214 @@
<script> <script>
import * as fs from "@tauri-apps/plugin-fs"; import * as fs from '@tauri-apps/plugin-fs'
import { convertFileSrc } from "@tauri-apps/api/core"; import * as os from '@tauri-apps/plugin-os'
import { arrayBufferToBase64 } from "../lib/utils"; import { convertFileSrc } from '@tauri-apps/api/core'
import { onDestroy } from "svelte"; import { arrayBufferToBase64 } from '../lib/utils'
import { onDestroy, onMount } from 'svelte'
export let onMessage; const { onMessage, insecureRenderHtml } = $props()
export let insecureRenderHtml;
let path = ""; let path = $state('')
let img; let img
/** @type {fs.FileHandle} */ /** @type {fs.FileHandle} */
let file; let file = $state()
let renameTo; let renameTo = $state()
let watchPath = ""; let watchPath = $state('')
let watchDebounceDelay = "0"; let watchDebounceDelay = $state(0)
let watchRecursive = false; let watchRecursive = $state(false)
let unwatchFn; /** @type {fs.BaseDirectory | undefined} */
let unwatchPath = ""; let baseDir = $state()
let unwatchFn
let unwatchPath = ''
let isMobile = $state(false)
function getDir() { onMount(() => {
const dirSelect = document.getElementById("dir"); let platform = os.platform()
return dirSelect.value ? parseInt(dir.value) : null; isMobile = platform === 'android' || platform === 'ios'
} })
const DirOptions = Object.keys(fs.BaseDirectory) const dirOptions = Object.keys(fs.BaseDirectory).filter((key) =>
.filter((key) => isNaN(parseInt(key))) isNaN(parseInt(key))
.map((dir) => [dir, fs.BaseDirectory[dir]]); )
function open() { function open() {
fs.open(path, { fs.open(path, {
baseDir: getDir(), baseDir,
read: true, read: true,
write: true, write: true,
create: true, create: true
}) })
.then((f) => { .then((f) => {
file = f; file = f
onMessage(`Opened ${path}`); onMessage(`Opened ${path}`)
}) })
.catch(onMessage); .catch(onMessage)
} }
function mkdir() { function mkdir() {
fs.mkdir(path, { baseDir: getDir() }) fs.mkdir(path, { baseDir, recursive: true })
.then(() => { .then(() => {
onMessage(`Created dir ${path}`); onMessage(`Created dir ${path}`)
}) })
.catch(onMessage); .catch(onMessage)
} }
function remove() { function remove() {
fs.remove(path, { baseDir: getDir() }) fs.remove(path, { baseDir })
.then(() => { .then(() => {
onMessage(`Removed ${path}`); onMessage(`Removed ${path}`)
}) })
.catch(onMessage); .catch(onMessage)
} }
function rename() { function rename() {
fs.rename(path, renameTo, { fs.rename(path, renameTo, {
oldPathBaseDir: getDir(), oldPathBaseDir,
newPathBaseDir: getDir(), newPathBaseDir
}) })
.then(() => { .then(() => {
onMessage(`Renamed ${path} to ${renameTo}`); onMessage(`Renamed ${path} to ${renameTo}`)
}) })
.catch(onMessage); .catch(onMessage)
} }
function truncate() { function truncate() {
file file
.truncate(0) .truncate(0)
.then(() => { .then(() => {
onMessage(`Truncated file`); onMessage(`Truncated file`)
}) })
.catch(onMessage); .catch(onMessage)
}
function write() {
const encoder = new TextEncoder()
file
.write(encoder.encode('Hello from Tauri :)'))
.then(() => {
onMessage(`wrote to file`)
})
.catch(onMessage)
} }
function stat() { function stat() {
file file
.stat() .stat()
.then((stat) => { .then((stat) => {
onMessage(`File stat ${JSON.stringify(stat)}`); onMessage(`File stat ${JSON.stringify(stat)}`)
}) })
.catch(onMessage); .catch(onMessage)
} }
function read() { function read() {
const opts = { const opts = {
baseDir: getDir(), baseDir
}; }
fs.stat(path, opts) fs.stat(path, opts)
.then((stat) => { .then((stat) => {
const isFile = stat.isFile; const isFile = stat.isFile
const promise = isFile const promise = isFile
? fs.readFile(path, opts) ? fs.readFile(path, opts)
: fs.readDir(path, opts); : fs.readDir(path, opts)
promise promise
.then(function (response) { .then(function (response) {
if (isFile) { if (isFile) {
if (path.includes(".png") || path.includes(".jpg")) { if (path.includes('.png') || path.includes('.jpg')) {
arrayBufferToBase64( arrayBufferToBase64(
new Uint8Array(response), new Uint8Array(response),
function (base64) { function (base64) {
const src = "data:image/png;base64," + base64; const src = 'data:image/png;base64,' + base64
insecureRenderHtml('<img src="' + src + '"></img>'); insecureRenderHtml('<img src="' + src + '"></img>')
} }
); )
} else { } else {
const value = String.fromCharCode.apply(null, response); const value = String.fromCharCode.apply(null, response)
insecureRenderHtml( insecureRenderHtml(
'<textarea id="file-response"></textarea><button id="file-save">Save</button>' '<textarea id="file-response"></textarea><button id="file-save">Save</button>'
); )
setTimeout(() => { setTimeout(() => {
const fileInput = document.getElementById("file-response"); const fileInput = document.getElementById('file-response')
fileInput.value = value; fileInput.value = value
document document
.getElementById("file-save") .getElementById('file-save')
.addEventListener("click", function () { .addEventListener('click', function () {
fs.writeTextFile(path, fileInput.value, { fs.writeTextFile(path, fileInput.value, {
baseDir: getDir(), baseDir
}).catch(onMessage); }).catch(onMessage)
}); })
}); })
} }
} else { } else {
onMessage(response); onMessage(response)
} }
}) })
.catch(onMessage); .catch(onMessage)
}) })
.catch(onMessage); .catch(onMessage)
} }
function setSrc() { function setSrc() {
img.src = convertFileSrc(path); img.src = convertFileSrc(path)
} }
function watch() { function watch() {
unwatch(); unwatch()
if (watchPath) { if (watchPath) {
onMessage(`Watching ${watchPath} for changes`); onMessage(`Watching ${watchPath} for changes`)
let options = { let options = {
recursive: watchRecursive, recursive: watchRecursive,
delayMs: parseInt(watchDebounceDelay), delayMs: watchDebounceDelay
}; }
if (options.delayMs === 0) { if (options.delayMs === 0) {
fs.watchImmediate(watchPath, onMessage, options) fs.watchImmediate(watchPath, onMessage, options)
.then((fn) => { .then((fn) => {
unwatchFn = fn; unwatchFn = fn
unwatchPath = watchPath; unwatchPath = watchPath
}) })
.catch(onMessage); .catch(onMessage)
} else { } else {
fs.watch(watchPath, onMessage, options) fs.watch(watchPath, onMessage, options)
.then((fn) => { .then((fn) => {
unwatchFn = fn; unwatchFn = fn
unwatchPath = watchPath; unwatchPath = watchPath
}) })
.catch(onMessage); .catch(onMessage)
} }
} }
} }
function unwatch() { function unwatch() {
if (unwatchFn) { if (unwatchFn) {
onMessage(`Stopped watching ${unwatchPath} for changes`); onMessage(`Stopped watching ${unwatchPath} for changes`)
unwatchFn(); unwatchFn()
} }
unwatchFn = undefined; unwatchFn = undefined
unwatchPath = undefined; unwatchPath = undefined
} }
onDestroy(() => { onDestroy(() => {
if (file) { if (file) {
file.close(); file.close()
} }
if (unwatchFn) { if (unwatchFn) {
unwatchFn(); unwatchFn()
} }
}) })
</script> </script>
<div class="flex flex-col"> <div class="flex flex-col">
{#if isMobile}
<div>
On mobile, paths outside of App* paths require the use of dialogs
regardless of Tauri's scope mechanism.
</div>
<br />
{/if}
<div class="flex gap-1"> <div class="flex gap-1">
<select class="input" id="dir"> <select class="input" bind:value={baseDir}>
<option value="">None</option> <option value={undefined} selected>None</option>
{#each DirOptions as dir} {#each dirOptions as dir}
<option value={dir[1]}>{dir[0]}</option> <option value={fs.BaseDirectory[dir]}>{dir}</option>
{/each} {/each}
</select> </select>
<input <input
@@ -199,20 +219,21 @@
</div> </div>
<br /> <br />
<div> <div>
<button class="btn" on:click={open}>Open</button> <button class="btn" onclick={open}>Open</button>
<button class="btn" on:click={read}>Read</button> <button class="btn" onclick={read}>Read</button>
<button class="btn" on:click={mkdir}>Mkdir</button> <button class="btn" onclick={mkdir}>Mkdir</button>
<button class="btn" on:click={remove}>Remove</button> <button class="btn" onclick={remove}>Remove</button>
<div class="flex flex-row"> <div class="flex flex-row">
<button class="btn" on:click={rename}>Rename</button> <button class="btn" onclick={rename}>Rename</button>
<input class="input" bind:value={renameTo} placeholder="To" /> <input class="input" bind:value={renameTo} placeholder="To" />
</div> </div>
<button class="btn" type="button" on:click={setSrc}>Use as img src</button> <button class="btn" type="button" onclick={setSrc}>Use as img src</button>
</div> </div>
{#if file} {#if file}
<div> <div>
<button class="btn" on:click={truncate}>Truncate</button> <button class="btn" onclick={write}>Write</button>
<button class="btn" on:click={stat}>Stat</button> <button class="btn" onclick={truncate}>Truncate</button>
<button class="btn" onclick={stat}>Stat</button>
</div> </div>
{/if} {/if}
@@ -241,8 +262,8 @@
</div> </div>
<br /> <br />
<div> <div>
<button class="btn" on:click={watch}>Watch</button> <button class="btn" onclick={watch}>Watch</button>
<button class="btn" on:click={unwatch}>Unwatch</button> <button class="btn" onclick={unwatch}>Unwatch</button>
</div> </div>
</div> </div>
+14 -4
View File
@@ -1,16 +1,21 @@
<script> <script>
import { sendNotification } from '@tauri-apps/plugin-notification'
export let onMessage export let onMessage
let sound = ''
// send the notification directly // send the notification directly
// the backend is responsible for checking the permission // the backend is responsible for checking the permission
function _sendNotification() { function _sendNotification() {
new Notification('Notification title', { sendNotification({
body: 'This is the notification body' title: 'Notification title',
body: 'This is the notification body',
sound: sound || null
}) })
} }
// alternatively, check the permission ourselves // alternatively, check the permission ourselves
function sendNotification() { function triggerNotification() {
if (Notification.permission === 'default') { if (Notification.permission === 'default') {
Notification.requestPermission() Notification.requestPermission()
.then(function (response) { .then(function (response) {
@@ -29,6 +34,11 @@
} }
</script> </script>
<button class="btn" id="notification" on:click={sendNotification}> <input
class="input grow"
placeholder="Notification sound..."
bind:value={sound}
/>
<button class="btn" id="notification" on:click={triggerNotification}>
Send test notification Send test notification
</button> </button>
+6 -5
View File
@@ -11,20 +11,21 @@
"example:api:dev": "pnpm run --filter \"api\" tauri dev" "example:api:dev": "pnpm run --filter \"api\" tauri dev"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "9.32.0", "@eslint/js": "9.36.0",
"@rollup/plugin-node-resolve": "16.0.1", "@rollup/plugin-node-resolve": "16.0.1",
"@rollup/plugin-terser": "0.4.4", "@rollup/plugin-terser": "0.4.4",
"@rollup/plugin-typescript": "12.1.4", "@rollup/plugin-typescript": "12.1.4",
"covector": "^0.12.4", "covector": "^0.12.4",
"eslint": "9.32.0", "eslint": "9.36.0",
"eslint-config-prettier": "10.1.8", "eslint-config-prettier": "10.1.8",
"eslint-plugin-security": "3.0.1", "eslint-plugin-security": "3.0.1",
"prettier": "3.6.2", "prettier": "3.6.2",
"rollup": "4.46.2", "rollup": "4.52.3",
"tslib": "2.8.1", "tslib": "2.8.1",
"typescript": "5.9.2", "typescript": "5.9.2",
"typescript-eslint": "8.39.0" "typescript-eslint": "8.44.1"
}, },
"minimumReleaseAge": 4320,
"pnpm": { "pnpm": {
"overrides": { "overrides": {
"esbuild@<0.25.0": ">=0.25.0" "esbuild@<0.25.0": ">=0.25.0"
@@ -34,6 +35,6 @@
] ]
}, },
"engines": { "engines": {
"pnpm": "^10.0.0" "pnpm": "^10.16.0"
} }
} }
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+1 -1
View File
@@ -25,6 +25,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+1 -1
View File
@@ -25,6 +25,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+8
View File
@@ -1,5 +1,13 @@
# Changelog # Changelog
## \[2.4.3]
- [`2522b71f`](https://github.com/tauri-apps/plugins-workspace/commit/2522b71f6bcae65c03b24415eb9295c9e7c84ffc) ([#2970](https://github.com/tauri-apps/plugins-workspace/pull/2970) by [@WSH032](https://github.com/tauri-apps/plugins-workspace/../../WSH032)) Revert the breaking change introduced by [#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928).
## \[2.4.2]
- [`21d721a0`](https://github.com/tauri-apps/plugins-workspace/commit/21d721a0c2731fc201872f5b99ea8bbdc61b0b60) ([#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Linux, improved error messages when OS commands fail.
## \[2.4.1] ## \[2.4.1]
- [`d4f8299b`](https://github.com/tauri-apps/plugins-workspace/commit/d4f8299b12f107718c70692840a63768d65baaf9) ([#2844](https://github.com/tauri-apps/plugins-workspace/pull/2844) by [@yobson1](https://github.com/tauri-apps/plugins-workspace/../../yobson1)) Fix deep link protocol handler not set as default on linux - [`d4f8299b`](https://github.com/tauri-apps/plugins-workspace/commit/d4f8299b12f107718c70692840a63768d65baaf9) ([#2844](https://github.com/tauri-apps/plugins-workspace/pull/2844) by [@yobson1](https://github.com/tauri-apps/plugins-workspace/../../yobson1)) Fix deep link protocol handler not set as default on linux
+4 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-deep-link" name = "tauri-plugin-deep-link"
version = "2.4.1" version = "2.4.3"
description = "Set your Tauri application as the default handler for an URL" description = "Set your Tauri application as the default handler for an URL"
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
@@ -27,6 +27,9 @@ serde_json = { workspace = true }
tauri-utils = { workspace = true } tauri-utils = { workspace = true }
tauri-plugin = { workspace = true, features = ["build"] } tauri-plugin = { workspace = true, features = ["build"] }
[target."cfg(target_os = \"macos\")".build-dependencies]
plist = "1"
[dependencies] [dependencies]
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
+119 -26
View File
@@ -10,50 +10,64 @@ const COMMANDS: &[&str] = &["get_current", "register", "unregister", "is_registe
// TODO: Consider using activity-alias in case users may have multiple activities in their app. // TODO: Consider using activity-alias in case users may have multiple activities in their app.
fn intent_filter(domain: &AssociatedDomain) -> String { fn intent_filter(domain: &AssociatedDomain) -> String {
let host = domain
.host
.as_ref()
.map(|h| format!(r#"<data android:host="{h}" />"#))
.unwrap_or_default();
let auto_verify = if domain.is_app_link() {
r#"android:autoVerify="true" "#.to_string()
} else {
String::new()
};
format!( format!(
r#"<intent-filter android:autoVerify="true"> r#"<intent-filter {auto_verify}>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
{} {schemes}
<data android:host="{}" /> {host}
{} {domains}
{} {path_patterns}
{} {path_prefixes}
{} {path_suffixes}
</intent-filter>"#, </intent-filter>"#,
domain schemes = domain
.scheme .scheme
.iter() .iter()
.map(|scheme| format!(r#"<data android:scheme="{scheme}" />"#)) .map(|scheme| format!(r#"<data android:scheme="{scheme}" />"#))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n "), .join("\n "),
domain.host, host = host,
domain domains = domain
.path .path
.iter() .iter()
.map(|path| format!(r#"<data android:path="{path}" />"#)) .map(|path| format!(r#"<data android:path="{path}" />"#))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n "), .join("\n "),
domain path_patterns = domain
.path_pattern .path_pattern
.iter() .iter()
.map(|pattern| format!(r#"<data android:pathPattern="{pattern}" />"#)) .map(|pattern| format!(r#"<data android:pathPattern="{pattern}" />"#))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n "), .join("\n "),
domain path_prefixes = domain
.path_prefix .path_prefix
.iter() .iter()
.map(|prefix| format!(r#"<data android:pathPrefix="{prefix}" />"#)) .map(|prefix| format!(r#"<data android:pathPrefix="{prefix}" />"#))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n "), .join("\n "),
domain path_suffixes = domain
.path_suffix .path_suffix
.iter() .iter()
.map(|suffix| format!(r#"<data android:pathSuffix="{suffix}" />"#)) .map(|suffix| format!(r#"<data android:pathSuffix="{suffix}" />"#))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n "), .join("\n "),
) )
.trim()
.to_string()
} }
fn main() { fn main() {
@@ -68,6 +82,16 @@ fn main() {
} }
if let Some(config) = tauri_plugin::plugin_config::<Config>("deep-link") { if let Some(config) = tauri_plugin::plugin_config::<Config>("deep-link") {
let errors: Vec<String> = config
.mobile
.iter()
.filter_map(|d| d.validate().err())
.collect();
if !errors.is_empty() {
panic!("Deep link config validation failed:\n{}", errors.join("\n"));
}
tauri_plugin::mobile::update_android_manifest( tauri_plugin::mobile::update_android_manifest(
"DEEP LINK PLUGIN", "DEEP LINK PLUGIN",
"activity", "activity",
@@ -80,20 +104,89 @@ fn main() {
) )
.expect("failed to rewrite AndroidManifest.xml"); .expect("failed to rewrite AndroidManifest.xml");
#[cfg(target_os = "macos")] #[cfg(any(target_os = "macos", target_os = "ios"))]
{ {
tauri_plugin::mobile::update_entitlements(|entitlements| { // we need to ensure that the entitlements are only
entitlements.insert( // generated for explicit app links and not
"com.apple.developer.associated-domains".into(), // other deep links because then they
config // are just going to complain and not be built or signed
.mobile let has_app_links = config.mobile.iter().any(|d| d.is_app_link());
.into_iter()
.map(|d| format!("applinks:{}", d.host).into()) if !has_app_links {
.collect::<Vec<_>>() tauri_plugin::mobile::update_entitlements(|entitlements| {
.into(), entitlements.remove("com.apple.developer.associated-domains");
); })
}) .expect("failed to update entitlements");
.expect("failed to update entitlements"); } else {
tauri_plugin::mobile::update_entitlements(|entitlements| {
entitlements.insert(
"com.apple.developer.associated-domains".into(),
config
.mobile
.iter()
.filter(|d| d.is_app_link())
.filter_map(|d| d.host.as_ref())
.map(|host| format!("applinks:{}", host).into())
.collect::<Vec<_>>()
.into(),
);
})
.expect("failed to update entitlements");
}
let deep_link_domains = config
.mobile
.iter()
.filter_map(|domain| {
if domain.is_app_link() {
return None;
}
Some(domain)
})
.collect::<Vec<_>>();
if deep_link_domains.is_empty() {
tauri_plugin::mobile::update_info_plist(|info_plist| {
info_plist.remove("CFBundleURLTypes");
})
.expect("failed to update Info.plist");
} else {
tauri_plugin::mobile::update_info_plist(|info_plist| {
info_plist.insert(
"CFBundleURLTypes".into(),
deep_link_domains
.iter()
.map(|domain| {
let schemes = domain
.scheme
.iter()
.filter(|scheme| {
scheme.as_str() != "https" && scheme.as_str() != "http"
})
.collect::<Vec<_>>();
let mut dict = plist::Dictionary::new();
dict.insert(
"CFBundleURLSchemes".into(),
schemes
.iter()
.map(|s| s.to_string().into())
.collect::<Vec<_>>()
.into(),
);
dict.insert(
"CFBundleURLName".into(),
format!("{}", domain.scheme[0]).into(),
);
plist::Value::Dictionary(dict)
})
.collect::<Vec<_>>()
.into(),
);
})
.expect("failed to update Info.plist");
}
} }
} }
} }
@@ -1,5 +1,17 @@
# Changelog # Changelog
## \[2.2.6]
### Dependencies
- Upgraded to `deep-link-js@2.4.3`
## \[2.2.5]
### Dependencies
- Upgraded to `deep-link-js@2.4.2`
## \[2.2.4] ## \[2.2.4]
### Dependencies ### Dependencies
+5 -5
View File
@@ -1,7 +1,7 @@
{ {
"name": "deep-link-example", "name": "deep-link-example",
"private": true, "private": true,
"version": "2.2.4", "version": "2.2.6",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -10,12 +10,12 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@tauri-apps/api": "2.7.0", "@tauri-apps/api": "2.8.0",
"@tauri-apps/plugin-deep-link": "2.4.1" "@tauri-apps/plugin-deep-link": "2.4.3"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "2.7.1", "@tauri-apps/cli": "2.8.4",
"typescript": "^5.7.3", "typescript": "^5.7.3",
"vite": "^7.0.4" "vite": "^7.0.7"
} }
} }
@@ -14,13 +14,13 @@ val tauriProperties = Properties().apply {
} }
android { android {
compileSdk = 34 compileSdk = 36
namespace = "com.tauri.deep_link_example" namespace = "com.tauri.deep_link_example"
defaultConfig { defaultConfig {
manifestPlaceholders["usesCleartextTraffic"] = "false" manifestPlaceholders["usesCleartextTraffic"] = "false"
applicationId = "com.tauri.deep_link_example" applicationId = "com.tauri.deep_link_example"
minSdk = 24 minSdk = 24
targetSdk = 34 targetSdk = 36
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
} }
@@ -58,9 +58,10 @@ rust {
} }
dependencies { dependencies {
implementation("androidx.webkit:webkit:1.6.1") implementation("androidx.webkit:webkit:1.14.0")
implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.appcompat:appcompat:1.7.1")
implementation("com.google.android.material:material:1.8.0") implementation("androidx.activity:activity-ktx:1.10.1")
implementation("com.google.android.material:material:1.12.0")
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.4") androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
@@ -23,23 +23,40 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER" /> <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter> </intent-filter>
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. --> <!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true" >
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:scheme="http" />
<data android:host="fabianlars.de" /> <data android:host="fabianlars.de" />
<data android:pathPrefix="/intent" /> <data android:pathPrefix="/intent" />
</intent-filter> </intent-filter>
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true" >
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:scheme="http" />
<data android:host="tauri.app" /> <data android:host="tauri.app" />
</intent-filter>
<intent-filter >
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="taurideeplink" />
</intent-filter> </intent-filter>
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. --> <!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
</activity> </activity>
@@ -1,7 +1,11 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
package com.tauri.deep_link_example package com.tauri.deep_link_example
class MainActivity : TauriActivity() import android.os.Bundle
import androidx.activity.enableEdgeToEdge
class MainActivity : TauriActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
}
}
@@ -4,7 +4,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle:8.5.1") classpath("com.android.tools.build:gradle:8.11.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
} }
} }
@@ -18,6 +18,6 @@ repositories {
dependencies { dependencies {
compileOnly(gradleApi()) compileOnly(gradleApi())
implementation("com.android.tools.build:gradle:8.5.1") implementation("com.android.tools.build:gradle:8.11.0")
} }
@@ -1,6 +1,6 @@
#Tue May 10 19:22:52 CST 2022 #Tue May 10 19:22:52 CST 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
@@ -1,116 +1,116 @@
{ {
"images": [ "images" : [
{ {
"size": "20x20", "size" : "20x20",
"idiom": "iphone", "idiom" : "iphone",
"filename": "AppIcon-20x20@2x.png", "filename" : "AppIcon-20x20@2x.png",
"scale": "2x" "scale" : "2x"
}, },
{ {
"size": "20x20", "size" : "20x20",
"idiom": "iphone", "idiom" : "iphone",
"filename": "AppIcon-20x20@3x.png", "filename" : "AppIcon-20x20@3x.png",
"scale": "3x" "scale" : "3x"
}, },
{ {
"size": "29x29", "size" : "29x29",
"idiom": "iphone", "idiom" : "iphone",
"filename": "AppIcon-29x29@2x-1.png", "filename" : "AppIcon-29x29@2x-1.png",
"scale": "2x" "scale" : "2x"
}, },
{ {
"size": "29x29", "size" : "29x29",
"idiom": "iphone", "idiom" : "iphone",
"filename": "AppIcon-29x29@3x.png", "filename" : "AppIcon-29x29@3x.png",
"scale": "3x" "scale" : "3x"
}, },
{ {
"size": "40x40", "size" : "40x40",
"idiom": "iphone", "idiom" : "iphone",
"filename": "AppIcon-40x40@2x.png", "filename" : "AppIcon-40x40@2x.png",
"scale": "2x" "scale" : "2x"
}, },
{ {
"size": "40x40", "size" : "40x40",
"idiom": "iphone", "idiom" : "iphone",
"filename": "AppIcon-40x40@3x.png", "filename" : "AppIcon-40x40@3x.png",
"scale": "3x" "scale" : "3x"
}, },
{ {
"size": "60x60", "size" : "60x60",
"idiom": "iphone", "idiom" : "iphone",
"filename": "AppIcon-60x60@2x.png", "filename" : "AppIcon-60x60@2x.png",
"scale": "2x" "scale" : "2x"
}, },
{ {
"size": "60x60", "size" : "60x60",
"idiom": "iphone", "idiom" : "iphone",
"filename": "AppIcon-60x60@3x.png", "filename" : "AppIcon-60x60@3x.png",
"scale": "3x" "scale" : "3x"
}, },
{ {
"size": "20x20", "size" : "20x20",
"idiom": "ipad", "idiom" : "ipad",
"filename": "AppIcon-20x20@1x.png", "filename" : "AppIcon-20x20@1x.png",
"scale": "1x" "scale" : "1x"
}, },
{ {
"size": "20x20", "size" : "20x20",
"idiom": "ipad", "idiom" : "ipad",
"filename": "AppIcon-20x20@2x-1.png", "filename" : "AppIcon-20x20@2x-1.png",
"scale": "2x" "scale" : "2x"
}, },
{ {
"size": "29x29", "size" : "29x29",
"idiom": "ipad", "idiom" : "ipad",
"filename": "AppIcon-29x29@1x.png", "filename" : "AppIcon-29x29@1x.png",
"scale": "1x" "scale" : "1x"
}, },
{ {
"size": "29x29", "size" : "29x29",
"idiom": "ipad", "idiom" : "ipad",
"filename": "AppIcon-29x29@2x.png", "filename" : "AppIcon-29x29@2x.png",
"scale": "2x" "scale" : "2x"
}, },
{ {
"size": "40x40", "size" : "40x40",
"idiom": "ipad", "idiom" : "ipad",
"filename": "AppIcon-40x40@1x.png", "filename" : "AppIcon-40x40@1x.png",
"scale": "1x" "scale" : "1x"
}, },
{ {
"size": "40x40", "size" : "40x40",
"idiom": "ipad", "idiom" : "ipad",
"filename": "AppIcon-40x40@2x-1.png", "filename" : "AppIcon-40x40@2x-1.png",
"scale": "2x" "scale" : "2x"
}, },
{ {
"size": "76x76", "size" : "76x76",
"idiom": "ipad", "idiom" : "ipad",
"filename": "AppIcon-76x76@1x.png", "filename" : "AppIcon-76x76@1x.png",
"scale": "1x" "scale" : "1x"
}, },
{ {
"size": "76x76", "size" : "76x76",
"idiom": "ipad", "idiom" : "ipad",
"filename": "AppIcon-76x76@2x.png", "filename" : "AppIcon-76x76@2x.png",
"scale": "2x" "scale" : "2x"
}, },
{ {
"size": "83.5x83.5", "size" : "83.5x83.5",
"idiom": "ipad", "idiom" : "ipad",
"filename": "AppIcon-83.5x83.5@2x.png", "filename" : "AppIcon-83.5x83.5@2x.png",
"scale": "2x" "scale" : "2x"
}, },
{ {
"size": "1024x1024", "size" : "1024x1024",
"idiom": "ios-marketing", "idiom" : "ios-marketing",
"filename": "AppIcon-512@2x.png", "filename" : "AppIcon-512@2x.png",
"scale": "1x" "scale" : "1x"
} }
], ],
"info": { "info" : {
"version": 1, "version" : 1,
"author": "xcode" "author" : "xcode"
} }
} }
@@ -1,6 +1,6 @@
{ {
"info": { "info" : {
"version": 1, "version" : 1,
"author": "xcode" "author" : "xcode"
} }
} }
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17150" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17150" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Y6W-OH-hqX">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17122"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17122"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
@@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 56; objectVersion = 63;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@@ -167,6 +167,8 @@
dependencies = ( dependencies = (
); );
name = "deep-link-example_iOS"; name = "deep-link-example_iOS";
packageProductDependencies = (
);
productName = "deep-link-example_iOS"; productName = "deep-link-example_iOS";
productReference = 1CAAFA750FD735A285DC1238 /* deep-link-example.app */; productReference = 1CAAFA750FD735A285DC1238 /* deep-link-example.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
@@ -189,6 +191,7 @@
en, en,
); );
mainGroup = 1DC58B1705AA3ECC6B887FE7; mainGroup = 1DC58B1705AA3ECC6B887FE7;
minimizedProjectReferenceProxies = 1;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
targets = ( targets = (
@@ -227,7 +230,6 @@
outputPaths = ( outputPaths = (
"$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a", "$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a",
"$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a", "$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a",
"$(SRCROOT)/Externals/arm64-sim/${CONFIGURATION}/libapp.a",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
@@ -314,18 +316,13 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ARCHS = ( ARCHS = arm64;
arm64,
"arm64-sim",
);
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements"; CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = "Q93MBH6S2F";
DEVELOPMENT_TEAM = Q93MBH6S2F;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64"; "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"\".\"", "\".\"",
@@ -335,13 +332,6 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
"LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = (
"$(inherited)",
"$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)",
"$(SDKROOT)/usr/lib/swift",
"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)",
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
);
"LIBRARY_SEARCH_PATHS[arch=arm64]" = ( "LIBRARY_SEARCH_PATHS[arch=arm64]" = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)", "$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)",
@@ -360,7 +350,7 @@
PRODUCT_NAME = "deep-link-example"; PRODUCT_NAME = "deep-link-example";
SDKROOT = iphoneos; SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VALID_ARCHS = "arm64 arm64-sim"; VALID_ARCHS = arm64;
}; };
name = debug; name = debug;
}; };
@@ -424,18 +414,13 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ARCHS = ( ARCHS = arm64;
arm64,
"arm64-sim",
);
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements"; CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements";
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = "Q93MBH6S2F";
DEVELOPMENT_TEAM = Q93MBH6S2F;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64"; "EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"\".\"", "\".\"",
@@ -445,13 +430,6 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
"LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = (
"$(inherited)",
"$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)",
"$(SDKROOT)/usr/lib/swift",
"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)",
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
);
"LIBRARY_SEARCH_PATHS[arch=arm64]" = ( "LIBRARY_SEARCH_PATHS[arch=arm64]" = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)", "$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)",
@@ -470,7 +448,7 @@
PRODUCT_NAME = "deep-link-example"; PRODUCT_NAME = "deep-link-example";
SDKROOT = iphoneos; SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VALID_ARCHS = "arm64 arm64-sim"; VALID_ARCHS = arm64;
}; };
name = release; name = release;
}; };
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.0.0</string> <string>0.1.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>0.1.0</string> <string>0.1.0</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
@@ -40,5 +40,16 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>taurideeplink</string>
</array>
<key>CFBundleURLName</key>
<string>taurideeplink</string>
</dict>
</array>
</dict> </dict>
</plist> </plist>
@@ -1,7 +1,3 @@
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
# SPDX-License-Identifier: Apache-2.0
# SPDX-License-Identifier: MIT
name: deep-link-example name: deep-link-example
options: options:
bundleIdPrefix: com.tauri.deep-link-example bundleIdPrefix: com.tauri.deep-link-example
@@ -16,7 +12,6 @@ settingGroups:
base: base:
PRODUCT_NAME: deep-link-example PRODUCT_NAME: deep-link-example
PRODUCT_BUNDLE_IDENTIFIER: com.tauri.deep-link-example PRODUCT_BUNDLE_IDENTIFIER: com.tauri.deep-link-example
DEVELOPMENT_TEAM: Q93MBH6S2F
targetTemplates: targetTemplates:
app: app:
type: application type: application
@@ -56,8 +51,8 @@ targets:
- UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight - UIInterfaceOrientationLandscapeRight
CFBundleShortVersionString: 0.0.0 CFBundleShortVersionString: 0.1.0
CFBundleVersion: 0.0.0 CFBundleVersion: '0.1.0'
entitlements: entitlements:
path: deep-link-example_iOS/deep-link-example_iOS.entitlements path: deep-link-example_iOS/deep-link-example_iOS.entitlements
scheme: scheme:
@@ -67,14 +62,12 @@ targets:
settings: settings:
base: base:
ENABLE_BITCODE: false ENABLE_BITCODE: false
ARCHS: [arm64, arm64-sim] ARCHS: [arm64]
VALID_ARCHS: arm64 arm64-sim VALID_ARCHS: arm64
LIBRARY_SEARCH_PATHS[arch=x86_64]: $(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME) LIBRARY_SEARCH_PATHS[arch=x86_64]: $(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
LIBRARY_SEARCH_PATHS[arch=arm64]: $(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME) LIBRARY_SEARCH_PATHS[arch=arm64]: $(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
LIBRARY_SEARCH_PATHS[arch=arm64-sim]: $(inherited) $(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: true ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: true
EXCLUDED_ARCHS[sdk=iphonesimulator*]: arm64 EXCLUDED_ARCHS[sdk=iphoneos*]: x86_64
EXCLUDED_ARCHS[sdk=iphoneos*]: arm64-sim x86_64
groups: [app] groups: [app]
dependencies: dependencies:
- framework: libapp.a - framework: libapp.a
@@ -93,4 +86,3 @@ targets:
outputFiles: outputFiles:
- $(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a - $(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a
- $(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a - $(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a
- $(SRCROOT)/Externals/arm64-sim/${CONFIGURATION}/libapp.a
@@ -35,6 +35,9 @@
}, },
{ {
"host": "tauri.app" "host": "tauri.app"
},
{
"scheme": ["taurideeplink"]
} }
], ],
"desktop": { "desktop": {
+5 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-deep-link", "name": "@tauri-apps/plugin-deep-link",
"version": "2.4.1", "version": "2.4.3",
"description": "Set your Tauri application as the default handler for an URL", "description": "Set your Tauri application as the default handler for an URL",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
@@ -25,6 +25,9 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
},
"devDependencies": {
"@tauri-apps/cli": "2.8.4"
} }
} }
+46 -12
View File
@@ -11,10 +11,8 @@ use tauri_utils::config::DeepLinkProtocol;
pub struct AssociatedDomain { pub struct AssociatedDomain {
#[serde(default = "default_schemes")] #[serde(default = "default_schemes")]
pub scheme: Vec<String>, pub scheme: Vec<String>,
#[serde(default, deserialize_with = "deserialize_associated_host")]
#[serde(deserialize_with = "deserialize_associated_host")] pub host: Option<String>, // Optional custom uri schemes dont NEED a host (may have one still), but required for https/http schemes
pub host: String,
#[serde(default)] #[serde(default)]
pub path: Vec<String>, pub path: Vec<String>,
#[serde(default, alias = "path-pattern", rename = "pathPattern")] #[serde(default, alias = "path-pattern", rename = "pathPattern")]
@@ -23,6 +21,41 @@ pub struct AssociatedDomain {
pub path_prefix: Vec<String>, pub path_prefix: Vec<String>,
#[serde(default, alias = "path-suffix", rename = "pathSuffix")] #[serde(default, alias = "path-suffix", rename = "pathSuffix")]
pub path_suffix: Vec<String>, pub path_suffix: Vec<String>,
#[serde(default, alias = "app-link", rename = "appLink")]
pub app_link: Option<bool>,
}
impl AssociatedDomain {
/// Returns true if the domain uses http or https scheme.
pub fn is_web_link(&self) -> bool {
self.scheme.iter().any(|s| s == "https" || s == "http")
}
/// Returns true if the domain uses http or https scheme and has proper host configuration.
pub fn is_app_link(&self) -> bool {
self.app_link
.unwrap_or_else(|| self.is_web_link() && self.host.is_some())
}
pub fn validate(&self) -> Result<(), String> {
// Rule 1: All web links require a host.
if self.is_web_link() && self.host.is_none() {
return Err("Web link requires a host".into());
}
// Rule 2: If it's an App Link, ensure http(s) and host.
if self.is_app_link() {
if !self.is_web_link() {
return Err("AppLink must be a valid web link (https/http + host)".into());
}
if self.scheme.iter().any(|s| s == "http") && !self.scheme.iter().any(|s| s == "https")
{
eprintln!("Warning: AppLink uses only 'http' — allowed on Android but not secure for production.");
}
}
Ok(())
}
} }
// TODO: Consider removing this in v3 // TODO: Consider removing this in v3
@@ -30,18 +63,19 @@ fn default_schemes() -> Vec<String> {
vec!["https".to_string(), "http".to_string()] vec!["https".to_string(), "http".to_string()]
} }
fn deserialize_associated_host<'de, D>(deserializer: D) -> Result<String, D::Error> fn deserialize_associated_host<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
let host = String::deserialize(deserializer)?; let opt = Option::<String>::deserialize(deserializer)?;
if let Some((scheme, _host)) = host.split_once("://") { if let Some(ref host) = opt {
Err(serde::de::Error::custom(format!( if let Some((scheme, _)) = host.split_once("://") {
"host `{host}` cannot start with a scheme, please remove the `{scheme}://` prefix" return Err(serde::de::Error::custom(format!(
))) "host `{host}` cannot start with a scheme, please remove the `{scheme}://` prefix"
} else { )));
Ok(host) }
} }
Ok(opt)
} }
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone)]
+10
View File
@@ -28,6 +28,16 @@ pub enum Error {
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError), PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
} }
// TODO(v3): change this into an error in v3,
// see <https://github.com/tauri-apps/plugins-workspace/pull/2970#issuecomment-3244660138>.
#[inline]
#[cfg(target_os = "linux")]
pub(crate) fn inspect_command_error<'a>(command: &'a str) -> impl Fn(&std::io::Error) + 'a {
move |e| {
tracing::error!("Failed to run OS command `{command}`: {e}");
}
}
impl Serialize for Error { impl Serialize for Error {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where where
+30 -6
View File
@@ -254,6 +254,7 @@ mod imp {
/// ///
/// ## Platform-specific: /// ## Platform-specific:
/// ///
/// - **Linux**: Needs the `xdg-mime` and `update-desktop-database` commands available on the system.
/// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`). /// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`).
pub fn register<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<()> { pub fn register<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<()> {
#[cfg(windows)] #[cfg(windows)]
@@ -292,6 +293,7 @@ mod imp {
.unwrap_or_else(|| bin.into_os_string()) .unwrap_or_else(|| bin.into_os_string())
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
let qualified_exec = format!("{} %u", exec);
let target = self.app.path().data_dir()?.join("applications"); let target = self.app.path().data_dir()?.join("applications");
@@ -303,12 +305,28 @@ mod imp {
if let Ok(mut desktop_file) = ini::Ini::load_from_file(&target_file) { if let Ok(mut desktop_file) = ini::Ini::load_from_file(&target_file) {
if let Some(section) = desktop_file.section_mut(Some("Desktop Entry")) { if let Some(section) = desktop_file.section_mut(Some("Desktop Entry")) {
// it's ok to remove it - we only write to the file if it's missing
// and in that case we include old_mimes
let old_mimes = section.remove("MimeType").unwrap_or_default(); let old_mimes = section.remove("MimeType").unwrap_or_default();
let mut change = false;
// if the mime type is not present, append it to the list
if !old_mimes.split(';').any(|mime| mime == mime_type) { if !old_mimes.split(';').any(|mime| mime == mime_type) {
section.append("MimeType", format!("{mime_type};{old_mimes}")); section.append("MimeType", format!("{mime_type};{old_mimes}"));
change = true;
} else {
section.insert("MimeType".to_string(), old_mimes);
}
// if the exec command doesnt match, update to the new one
let old_exec = section.remove("Exec").unwrap_or_default();
if old_exec != qualified_exec {
section.append("Exec", qualified_exec);
change = true;
} else {
section.insert("Exec".to_string(), old_exec.to_string());
}
// if any property has changed, rewrite the .desktop file
if change {
desktop_file.write_to_file(&target_file)?; desktop_file.write_to_file(&target_file)?;
} }
} }
@@ -323,7 +341,7 @@ mod imp {
.product_name .product_name
.clone() .clone()
.unwrap_or_else(|| file_name.clone()), .unwrap_or_else(|| file_name.clone()),
exec = exec, qualified_exec = qualified_exec,
mime_type = mime_type mime_type = mime_type
) )
.as_bytes(), .as_bytes(),
@@ -332,11 +350,15 @@ mod imp {
Command::new("update-desktop-database") Command::new("update-desktop-database")
.arg(target) .arg(target)
.status()?; .status()
.inspect_err(crate::error::inspect_command_error(
"update-desktop-database",
))?;
Command::new("xdg-mime") Command::new("xdg-mime")
.args(["default", &file_name, mime_type.as_str()]) .args(["default", &file_name, mime_type.as_str()])
.status()?; .status()
.inspect_err(crate::error::inspect_command_error("xdg-mime"))?;
Ok(()) Ok(())
} }
@@ -405,6 +427,7 @@ mod imp {
/// ///
/// ## Platform-specific: /// ## Platform-specific:
/// ///
/// - **Linux**: Needs the `xdg-mime` command available on the system.
/// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`). /// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`).
pub fn is_registered<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<bool> { pub fn is_registered<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<bool> {
#[cfg(windows)] #[cfg(windows)]
@@ -439,7 +462,8 @@ mod imp {
"default", "default",
&format!("x-scheme-handler/{}", _protocol.as_ref()), &format!("x-scheme-handler/{}", _protocol.as_ref()),
]) ])
.output()?; .output()
.inspect_err(crate::error::inspect_command_error("xdg-mime"))?;
Ok(String::from_utf8_lossy(&output.stdout).contains(&file_name)) Ok(String::from_utf8_lossy(&output.stdout).contains(&file_name))
} }
+2 -2
View File
@@ -1,7 +1,7 @@
[Desktop Entry] [Desktop Entry]
Type=Application Type=Application
Name={name} Name={name}
Exec={exec} %u Exec={qualified_exec}
Terminal=false Terminal=false
MimeType={mime_type} MimeType={mime_type}
NoDisplay=true NoDisplay=true
+10
View File
@@ -1,5 +1,15 @@
# Changelog # Changelog
## \[2.4.0]
- [`509eba8d`](https://github.com/tauri-apps/plugins-workspace/commit/509eba8d441c4f6ecf0af77b572cb2afd69a752d) ([#2641](https://github.com/tauri-apps/plugins-workspace/pull/2641) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Add support for showing a message dialog with 3 buttons.
## \[2.3.3]
### Dependencies
- Upgraded to `fs-js@2.4.2`
## \[2.3.2] ## \[2.3.2]
- [`af08c66f`](https://github.com/tauri-apps/plugins-workspace/commit/af08c66faafe0dffc4b0a80aef030cd3f0f89a9c) ([#2871](https://github.com/tauri-apps/plugins-workspace/pull/2871) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused the file picker not to open on Android when extension filters were set. - [`af08c66f`](https://github.com/tauri-apps/plugins-workspace/commit/af08c66faafe0dffc4b0a80aef030cd3f0f89a9c) ([#2871](https://github.com/tauri-apps/plugins-workspace/pull/2871) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) Fixed an issue that caused the file picker not to open on Android when extension filters were set.
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-dialog" name = "tauri-plugin-dialog"
version = "2.3.2" version = "2.4.0"
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application." description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -34,7 +34,7 @@ tauri = { workspace = true }
log = { workspace = true } log = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
url = { workspace = true } url = { workspace = true }
tauri-plugin-fs = { path = "../fs", version = "2.4.1" } tauri-plugin-fs = { path = "../fs", version = "2.4.2" }
[target.'cfg(target_os = "ios")'.dependencies] [target.'cfg(target_os = "ios")'.dependencies]
tauri = { workspace = true, features = ["wry"] } tauri = { workspace = true, features = ["wry"] }
@@ -38,6 +38,7 @@ class MessageOptions {
var title: String? = null var title: String? = null
lateinit var message: String lateinit var message: String
var okButtonLabel: String? = null var okButtonLabel: String? = null
var noButtonLabel: String? = null
var cancelButtonLabel: String? = null var cancelButtonLabel: String? = null
} }
@@ -139,9 +140,8 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
return return
} }
val handler = { cancelled: Boolean, value: Boolean -> val handler = { value: String ->
val ret = JSObject() val ret = JSObject()
ret.put("cancelled", cancelled)
ret.put("value", value) ret.put("value", value)
invoke.resolve(ret) invoke.resolve(ret)
} }
@@ -153,24 +153,34 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
if (args.title != null) { if (args.title != null) {
builder.setTitle(args.title) builder.setTitle(args.title)
} }
val okButtonLabel = args.okButtonLabel ?: "Ok"
builder builder
.setMessage(args.message) .setMessage(args.message)
.setPositiveButton( .setPositiveButton(okButtonLabel) { dialog, _ ->
args.okButtonLabel ?: "OK"
) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
handler(false, true) handler(okButtonLabel)
} }
.setOnCancelListener { dialog -> .setOnCancelListener { dialog ->
dialog.dismiss() dialog.dismiss()
handler(true, false) handler(args.cancelButtonLabel ?: "Cancel")
} }
if (args.noButtonLabel != null) {
builder.setNeutralButton(args.noButtonLabel) { dialog, _ ->
dialog.dismiss()
handler(args.noButtonLabel!!)
}
}
if (args.cancelButtonLabel != null) { if (args.cancelButtonLabel != null) {
builder.setNegativeButton( args.cancelButtonLabel) { dialog, _ -> builder.setNegativeButton( args.cancelButtonLabel) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
handler(false, false) handler(args.cancelButtonLabel!!)
} }
} }
val dialog = builder.create() val dialog = builder.create()
dialog.show() dialog.show()
} }
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_DIALOG__=function(t){"use strict";async function n(t,n={},e){return window.__TAURI_INTERNALS__.invoke(t,n,e)}return"function"==typeof SuppressedError&&SuppressedError,t.ask=async function(t,e){const i="string"==typeof e?{title:e}:e;return await n("plugin:dialog|ask",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,yesButtonLabel:i?.okLabel?.toString(),noButtonLabel:i?.cancelLabel?.toString()})},t.confirm=async function(t,e){const i="string"==typeof e?{title:e}:e;return await n("plugin:dialog|confirm",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString(),cancelButtonLabel:i?.cancelLabel?.toString()})},t.message=async function(t,e){const i="string"==typeof e?{title:e}:e;await n("plugin:dialog|message",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString()})},t.open=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|open",{options:t})},t.save=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|save",{options:t})},t}({});Object.defineProperty(window.__TAURI__,"dialog",{value:__TAURI_PLUGIN_DIALOG__})} if("__TAURI__"in window){var __TAURI_PLUGIN_DIALOG__=function(t){"use strict";async function n(t,n={},e){return window.__TAURI_INTERNALS__.invoke(t,n,e)}function e(t){if(void 0!==t)return"string"==typeof t?t:"ok"in t&&"cancel"in t?{OkCancelCustom:[t.ok,t.cancel]}:"yes"in t&&"no"in t&&"cancel"in t?{YesNoCancelCustom:[t.yes,t.no,t.cancel]}:"ok"in t?{OkCustom:t.ok}:void 0}return"function"==typeof SuppressedError&&SuppressedError,t.ask=async function(t,e){const o="string"==typeof e?{title:e}:e;return await n("plugin:dialog|ask",{message:t.toString(),title:o?.title?.toString(),kind:o?.kind,yesButtonLabel:o?.okLabel?.toString(),noButtonLabel:o?.cancelLabel?.toString()})},t.confirm=async function(t,e){const o="string"==typeof e?{title:e}:e;return await n("plugin:dialog|confirm",{message:t.toString(),title:o?.title?.toString(),kind:o?.kind,okButtonLabel:o?.okLabel?.toString(),cancelButtonLabel:o?.cancelLabel?.toString()})},t.message=async function(t,o){const i="string"==typeof o?{title:o}:o;return n("plugin:dialog|message",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString(),buttons:e(i?.buttons)})},t.open=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|open",{options:t})},t.save=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|save",{options:t})},t}({});Object.defineProperty(window.__TAURI__,"dialog",{value:__TAURI_PLUGIN_DIALOG__})}
+140 -4
View File
@@ -77,6 +77,80 @@ interface SaveDialogOptions {
canCreateDirectories?: boolean canCreateDirectories?: boolean
} }
/**
* Default buttons for a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogDefaultButtons =
| 'Ok'
| 'OkCancel'
| 'YesNo'
| 'YesNoCancel'
/** All possible button keys. */
type ButtonKey = 'ok' | 'cancel' | 'yes' | 'no'
/** Ban everything except a set of keys. */
type BanExcept<Allowed extends ButtonKey> = Partial<
Record<Exclude<ButtonKey, Allowed>, never>
>
/**
* The Yes, No and Cancel buttons of a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogButtonsYesNoCancel = {
/** The Yes button. */
yes: string
/** The No button. */
no: string
/** The Cancel button. */
cancel: string
} & BanExcept<'yes' | 'no' | 'cancel'>
/**
* The Ok and Cancel buttons of a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogButtonsOkCancel = {
/** The Ok button. */
ok: string
/** The Cancel button. */
cancel: string
} & BanExcept<'ok' | 'cancel'>
/**
* The Ok button of a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogButtonsOk = {
/** The Ok button. */
ok: string
} & BanExcept<'ok'>
/**
* Custom buttons for a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogCustomButtons =
| MessageDialogButtonsYesNoCancel
| MessageDialogButtonsOkCancel
| MessageDialogButtonsOk
/**
* The buttons of a message dialog.
*
* @since 2.4.0
*/
export type MessageDialogButtons =
| MessageDialogDefaultButtons
| MessageDialogCustomButtons
/** /**
* @since 2.0.0 * @since 2.0.0
*/ */
@@ -85,8 +159,58 @@ interface MessageDialogOptions {
title?: string title?: string
/** The kind of the dialog. Defaults to `info`. */ /** The kind of the dialog. Defaults to `info`. */
kind?: 'info' | 'warning' | 'error' kind?: 'info' | 'warning' | 'error'
/** The label of the confirm button. */ /**
* The label of the Ok button.
*
* @deprecated Use {@linkcode MessageDialogOptions.buttons} instead.
*/
okLabel?: string okLabel?: string
/**
* The buttons of the dialog.
*
* @example
*
* ```ts
* // Use system default buttons texts
* await message('Hello World!', { buttons: 'Ok' })
* await message('Hello World!', { buttons: 'OkCancel' })
*
* // Or with custom button texts
* await message('Hello World!', { buttons: { ok: 'Yes!' } })
* await message('Take on the task?', {
* buttons: { ok: 'Accept', cancel: 'Cancel' }
* })
* await message('Show the file content?', {
* buttons: { yes: 'Show content', no: 'Show in folder', cancel: 'Cancel' }
* })
* ```
*
* @since 2.4.0
*/
buttons?: MessageDialogButtons
}
/**
* Internal function to convert the buttons to the Rust type.
*/
function buttonsToRust(buttons: MessageDialogButtons | undefined) {
if (buttons === undefined) {
return undefined
}
if (typeof buttons === 'string') {
return buttons
} else if ('ok' in buttons && 'cancel' in buttons) {
return { OkCancelCustom: [buttons.ok, buttons.cancel] }
} else if ('yes' in buttons && 'no' in buttons && 'cancel' in buttons) {
return {
YesNoCancelCustom: [buttons.yes, buttons.no, buttons.cancel]
}
} else if ('ok' in buttons) {
return { OkCustom: buttons.ok }
}
return undefined
} }
interface ConfirmDialogOptions { interface ConfirmDialogOptions {
@@ -202,6 +326,16 @@ async function save(options: SaveDialogOptions = {}): Promise<string | null> {
return await invoke('plugin:dialog|save', { options }) return await invoke('plugin:dialog|save', { options })
} }
/**
* The result of a message dialog.
*
* The result is a string if the dialog has custom buttons,
* otherwise it is one of the default buttons.
*
* @since 2.4.0
*/
export type MessageDialogResult = 'Yes' | 'No' | 'Ok' | 'Cancel' | (string & {})
/** /**
* Shows a message dialog with an `Ok` button. * Shows a message dialog with an `Ok` button.
* @example * @example
@@ -222,13 +356,15 @@ async function save(options: SaveDialogOptions = {}): Promise<string | null> {
async function message( async function message(
message: string, message: string,
options?: string | MessageDialogOptions options?: string | MessageDialogOptions
): Promise<void> { ): Promise<MessageDialogResult> {
const opts = typeof options === 'string' ? { title: options } : options const opts = typeof options === 'string' ? { title: options } : options
await invoke('plugin:dialog|message', {
return invoke<MessageDialogResult>('plugin:dialog|message', {
message: message.toString(), message: message.toString(),
title: opts?.title?.toString(), title: opts?.title?.toString(),
kind: opts?.kind, kind: opts?.kind,
okButtonLabel: opts?.okLabel?.toString() okButtonLabel: opts?.okLabel?.toString(),
buttons: buttonsToRust(opts?.buttons)
}) })
} }
+22 -19
View File
@@ -20,6 +20,7 @@ struct MessageDialogOptions: Decodable {
var title: String? var title: String?
let message: String let message: String
var okButtonLabel: String? var okButtonLabel: String?
var noButtonLabel: String?
var cancelButtonLabel: String? var cancelButtonLabel: String?
} }
@@ -200,36 +201,38 @@ class DialogPlugin: Plugin {
let alert = UIAlertController( let alert = UIAlertController(
title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert) title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert)
let cancelButtonLabel = args.cancelButtonLabel ?? "" if let cancelButtonLabel = args.cancelButtonLabel {
if !cancelButtonLabel.isEmpty {
alert.addAction( alert.addAction(
UIAlertAction( UIAlertAction(
title: cancelButtonLabel, style: UIAlertAction.Style.default, title: cancelButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in handler: { (_) -> Void in
Logger.error("cancel") invoke.resolve(["value": cancelButtonLabel])
}
invoke.resolve([ )
"value": false, )
"cancelled": false,
])
}))
} }
let okButtonLabel = args.okButtonLabel ?? (cancelButtonLabel.isEmpty ? "OK" : "") if let noButtonLabel = args.noButtonLabel {
if !okButtonLabel.isEmpty {
alert.addAction( alert.addAction(
UIAlertAction( UIAlertAction(
title: okButtonLabel, style: UIAlertAction.Style.default, title: noButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in handler: { (_) -> Void in
Logger.error("ok") invoke.resolve(["value": noButtonLabel])
}
invoke.resolve([ )
"value": true, )
"cancelled": false,
])
}))
} }
let okButtonLabel = args.okButtonLabel ?? "Ok"
alert.addAction(
UIAlertAction(
title: okButtonLabel, style: UIAlertAction.Style.default,
handler: { (_) -> Void in
invoke.resolve(["value": okButtonLabel])
}
)
)
manager.viewController?.present(alert, animated: true, completion: nil) manager.viewController?.present(alert, animated: true, completion: nil)
} }
} }
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-dialog", "name": "@tauri-apps/plugin-dialog",
"version": "2.3.2", "version": "2.4.0",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+21 -21
View File
@@ -9,8 +9,8 @@ use tauri::{command, Manager, Runtime, State, Window};
use tauri_plugin_fs::FsExt; use tauri_plugin_fs::FsExt;
use crate::{ use crate::{
Dialog, FileDialogBuilder, FilePath, MessageDialogButtons, MessageDialogKind, Result, CANCEL, Dialog, FileDialogBuilder, FilePath, MessageDialogBuilder, MessageDialogButtons,
NO, OK, YES, MessageDialogKind, MessageDialogResult, Result, CANCEL, NO, OK, YES,
}; };
#[derive(Serialize)] #[derive(Serialize)]
@@ -248,7 +248,7 @@ fn message_dialog<R: Runtime>(
message: String, message: String,
kind: Option<MessageDialogKind>, kind: Option<MessageDialogKind>,
buttons: MessageDialogButtons, buttons: MessageDialogButtons,
) -> bool { ) -> MessageDialogBuilder<R> {
let mut builder = dialog.message(message); let mut builder = dialog.message(message);
builder = builder.buttons(buttons); builder = builder.buttons(buttons);
@@ -266,7 +266,7 @@ fn message_dialog<R: Runtime>(
builder = builder.kind(kind); builder = builder.kind(kind);
} }
builder.blocking_show() builder
} }
#[command] #[command]
@@ -277,19 +277,15 @@ pub(crate) async fn message<R: Runtime>(
message: String, message: String,
kind: Option<MessageDialogKind>, kind: Option<MessageDialogKind>,
ok_button_label: Option<String>, ok_button_label: Option<String>,
) -> Result<bool> { buttons: Option<MessageDialogButtons>,
Ok(message_dialog( ) -> Result<MessageDialogResult> {
window, let buttons = buttons.unwrap_or(if let Some(ok_button_label) = ok_button_label {
dialog, MessageDialogButtons::OkCustom(ok_button_label)
title, } else {
message, MessageDialogButtons::Ok
kind, });
if let Some(ok_button_label) = ok_button_label {
MessageDialogButtons::OkCustom(ok_button_label) Ok(message_dialog(window, dialog, title, message, kind, buttons).blocking_show_with_result())
} else {
MessageDialogButtons::Ok
},
))
} }
#[command] #[command]
@@ -302,7 +298,7 @@ pub(crate) async fn ask<R: Runtime>(
yes_button_label: Option<String>, yes_button_label: Option<String>,
no_button_label: Option<String>, no_button_label: Option<String>,
) -> Result<bool> { ) -> Result<bool> {
Ok(message_dialog( let dialog = message_dialog(
window, window,
dialog, dialog,
title, title,
@@ -318,7 +314,9 @@ pub(crate) async fn ask<R: Runtime>(
} else { } else {
MessageDialogButtons::YesNo MessageDialogButtons::YesNo
}, },
)) );
Ok(dialog.blocking_show())
} }
#[command] #[command]
@@ -331,7 +329,7 @@ pub(crate) async fn confirm<R: Runtime>(
ok_button_label: Option<String>, ok_button_label: Option<String>,
cancel_button_label: Option<String>, cancel_button_label: Option<String>,
) -> Result<bool> { ) -> Result<bool> {
Ok(message_dialog( let dialog = message_dialog(
window, window,
dialog, dialog,
title, title,
@@ -347,5 +345,7 @@ pub(crate) async fn confirm<R: Runtime>(
} else { } else {
MessageDialogButtons::OkCancel MessageDialogButtons::OkCancel
}, },
)) );
Ok(dialog.blocking_show())
} }
+40 -18
View File
@@ -13,7 +13,7 @@ use rfd::{AsyncFileDialog, AsyncMessageDialog};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use tauri::{plugin::PluginApi, AppHandle, Runtime}; use tauri::{plugin::PluginApi, AppHandle, Runtime};
use crate::{models::*, FileDialogBuilder, FilePath, MessageDialogBuilder, OK}; use crate::{models::*, FileDialogBuilder, FilePath, MessageDialogBuilder};
pub fn init<R: Runtime, C: DeserializeOwned>( pub fn init<R: Runtime, C: DeserializeOwned>(
app: &AppHandle<R>, app: &AppHandle<R>,
@@ -115,6 +115,10 @@ impl From<MessageDialogButtons> for rfd::MessageButtons {
MessageDialogButtons::YesNo => Self::YesNo, MessageDialogButtons::YesNo => Self::YesNo,
MessageDialogButtons::OkCustom(ok) => Self::OkCustom(ok), MessageDialogButtons::OkCustom(ok) => Self::OkCustom(ok),
MessageDialogButtons::OkCancelCustom(ok, cancel) => Self::OkCancelCustom(ok, cancel), MessageDialogButtons::OkCancelCustom(ok, cancel) => Self::OkCancelCustom(ok, cancel),
MessageDialogButtons::YesNoCancel => Self::YesNoCancel,
MessageDialogButtons::YesNoCancelCustom(yes, no, cancel) => {
Self::YesNoCancelCustom(yes, no, cancel)
}
} }
} }
} }
@@ -208,28 +212,46 @@ pub fn save_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
} }
/// Shows a message dialog /// Shows a message dialog
pub fn show_message_dialog<R: Runtime, F: FnOnce(bool) + Send + 'static>( pub fn show_message_dialog<R: Runtime, F: FnOnce(MessageDialogResult) + Send + 'static>(
dialog: MessageDialogBuilder<R>, dialog: MessageDialogBuilder<R>,
f: F, callback: F,
) { ) {
use rfd::MessageDialogResult; let f = move |res: rfd::MessageDialogResult| callback(res.into());
let ok_label = match &dialog.buttons {
MessageDialogButtons::OkCustom(ok) => Some(ok.clone()),
MessageDialogButtons::OkCancelCustom(ok, _) => Some(ok.clone()),
_ => None,
};
let f = move |res| {
f(match res {
MessageDialogResult::Ok | MessageDialogResult::Yes => true,
MessageDialogResult::Custom(s) => ok_label.map_or(s == OK, |ok_label| ok_label == s),
_ => false,
});
};
let handle = dialog.dialog.app_handle().to_owned(); let handle = dialog.dialog.app_handle().to_owned();
let _ = handle.run_on_main_thread(move || { let _ = handle.run_on_main_thread(move || {
let buttons = dialog.buttons.clone();
let dialog = AsyncMessageDialog::from(dialog).show(); let dialog = AsyncMessageDialog::from(dialog).show();
std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog))); std::thread::spawn(move || {
let result = tauri::async_runtime::block_on(dialog);
// on Linux rfd does not return rfd::MessageDialogResult::Custom, so we must map manually
let result = match (result, buttons) {
(rfd::MessageDialogResult::Ok, MessageDialogButtons::OkCustom(s)) => {
rfd::MessageDialogResult::Custom(s)
}
(
rfd::MessageDialogResult::Ok,
MessageDialogButtons::OkCancelCustom(ok, _cancel),
) => rfd::MessageDialogResult::Custom(ok),
(
rfd::MessageDialogResult::Cancel,
MessageDialogButtons::OkCancelCustom(_ok, cancel),
) => rfd::MessageDialogResult::Custom(cancel),
(
rfd::MessageDialogResult::Yes,
MessageDialogButtons::YesNoCancelCustom(yes, _no, _cancel),
) => rfd::MessageDialogResult::Custom(yes),
(
rfd::MessageDialogResult::No,
MessageDialogButtons::YesNoCancelCustom(_yes, no, _cancel),
) => rfd::MessageDialogResult::Custom(no),
(
rfd::MessageDialogResult::Cancel,
MessageDialogButtons::YesNoCancelCustom(_yes, _no, cancel),
) => rfd::MessageDialogResult::Custom(cancel),
(result, _) => result,
};
f(result);
});
}); });
} }
+51 -6
View File
@@ -216,6 +216,7 @@ pub(crate) struct MessageDialogPayload<'a> {
message: &'a String, message: &'a String,
kind: &'a MessageDialogKind, kind: &'a MessageDialogKind,
ok_button_label: Option<&'a str>, ok_button_label: Option<&'a str>,
no_button_label: Option<&'a str>,
cancel_button_label: Option<&'a str>, cancel_button_label: Option<&'a str>,
} }
@@ -238,13 +239,17 @@ impl<R: Runtime> MessageDialogBuilder<R> {
#[cfg(mobile)] #[cfg(mobile)]
pub(crate) fn payload(&self) -> MessageDialogPayload<'_> { pub(crate) fn payload(&self) -> MessageDialogPayload<'_> {
let (ok_button_label, cancel_button_label) = match &self.buttons { let (ok_button_label, no_button_label, cancel_button_label) = match &self.buttons {
MessageDialogButtons::Ok => (Some(OK), None), MessageDialogButtons::Ok => (Some(OK), None, None),
MessageDialogButtons::OkCancel => (Some(OK), Some(CANCEL)), MessageDialogButtons::OkCancel => (Some(OK), None, Some(CANCEL)),
MessageDialogButtons::YesNo => (Some(YES), Some(NO)), MessageDialogButtons::YesNo => (Some(YES), Some(NO), None),
MessageDialogButtons::OkCustom(ok) => (Some(ok.as_str()), Some(CANCEL)), MessageDialogButtons::YesNoCancel => (Some(YES), Some(NO), Some(CANCEL)),
MessageDialogButtons::OkCustom(ok) => (Some(ok.as_str()), None, None),
MessageDialogButtons::OkCancelCustom(ok, cancel) => { MessageDialogButtons::OkCancelCustom(ok, cancel) => {
(Some(ok.as_str()), Some(cancel.as_str())) (Some(ok.as_str()), None, Some(cancel.as_str()))
}
MessageDialogButtons::YesNoCancelCustom(yes, no, cancel) => {
(Some(yes.as_str()), Some(no.as_str()), Some(cancel.as_str()))
} }
}; };
MessageDialogPayload { MessageDialogPayload {
@@ -252,6 +257,7 @@ impl<R: Runtime> MessageDialogBuilder<R> {
message: &self.message, message: &self.message,
kind: &self.kind, kind: &self.kind,
ok_button_label, ok_button_label,
no_button_label,
cancel_button_label, cancel_button_label,
} }
} }
@@ -295,16 +301,55 @@ impl<R: Runtime> MessageDialogBuilder<R> {
} }
/// Shows a message dialog /// Shows a message dialog
///
/// Returns `true` if the user pressed the OK/Yes button,
pub fn show<F: FnOnce(bool) + Send + 'static>(self, f: F) { pub fn show<F: FnOnce(bool) + Send + 'static>(self, f: F) {
let ok_label = match &self.buttons {
MessageDialogButtons::OkCustom(ok) => Some(ok.clone()),
MessageDialogButtons::OkCancelCustom(ok, _) => Some(ok.clone()),
MessageDialogButtons::YesNoCancelCustom(yes, _, _) => Some(yes.clone()),
_ => None,
};
show_message_dialog(self, move |res| {
let sucess = match res {
MessageDialogResult::Ok | MessageDialogResult::Yes => true,
MessageDialogResult::Custom(s) => {
ok_label.map_or(s == OK, |ok_label| ok_label == s)
}
_ => false,
};
f(sucess)
})
}
/// Shows a message dialog and returns the button that was pressed.
///
/// Returns a [`MessageDialogResult`] enum that indicates which button was pressed.
pub fn show_with_result<F: FnOnce(MessageDialogResult) + Send + 'static>(self, f: F) {
show_message_dialog(self, f) show_message_dialog(self, f)
} }
/// Shows a message dialog. /// Shows a message dialog.
///
/// Returns `true` if the user pressed the OK/Yes button,
///
/// This is a blocking operation, /// This is a blocking operation,
/// and should *NOT* be used when running on the main thread context. /// and should *NOT* be used when running on the main thread context.
pub fn blocking_show(self) -> bool { pub fn blocking_show(self) -> bool {
blocking_fn!(self, show) blocking_fn!(self, show)
} }
/// Shows a message dialog and returns the button that was pressed.
///
/// Returns a [`MessageDialogResult`] enum that indicates which button was pressed.
///
/// This is a blocking operation,
/// and should *NOT* be used when running on the main thread context.
pub fn blocking_show_with_result(self) -> MessageDialogResult {
blocking_fn!(self, show_with_result)
}
} }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub(crate) struct Filter { pub(crate) struct Filter {
+6 -6
View File
@@ -8,7 +8,7 @@ use tauri::{
AppHandle, Runtime, AppHandle, Runtime,
}; };
use crate::{FileDialogBuilder, FilePath, MessageDialogBuilder}; use crate::{FileDialogBuilder, FilePath, MessageDialogBuilder, MessageDialogResult};
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
const PLUGIN_IDENTIFIER: &str = "app.tauri.dialog"; const PLUGIN_IDENTIFIER: &str = "app.tauri.dialog";
@@ -107,13 +107,11 @@ pub fn save_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct ShowMessageDialogResponse { struct ShowMessageDialogResponse {
#[allow(dead_code)] value: String,
cancelled: bool,
value: bool,
} }
/// Shows a message dialog /// Shows a message dialog
pub fn show_message_dialog<R: Runtime, F: FnOnce(bool) + Send + 'static>( pub fn show_message_dialog<R: Runtime, F: FnOnce(MessageDialogResult) + Send + 'static>(
dialog: MessageDialogBuilder<R>, dialog: MessageDialogBuilder<R>,
f: F, f: F,
) { ) {
@@ -122,6 +120,8 @@ pub fn show_message_dialog<R: Runtime, F: FnOnce(bool) + Send + 'static>(
.dialog .dialog
.0 .0
.run_mobile_plugin::<ShowMessageDialogResponse>("showMessageDialog", dialog.payload()); .run_mobile_plugin::<ShowMessageDialogResponse>("showMessageDialog", dialog.payload());
f(res.map(|r| r.value).unwrap_or_default())
let res = res.map(|res| res.value.into());
f(res.unwrap_or_default())
}); });
} }
+42 -1
View File
@@ -52,7 +52,7 @@ impl Serialize for MessageDialogKind {
/// Set of button that will be displayed on the dialog /// Set of button that will be displayed on the dialog
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub enum MessageDialogButtons { pub enum MessageDialogButtons {
#[default] #[default]
/// A single `Ok` button with OS default dialog text /// A single `Ok` button with OS default dialog text
@@ -61,8 +61,49 @@ pub enum MessageDialogButtons {
OkCancel, OkCancel,
/// 2 buttons `Yes` and `No` with OS default dialog texts /// 2 buttons `Yes` and `No` with OS default dialog texts
YesNo, YesNo,
/// 3 buttons `Yes`, `No` and `Cancel` with OS default dialog texts
YesNoCancel,
/// A single `Ok` button with custom text /// A single `Ok` button with custom text
OkCustom(String), OkCustom(String),
/// 2 buttons `Ok` and `Cancel` with custom texts /// 2 buttons `Ok` and `Cancel` with custom texts
OkCancelCustom(String, String), OkCancelCustom(String, String),
/// 3 buttons `Yes`, `No` and `Cancel` with custom texts
YesNoCancelCustom(String, String, String),
}
/// Result of a message dialog
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum MessageDialogResult {
Yes,
No,
Ok,
#[default]
Cancel,
#[serde(untagged)]
Custom(String),
}
#[cfg(desktop)]
impl From<rfd::MessageDialogResult> for MessageDialogResult {
fn from(result: rfd::MessageDialogResult) -> Self {
match result {
rfd::MessageDialogResult::Yes => Self::Yes,
rfd::MessageDialogResult::No => Self::No,
rfd::MessageDialogResult::Ok => Self::Ok,
rfd::MessageDialogResult::Cancel => Self::Cancel,
rfd::MessageDialogResult::Custom(s) => Self::Custom(s),
}
}
}
impl From<String> for MessageDialogResult {
fn from(value: String) -> Self {
match value.as_str() {
"Yes" => Self::Yes,
"No" => Self::No,
"Ok" => Self::Ok,
"Cancel" => Self::Cancel,
_ => Self::Custom(value),
}
}
} }
+5
View File
@@ -1,5 +1,10 @@
# Changelog # Changelog
## \[2.4.2]
- [`4eb36b0f`](https://github.com/tauri-apps/plugins-workspace/commit/4eb36b0ff57acb0bb1b911c583efa3bf2f56aa32) ([#2907](https://github.com/tauri-apps/plugins-workspace/pull/2907) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fixed calling `writeFile` with `data: ReadableStream` throws `Invalid argument`
- [`515182a1`](https://github.com/tauri-apps/plugins-workspace/commit/515182a179d4439079b2b7f6927555ba5ab0b035) ([#2915](https://github.com/tauri-apps/plugins-workspace/pull/2915) by [@samhinshaw](https://github.com/tauri-apps/plugins-workspace/../../samhinshaw)) `readFile` now returns a more specific type `Promise<Uint8Array<ArrayBuffer>>` instead of the default `Promise<Uint8Array<ArrayBufferLike>`
## \[2.4.1] ## \[2.4.1]
- [`44a1f659`](https://github.com/tauri-apps/plugins-workspace/commit/44a1f659125a341191420e650608b0b6ff316a0e) ([#2846](https://github.com/tauri-apps/plugins-workspace/pull/2846) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fix `writeFile` doesn't create a new file by default when the data is a `ReadableStream` - [`44a1f659`](https://github.com/tauri-apps/plugins-workspace/commit/44a1f659125a341191420e650608b0b6ff316a0e) ([#2846](https://github.com/tauri-apps/plugins-workspace/pull/2846) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fix `writeFile` doesn't create a new file by default when the data is a `ReadableStream`
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-fs" name = "tauri-plugin-fs"
version = "2.4.1" version = "2.4.2"
description = "Access the file system." description = "Access the file system."
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
@@ -24,7 +24,7 @@ ios = { level = "partial", notes = "Access is restricted to Application folder b
tauri-plugin = { workspace = true, features = ["build"] } tauri-plugin = { workspace = true, features = ["build"] }
schemars = { workspace = true } schemars = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
toml = "0.8" toml = "0.9"
tauri-utils = { workspace = true, features = ["build"] } tauri-utils = { workspace = true, features = ["build"] }
[dependencies] [dependencies]
+1 -1
View File
@@ -1 +1 @@
if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(t){"use strict";function e(t,e,n,i){if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var i,o,r,a,s;"function"==typeof SuppressedError&&SuppressedError;const c="__TAURI_TO_IPC_KEY__";class f{constructor(t){i.set(this,void 0),o.set(this,0),r.set(this,[]),a.set(this,void 0),n(this,i,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((t=>{const s=t.index;if("end"in t)return void(s==e(this,o,"f")?this.cleanupCallback():n(this,a,s));const c=t.message;if(s==e(this,o,"f")){for(e(this,i,"f").call(this,c),n(this,o,e(this,o,"f")+1);e(this,o,"f")in e(this,r,"f");){const t=e(this,r,"f")[e(this,o,"f")];e(this,i,"f").call(this,t),delete e(this,r,"f")[e(this,o,"f")],n(this,o,e(this,o,"f")+1)}e(this,o,"f")===e(this,a,"f")&&this.cleanupCallback()}else e(this,r,"f")[s]=c}))}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}[(i=new WeakMap,o=new WeakMap,r=new WeakMap,a=new WeakMap,c)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[c]()}}async function l(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}class u{get rid(){return e(this,s,"f")}constructor(t){s.set(this,void 0),n(this,s,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}var p,w;function d(t){return{isFile:t.isFile,isDirectory:t.isDirectory,isSymlink:t.isSymlink,size:t.size,mtime:null!==t.mtime?new Date(t.mtime):null,atime:null!==t.atime?new Date(t.atime):null,birthtime:null!==t.birthtime?new Date(t.birthtime):null,readonly:t.readonly,fileAttributes:t.fileAttributes,dev:t.dev,ino:t.ino,mode:t.mode,nlink:t.nlink,uid:t.uid,gid:t.gid,rdev:t.rdev,blksize:t.blksize,blocks:t.blocks}}s=new WeakMap,t.BaseDirectory=void 0,(p=t.BaseDirectory||(t.BaseDirectory={}))[p.Audio=1]="Audio",p[p.Cache=2]="Cache",p[p.Config=3]="Config",p[p.Data=4]="Data",p[p.LocalData=5]="LocalData",p[p.Document=6]="Document",p[p.Download=7]="Download",p[p.Picture=8]="Picture",p[p.Public=9]="Public",p[p.Video=10]="Video",p[p.Resource=11]="Resource",p[p.Temp=12]="Temp",p[p.AppConfig=13]="AppConfig",p[p.AppData=14]="AppData",p[p.AppLocalData=15]="AppLocalData",p[p.AppCache=16]="AppCache",p[p.AppLog=17]="AppLog",p[p.Desktop=18]="Desktop",p[p.Executable=19]="Executable",p[p.Font=20]="Font",p[p.Home=21]="Home",p[p.Runtime=22]="Runtime",p[p.Template=23]="Template",t.SeekMode=void 0,(w=t.SeekMode||(t.SeekMode={}))[w.Start=0]="Start",w[w.Current=1]="Current",w[w.End=2]="End";class h extends u{async read(t){if(0===t.byteLength)return 0;const e=await l("plugin:fs|read",{rid:this.rid,len:t.byteLength}),n=function(t){const e=new Uint8ClampedArray(t),n=e.byteLength;let i=0;for(let t=0;t<n;t++)i*=256,i+=e[t];return i}(e.slice(-8)),i=e instanceof ArrayBuffer?new Uint8Array(e):e;return t.set(i.slice(0,i.length-8)),0===n?null:n}async seek(t,e){return await l("plugin:fs|seek",{rid:this.rid,offset:t,whence:e})}async stat(){return d(await l("plugin:fs|fstat",{rid:this.rid}))}async truncate(t){await l("plugin:fs|ftruncate",{rid:this.rid,len:t})}async write(t){return await l("plugin:fs|write",{rid:this.rid,data:t})}}async function y(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|open",{path:t instanceof URL?t.toString():t,options:e});return new h(n)}class L extends u{}async function R(t,e,n){const i=Array.isArray(t)?t:[t];for(const t of i)if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const o=new f;o.onmessage=e;const r=await l("plugin:fs|watch",{paths:i.map((t=>t instanceof URL?t.toString():t)),options:n,onEvent:o}),a=new L(r);return()=>{a.close()}}return t.FileHandle=h,t.copyFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol||e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|copy_file",{fromPath:t instanceof URL?t.toString():t,toPath:e instanceof URL?e.toString():e,options:n})},t.create=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|create",{path:t instanceof URL?t.toString():t,options:e});return new h(n)},t.exists=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|exists",{path:t instanceof URL?t.toString():t,options:e})},t.lstat=async function(t,e){return d(await l("plugin:fs|lstat",{path:t instanceof URL?t.toString():t,options:e}))},t.mkdir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l(Line truncated if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(t){"use strict";function e(t,e,n,i){if("function"==typeof e?t!==e||!i:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var i,o,r,a,s;"function"==typeof SuppressedError&&SuppressedError;const c="__TAURI_TO_IPC_KEY__";class f{constructor(t){i.set(this,void 0),o.set(this,0),r.set(this,[]),a.set(this,void 0),n(this,i,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((t=>{const s=t.index;if("end"in t)return void(s==e(this,o,"f")?this.cleanupCallback():n(this,a,s));const c=t.message;if(s==e(this,o,"f")){for(e(this,i,"f").call(this,c),n(this,o,e(this,o,"f")+1);e(this,o,"f")in e(this,r,"f");){const t=e(this,r,"f")[e(this,o,"f")];e(this,i,"f").call(this,t),delete e(this,r,"f")[e(this,o,"f")],n(this,o,e(this,o,"f")+1)}e(this,o,"f")===e(this,a,"f")&&this.cleanupCallback()}else e(this,r,"f")[s]=c}))}cleanupCallback(){window.__TAURI_INTERNALS__.unregisterCallback(this.id)}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}[(i=new WeakMap,o=new WeakMap,r=new WeakMap,a=new WeakMap,c)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[c]()}}async function l(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}class u{get rid(){return e(this,s,"f")}constructor(t){s.set(this,void 0),n(this,s,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}var p,w;function d(t){return{isFile:t.isFile,isDirectory:t.isDirectory,isSymlink:t.isSymlink,size:t.size,mtime:null!==t.mtime?new Date(t.mtime):null,atime:null!==t.atime?new Date(t.atime):null,birthtime:null!==t.birthtime?new Date(t.birthtime):null,readonly:t.readonly,fileAttributes:t.fileAttributes,dev:t.dev,ino:t.ino,mode:t.mode,nlink:t.nlink,uid:t.uid,gid:t.gid,rdev:t.rdev,blksize:t.blksize,blocks:t.blocks}}s=new WeakMap,t.BaseDirectory=void 0,(p=t.BaseDirectory||(t.BaseDirectory={}))[p.Audio=1]="Audio",p[p.Cache=2]="Cache",p[p.Config=3]="Config",p[p.Data=4]="Data",p[p.LocalData=5]="LocalData",p[p.Document=6]="Document",p[p.Download=7]="Download",p[p.Picture=8]="Picture",p[p.Public=9]="Public",p[p.Video=10]="Video",p[p.Resource=11]="Resource",p[p.Temp=12]="Temp",p[p.AppConfig=13]="AppConfig",p[p.AppData=14]="AppData",p[p.AppLocalData=15]="AppLocalData",p[p.AppCache=16]="AppCache",p[p.AppLog=17]="AppLog",p[p.Desktop=18]="Desktop",p[p.Executable=19]="Executable",p[p.Font=20]="Font",p[p.Home=21]="Home",p[p.Runtime=22]="Runtime",p[p.Template=23]="Template",t.SeekMode=void 0,(w=t.SeekMode||(t.SeekMode={}))[w.Start=0]="Start",w[w.Current=1]="Current",w[w.End=2]="End";class h extends u{async read(t){if(0===t.byteLength)return 0;const e=await l("plugin:fs|read",{rid:this.rid,len:t.byteLength}),n=function(t){const e=new Uint8ClampedArray(t),n=e.byteLength;let i=0;for(let t=0;t<n;t++)i*=256,i+=e[t];return i}(e.slice(-8)),i=e instanceof ArrayBuffer?new Uint8Array(e):e;return t.set(i.slice(0,i.length-8)),0===n?null:n}async seek(t,e){return await l("plugin:fs|seek",{rid:this.rid,offset:t,whence:e})}async stat(){return d(await l("plugin:fs|fstat",{rid:this.rid}))}async truncate(t){await l("plugin:fs|ftruncate",{rid:this.rid,len:t})}async write(t){return await l("plugin:fs|write",{rid:this.rid,data:t})}}async function y(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|open",{path:t instanceof URL?t.toString():t,options:e});return new h(n)}class L extends u{}async function R(t,e,n){const i=Array.isArray(t)?t:[t];for(const t of i)if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const o=new f;o.onmessage=e;const r=await l("plugin:fs|watch",{paths:i.map((t=>t instanceof URL?t.toString():t)),options:n,onEvent:o}),a=new L(r);return()=>{a.close()}}return t.FileHandle=h,t.copyFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol||e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|copy_file",{fromPath:t instanceof URL?t.toString():t,toPath:e instanceof URL?e.toString():e,options:n})},t.create=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|create",{path:t instanceof URL?t.toString():t,options:e});return new h(n)},t.exists=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|exists",{path:t instanceof URL?t.toString():t,options:e})},t.lstat=async function(t,e){return d(await l("plugin:fs|lstat",{path:t instanceof URL?t.toString():t,options:e}))},t.mkdir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l(Line truncated
+7 -2
View File
@@ -739,7 +739,7 @@ interface ReadFileOptions {
async function readFile( async function readFile(
path: string | URL, path: string | URL,
options?: ReadFileOptions options?: ReadFileOptions
): Promise<Uint8Array> { ): Promise<Uint8Array<ArrayBuffer>> {
if (path instanceof URL && path.protocol !== 'file:') { if (path instanceof URL && path.protocol !== 'file:') {
throw new TypeError('Must be a file URL.') throw new TypeError('Must be a file URL.')
} }
@@ -1074,7 +1074,12 @@ async function writeFile(
} }
if (data instanceof ReadableStream) { if (data instanceof ReadableStream) {
const file = await open(path, { create: true, ...options }) const file = await open(path, {
read: false,
create: true,
write: true,
...options
})
const reader = data.getReader() const reader = data.getReader()
try { try {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-fs", "name": "@tauri-apps/plugin-fs",
"version": "2.4.1", "version": "2.4.2",
"description": "Access the file system.", "description": "Access the file system.",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
@@ -25,6 +25,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
@@ -3748,6 +3748,28 @@ This enables all index or metadata related commands without any pre-configured a
An empty permission you can use to modify the global scope. An empty permission you can use to modify the global scope.
## Example
```json
{
"identifier": "read-documents",
"windows": ["main"],
"permissions": [
"fs:allow-read",
{
"identifier": "fs:scope",
"allow": [
"$APPDATA/documents/**/*"
],
"deny": [
"$APPDATA/documents/secret.txt"
]
}
]
}
```
</td> </td>
</tr> </tr>
+2 -2
View File
@@ -2005,10 +2005,10 @@
"markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths." "markdownDescription": "This enables all index or metadata related commands without any pre-configured accessible paths."
}, },
{ {
"description": "An empty permission you can use to modify the global scope.", "description": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n",
"type": "string", "type": "string",
"const": "scope", "const": "scope",
"markdownDescription": "An empty permission you can use to modify the global scope." "markdownDescription": "An empty permission you can use to modify the global scope.\n\n## Example\n\n```json\n{\n \"identifier\": \"read-documents\",\n \"windows\": [\"main\"],\n \"permissions\": [\n \"fs:allow-read\",\n {\n \"identifier\": \"fs:scope\",\n \"allow\": [\n \"$APPDATA/documents/**/*\"\n ],\n \"deny\": [\n \"$APPDATA/documents/secret.txt\"\n ]\n }\n ]\n}\n```\n"
}, },
{ {
"description": "This enables all write related commands without any pre-configured accessible paths.", "description": "This enables all write related commands without any pre-configured accessible paths.",
+24 -1
View File
@@ -2,4 +2,27 @@
[[permission]] [[permission]]
identifier = "scope" identifier = "scope"
description = "An empty permission you can use to modify the global scope." description = """
An empty permission you can use to modify the global scope.
## Example
```json
{
"identifier": "read-documents",
"windows": ["main"],
"permissions": [
"fs:allow-read",
{
"identifier": "fs:scope",
"allow": [
"$APPDATA/documents/**/*"
],
"deny": [
"$APPDATA/documents/secret.txt"
]
}
]
}
```
"""
+110 -18
View File
@@ -85,6 +85,7 @@ pub fn create<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"create",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -119,6 +120,7 @@ pub fn open<R: Runtime>(
options: Option<OpenOptions>, options: Option<OpenOptions>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
let (file, _path) = resolve_file( let (file, _path) = resolve_file(
"open",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -167,6 +169,7 @@ pub async fn copy_file<R: Runtime>(
options: Option<CopyFileOptions>, options: Option<CopyFileOptions>,
) -> CommandResult<()> { ) -> CommandResult<()> {
let resolved_from_path = resolve_path( let resolved_from_path = resolve_path(
"copy-file",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -174,6 +177,7 @@ pub async fn copy_file<R: Runtime>(
options.as_ref().and_then(|o| o.from_path_base_dir), options.as_ref().and_then(|o| o.from_path_base_dir),
)?; )?;
let resolved_to_path = resolve_path( let resolved_to_path = resolve_path(
"copy-file",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -208,6 +212,7 @@ pub fn mkdir<R: Runtime>(
options: Option<MkdirOptions>, options: Option<MkdirOptions>,
) -> CommandResult<()> { ) -> CommandResult<()> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"mkdir",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -255,6 +260,7 @@ pub async fn read_dir<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<Vec<DirEntry>> { ) -> CommandResult<Vec<DirEntry>> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"read-dir",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -331,8 +337,8 @@ pub async fn read<R: Runtime>(
Ok(tauri::ipc::Response::new(data)) Ok(tauri::ipc::Response::new(data))
} }
#[tauri::command] async fn read_file_inner<R: Runtime>(
pub async fn read_file<R: Runtime>( permission: &str,
webview: Webview<R>, webview: Webview<R>,
global_scope: GlobalScope<Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<Entry>, command_scope: CommandScope<Entry>,
@@ -340,6 +346,7 @@ pub async fn read_file<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<tauri::ipc::Response> { ) -> CommandResult<tauri::ipc::Response> {
let (mut file, path) = resolve_file( let (mut file, path) = resolve_file(
permission,
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -367,6 +374,25 @@ pub async fn read_file<R: Runtime>(
Ok(tauri::ipc::Response::new(contents)) Ok(tauri::ipc::Response::new(contents))
} }
#[tauri::command]
pub async fn read_file<R: Runtime>(
webview: Webview<R>,
global_scope: GlobalScope<Entry>,
command_scope: CommandScope<Entry>,
path: SafeFilePath,
options: Option<BaseOptions>,
) -> CommandResult<tauri::ipc::Response> {
read_file_inner(
"read-file",
webview,
global_scope,
command_scope,
path,
options,
)
.await
}
// TODO, remove in v3, rely on `read_file` command instead // TODO, remove in v3, rely on `read_file` command instead
#[tauri::command] #[tauri::command]
pub async fn read_text_file<R: Runtime>( pub async fn read_text_file<R: Runtime>(
@@ -376,7 +402,15 @@ pub async fn read_text_file<R: Runtime>(
path: SafeFilePath, path: SafeFilePath,
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<tauri::ipc::Response> { ) -> CommandResult<tauri::ipc::Response> {
read_file(webview, global_scope, command_scope, path, options).await read_file_inner(
"read-text-file",
webview,
global_scope,
command_scope,
path,
options,
)
.await
} }
#[tauri::command] #[tauri::command]
@@ -388,6 +422,7 @@ pub fn read_text_file_lines<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"read-text-file-lines",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -452,6 +487,7 @@ pub fn remove<R: Runtime>(
options: Option<RemoveOptions>, options: Option<RemoveOptions>,
) -> CommandResult<()> { ) -> CommandResult<()> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"remove",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -521,6 +557,7 @@ pub fn rename<R: Runtime>(
options: Option<RenameOptions>, options: Option<RenameOptions>,
) -> CommandResult<()> { ) -> CommandResult<()> {
let resolved_old_path = resolve_path( let resolved_old_path = resolve_path(
"rename",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -528,6 +565,7 @@ pub fn rename<R: Runtime>(
options.as_ref().and_then(|o| o.old_path_base_dir), options.as_ref().and_then(|o| o.old_path_base_dir),
)?; )?;
let resolved_new_path = resolve_path( let resolved_new_path = resolve_path(
"rename",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -575,6 +613,7 @@ pub async fn seek<R: Runtime>(
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Metadata>>( fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Metadata>>(
permission: &str,
metadata_fn: F, metadata_fn: F,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
@@ -585,6 +624,7 @@ fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Meta
match path { match path {
SafeFilePath::Url(url) => { SafeFilePath::Url(url) => {
let (file, path) = resolve_file( let (file, path) = resolve_file(
permission,
webview, webview,
global_scope, global_scope,
command_scope, command_scope,
@@ -606,6 +646,7 @@ fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Meta
}) })
} }
SafeFilePath::Path(p) => get_fs_metadata( SafeFilePath::Path(p) => get_fs_metadata(
permission,
metadata_fn, metadata_fn,
webview, webview,
global_scope, global_scope,
@@ -618,6 +659,7 @@ fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Meta
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Metadata>>( fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Metadata>>(
permission: &str,
metadata_fn: F, metadata_fn: F,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
@@ -626,6 +668,7 @@ fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Meta
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<std::fs::Metadata> { ) -> CommandResult<std::fs::Metadata> {
get_fs_metadata( get_fs_metadata(
permission,
metadata_fn, metadata_fn,
webview, webview,
global_scope, global_scope,
@@ -636,6 +679,7 @@ fn get_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Meta
} }
fn get_fs_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Metadata>>( fn get_fs_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::Metadata>>(
permission: &str,
metadata_fn: F, metadata_fn: F,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
@@ -644,6 +688,7 @@ fn get_fs_metadata<R: Runtime, F: FnOnce(&PathBuf) -> std::io::Result<std::fs::M
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<std::fs::Metadata> { ) -> CommandResult<std::fs::Metadata> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
permission,
webview, webview,
global_scope, global_scope,
command_scope, command_scope,
@@ -668,6 +713,7 @@ pub fn stat<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<FileInfo> { ) -> CommandResult<FileInfo> {
let metadata = get_metadata( let metadata = get_metadata(
"stat",
|p| std::fs::metadata(p), |p| std::fs::metadata(p),
&webview, &webview,
&global_scope, &global_scope,
@@ -688,6 +734,7 @@ pub fn lstat<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<FileInfo> { ) -> CommandResult<FileInfo> {
let metadata = get_metadata( let metadata = get_metadata(
"lstat",
|p| std::fs::symlink_metadata(p), |p| std::fs::symlink_metadata(p),
&webview, &webview,
&global_scope, &global_scope,
@@ -716,6 +763,7 @@ pub async fn truncate<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<()> { ) -> CommandResult<()> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"truncate",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -784,23 +832,13 @@ fn default_create_value() -> bool {
true true
} }
#[tauri::command] async fn write_file_inner<R: Runtime>(
pub async fn write_file<R: Runtime>( permission: &str,
webview: Webview<R>, webview: Webview<R>,
global_scope: GlobalScope<Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<Entry>, command_scope: CommandScope<Entry>,
request: tauri::ipc::Request<'_>, request: tauri::ipc::Request<'_>,
) -> CommandResult<()> { ) -> CommandResult<()> {
let data = match request.body() {
tauri::ipc::InvokeBody::Raw(data) => Cow::Borrowed(data),
tauri::ipc::InvokeBody::Json(serde_json::Value::Array(data)) => Cow::Owned(
data.iter()
.flat_map(|v| v.as_number().and_then(|v| v.as_u64().map(|v| v as u8)))
.collect(),
),
_ => return Err(anyhow::anyhow!("unexpected invoke body").into()),
};
let path = request let path = request
.headers() .headers()
.get("path") .get("path")
@@ -811,6 +849,7 @@ pub async fn write_file<R: Runtime>(
.map_err(|_| anyhow::anyhow!("path is not a valid UTF-8").into()) .map_err(|_| anyhow::anyhow!("path is not a valid UTF-8").into())
}) })
.and_then(|p| SafeFilePath::from_str(&p).map_err(CommandError::from))?; .and_then(|p| SafeFilePath::from_str(&p).map_err(CommandError::from))?;
let options: Option<WriteFileOptions> = request let options: Option<WriteFileOptions> = request
.headers() .headers()
.get("options") .get("options")
@@ -818,6 +857,7 @@ pub async fn write_file<R: Runtime>(
.and_then(|opts| serde_json::from_str(opts).ok()); .and_then(|opts| serde_json::from_str(opts).ok());
let (mut file, path) = resolve_file( let (mut file, path) = resolve_file(
permission,
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -853,6 +893,16 @@ pub async fn write_file<R: Runtime>(
}, },
)?; )?;
let data = match request.body() {
tauri::ipc::InvokeBody::Raw(data) => Cow::Borrowed(data),
tauri::ipc::InvokeBody::Json(serde_json::Value::Array(data)) => Cow::Owned(
data.iter()
.flat_map(|v| v.as_number().and_then(|v| v.as_u64().map(|v| v as u8)))
.collect(),
),
_ => return Err(anyhow::anyhow!("unexpected invoke body").into()),
};
file.write_all(&data) file.write_all(&data)
.map_err(|e| { .map_err(|e| {
format!( format!(
@@ -863,6 +913,16 @@ pub async fn write_file<R: Runtime>(
.map_err(Into::into) .map_err(Into::into)
} }
#[tauri::command]
pub async fn write_file<R: Runtime>(
webview: Webview<R>,
global_scope: GlobalScope<Entry>,
command_scope: CommandScope<Entry>,
request: tauri::ipc::Request<'_>,
) -> CommandResult<()> {
write_file_inner("write-file", webview, global_scope, command_scope, request).await
}
// TODO, remove in v3, rely on `write_file` command instead // TODO, remove in v3, rely on `write_file` command instead
#[tauri::command] #[tauri::command]
pub async fn write_text_file<R: Runtime>( pub async fn write_text_file<R: Runtime>(
@@ -871,7 +931,14 @@ pub async fn write_text_file<R: Runtime>(
command_scope: CommandScope<Entry>, command_scope: CommandScope<Entry>,
request: tauri::ipc::Request<'_>, request: tauri::ipc::Request<'_>,
) -> CommandResult<()> { ) -> CommandResult<()> {
write_file(webview, global_scope, command_scope, request).await write_file_inner(
"write-text-file",
webview,
global_scope,
command_scope,
request,
)
.await
} }
#[tauri::command] #[tauri::command]
@@ -883,6 +950,7 @@ pub fn exists<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<bool> { ) -> CommandResult<bool> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"exists",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -901,6 +969,7 @@ pub async fn size<R: Runtime>(
options: Option<BaseOptions>, options: Option<BaseOptions>,
) -> CommandResult<u64> { ) -> CommandResult<u64> {
let resolved_path = resolve_path( let resolved_path = resolve_path(
"size",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -943,16 +1012,25 @@ fn get_dir_size(path: &PathBuf) -> CommandResult<u64> {
#[cfg(not(target_os = "android"))] #[cfg(not(target_os = "android"))]
pub fn resolve_file<R: Runtime>( pub fn resolve_file<R: Runtime>(
permission: &str,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
command_scope: &CommandScope<Entry>, command_scope: &CommandScope<Entry>,
path: SafeFilePath, path: SafeFilePath,
open_options: OpenOptions, open_options: OpenOptions,
) -> CommandResult<(File, PathBuf)> { ) -> CommandResult<(File, PathBuf)> {
resolve_file_in_fs(webview, global_scope, command_scope, path, open_options) resolve_file_in_fs(
permission,
webview,
global_scope,
command_scope,
path,
open_options,
)
} }
fn resolve_file_in_fs<R: Runtime>( fn resolve_file_in_fs<R: Runtime>(
permission: &str,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
command_scope: &CommandScope<Entry>, command_scope: &CommandScope<Entry>,
@@ -960,6 +1038,7 @@ fn resolve_file_in_fs<R: Runtime>(
open_options: OpenOptions, open_options: OpenOptions,
) -> CommandResult<(File, PathBuf)> { ) -> CommandResult<(File, PathBuf)> {
let path = resolve_path( let path = resolve_path(
permission,
webview, webview,
global_scope, global_scope,
command_scope, command_scope,
@@ -980,6 +1059,7 @@ fn resolve_file_in_fs<R: Runtime>(
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
pub fn resolve_file<R: Runtime>( pub fn resolve_file<R: Runtime>(
permission: &str,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
command_scope: &CommandScope<Entry>, command_scope: &CommandScope<Entry>,
@@ -997,6 +1077,7 @@ pub fn resolve_file<R: Runtime>(
Ok((file, path)) Ok((file, path))
} }
SafeFilePath::Path(path) => resolve_file_in_fs( SafeFilePath::Path(path) => resolve_file_in_fs(
permission,
webview, webview,
global_scope, global_scope,
command_scope, command_scope,
@@ -1007,6 +1088,7 @@ pub fn resolve_file<R: Runtime>(
} }
pub fn resolve_path<R: Runtime>( pub fn resolve_path<R: Runtime>(
permission: &str,
webview: &Webview<R>, webview: &Webview<R>,
global_scope: &GlobalScope<Entry>, global_scope: &GlobalScope<Entry>,
command_scope: &CommandScope<Entry>, command_scope: &CommandScope<Entry>,
@@ -1052,7 +1134,17 @@ pub fn resolve_path<R: Runtime>(
if fs_scope.scope.is_allowed(&path) || scope.is_allowed(&path) { if fs_scope.scope.is_allowed(&path) || scope.is_allowed(&path) {
Ok(path) Ok(path)
} else { } else {
Err(CommandError::Plugin(Error::PathForbidden(path))) #[cfg(not(debug_assertions))]
return Err(CommandError::Plugin(Error::PathForbidden(path)));
#[cfg(debug_assertions)]
Err(
anyhow::anyhow!(
"forbidden path: {}, maybe it is not allowed on the scope for `allow-{permission}` permission in your capability file",
path.display()
)
)
.map_err(Into::into)
} }
} }
+1 -1
View File
@@ -89,7 +89,7 @@ impl<R: Runtime> Fs<R> {
std::fs::File::from_raw_fd(fd) std::fs::File::from_raw_fd(fd)
}) })
} else { } else {
todo!() unimplemented!()
} }
} }
} }
+1
View File
@@ -49,6 +49,7 @@ pub fn watch<R: Runtime>(
.into_iter() .into_iter()
.map(|path| { .map(|path| {
resolve_path( resolve_path(
"watch",
&webview, &webview,
&global_scope, &global_scope,
&command_scope, &command_scope,
@@ -91,11 +91,9 @@ public class Geolocation(private val context: Context) {
val lowPrio = if (networkEnabled) Priority.PRIORITY_BALANCED_POWER_ACCURACY else Priority.PRIORITY_LOW_POWER val lowPrio = if (networkEnabled) Priority.PRIORITY_BALANCED_POWER_ACCURACY else Priority.PRIORITY_LOW_POWER
val prio = if (enableHighAccuracy) Priority.PRIORITY_HIGH_ACCURACY else lowPrio val prio = if (enableHighAccuracy) Priority.PRIORITY_HIGH_ACCURACY else lowPrio
Logger.error(prio.toString()) val locationRequest = LocationRequest.Builder(timeout)
val locationRequest = LocationRequest.Builder(10000)
.setMaxUpdateDelayMillis(timeout) .setMaxUpdateDelayMillis(timeout)
.setMinUpdateIntervalMillis(5000) .setMinUpdateIntervalMillis(timeout)
.setPriority(prio) .setPriority(prio)
.build() .build()
@@ -145,4 +143,4 @@ public class Geolocation(private val context: Context) {
return lastLoc return lastLoc
} }
} }
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+1 -1
View File
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## \[2.5.2]
### Dependencies
- Upgraded to `fs-js@2.4.2`
## \[2.5.1] ## \[2.5.1]
### Dependencies ### Dependencies
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-http" name = "tauri-plugin-http"
version = "2.5.1" version = "2.5.2"
description = "Access an HTTP client written in Rust." description = "Access an HTTP client written in Rust."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -34,7 +34,7 @@ serde_json = { workspace = true }
tauri = { workspace = true } tauri = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tokio = { version = "1", features = ["sync", "macros"] } tokio = { version = "1", features = ["sync", "macros"] }
tauri-plugin-fs = { path = "../fs", version = "2.4.1" } tauri-plugin-fs = { path = "../fs", version = "2.4.2" }
urlpattern = "0.3" urlpattern = "0.3"
regex = "1" regex = "1"
http = "1" http = "1"
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-http", "name": "@tauri-apps/plugin-http",
"version": "2.5.1", "version": "2.5.2",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.7.0]
- [`625bb1c0`](https://github.com/tauri-apps/plugins-workspace/commit/625bb1c0965394b88522643731f78ccbcca84add) ([#2965](https://github.com/tauri-apps/plugins-workspace/pull/2965) by [@lucasfernog](https://github.com/tauri-apps/plugins-workspace/../../lucasfernog)) Re-export the log crate.
## \[2.6.0] ## \[2.6.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 - [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-log" name = "tauri-plugin-log"
version = "2.6.0" version = "2.7.0"
description = "Configurable logging for your Tauri app." description = "Configurable logging for your Tauri app."
authors = { workspace = true } authors = { workspace = true }
license = { workspace = true } license = { workspace = true }
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-log", "name": "@tauri-apps/plugin-log",
"version": "2.6.0", "version": "2.7.0",
"description": "Configurable logging for your Tauri app.", "description": "Configurable logging for your Tauri app.",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
@@ -25,6 +25,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+73
View File
@@ -0,0 +1,73 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::collections::HashMap;
use log::RecordBuilder;
use crate::{LogLevel, WEBVIEW_TARGET};
#[tauri::command]
pub fn log(
level: LogLevel,
message: String,
location: Option<&str>,
file: Option<&str>,
line: Option<u32>,
key_values: Option<HashMap<String, String>>,
) {
let level = log::Level::from(level);
let target = if let Some(location) = location {
format!("{WEBVIEW_TARGET}:{location}")
} else {
WEBVIEW_TARGET.to_string()
};
let mut builder = RecordBuilder::new();
builder.level(level).target(&target).file(file).line(line);
let key_values = key_values.unwrap_or_default();
let mut kv = HashMap::new();
for (k, v) in key_values.iter() {
kv.insert(k.as_str(), v.as_str());
}
builder.key_values(&kv);
#[cfg(feature = "tracing")]
emit_trace(level, &message, location, file, line, &kv);
log::logger().log(&builder.args(format_args!("{message}")).build());
}
// Target becomes default and location is added as a parameter
#[cfg(feature = "tracing")]
fn emit_trace(
level: log::Level,
message: &String,
location: Option<&str>,
file: Option<&str>,
line: Option<u32>,
kv: &HashMap<&str, &str>,
) {
macro_rules! emit_event {
($level:expr) => {
tracing::event!(
target: WEBVIEW_TARGET,
$level,
message = %message,
location = location,
file,
line,
?kv
)
};
}
match level {
log::Level::Error => emit_event!(tracing::Level::ERROR),
log::Level::Warn => emit_event!(tracing::Level::WARN),
log::Level::Info => emit_event!(tracing::Level::INFO),
log::Level::Debug => emit_event!(tracing::Level::DEBUG),
log::Level::Trace => emit_event!(tracing::Level::TRACE),
}
}
+4 -67
View File
@@ -10,12 +10,10 @@
)] )]
use fern::{Filter, FormatCallback}; use fern::{Filter, FormatCallback};
use log::{logger, RecordBuilder};
use log::{LevelFilter, Record}; use log::{LevelFilter, Record};
use serde::Serialize; use serde::Serialize;
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap;
use std::{ use std::{
fmt::Arguments, fmt::Arguments,
fs::{self, File}, fs::{self, File},
@@ -30,6 +28,9 @@ use tauri::{AppHandle, Emitter};
use time::{macros::format_description, OffsetDateTime}; use time::{macros::format_description, OffsetDateTime};
pub use fern; pub use fern;
pub use log;
mod commands;
pub const WEBVIEW_TARGET: &str = "webview"; pub const WEBVIEW_TARGET: &str = "webview";
@@ -206,70 +207,6 @@ impl Target {
} }
} }
// Target becomes default and location is added as a parameter
#[cfg(feature = "tracing")]
fn emit_trace(
level: log::Level,
message: &String,
location: Option<&str>,
file: Option<&str>,
line: Option<u32>,
kv: &HashMap<&str, &str>,
) {
macro_rules! emit_event {
($level:expr) => {
tracing::event!(
target: WEBVIEW_TARGET,
$level,
message = %message,
location = location,
file,
line,
?kv
)
};
}
match level {
log::Level::Error => emit_event!(tracing::Level::ERROR),
log::Level::Warn => emit_event!(tracing::Level::WARN),
log::Level::Info => emit_event!(tracing::Level::INFO),
log::Level::Debug => emit_event!(tracing::Level::DEBUG),
log::Level::Trace => emit_event!(tracing::Level::TRACE),
}
}
#[tauri::command]
fn log(
level: LogLevel,
message: String,
location: Option<&str>,
file: Option<&str>,
line: Option<u32>,
key_values: Option<HashMap<String, String>>,
) {
let level = log::Level::from(level);
let target = if let Some(location) = location {
format!("{WEBVIEW_TARGET}:{location}")
} else {
WEBVIEW_TARGET.to_string()
};
let mut builder = RecordBuilder::new();
builder.level(level).target(&target).file(file).line(line);
let key_values = key_values.unwrap_or_default();
let mut kv = HashMap::new();
for (k, v) in key_values.iter() {
kv.insert(k.as_str(), v.as_str());
}
builder.key_values(&kv);
#[cfg(feature = "tracing")]
emit_trace(level, &message, location, file, line, &kv);
logger().log(&builder.args(format_args!("{message}")).build());
}
pub struct Builder { pub struct Builder {
dispatch: fern::Dispatch, dispatch: fern::Dispatch,
rotation_strategy: RotationStrategy, rotation_strategy: RotationStrategy,
@@ -528,7 +465,7 @@ impl Builder {
} }
fn plugin_builder<R: Runtime>() -> plugin::Builder<R> { fn plugin_builder<R: Runtime>() -> plugin::Builder<R> {
plugin::Builder::new("log").invoke_handler(tauri::generate_handler![log]) plugin::Builder::new("log").invoke_handler(tauri::generate_handler![commands::log])
} }
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
+4
View File
@@ -1,5 +1,9 @@
# Changelog # Changelog
## \[2.3.1]
- [`fe23a5e0`](https://github.com/tauri-apps/plugins-workspace/commit/fe23a5e01399a6ad61426bf8a94a6bb97227cf88) ([#2885](https://github.com/tauri-apps/plugins-workspace/pull/2885) by [@zaphim12](https://github.com/tauri-apps/plugins-workspace/../../zaphim12)) On iOS, the reader session will now get closed properly on errors, preventing dangling invalid sessions that could prevent subsequent write attempts.
## \[2.3.0] ## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 - [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-nfc" name = "tauri-plugin-nfc"
version = "2.3.0" version = "2.3.1"
description = "Read and write NFC tags on Android and iOS." description = "Read and write NFC tags on Android and iOS."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-nfc", "name": "@tauri-apps/plugin-nfc",
"version": "2.3.0", "version": "2.3.1",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@@ -25,6 +25,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+5
View File
@@ -1,5 +1,10 @@
# Changelog # Changelog
## \[2.3.1]
- [`8abb31ee`](https://github.com/tauri-apps/plugins-workspace/commit/8abb31ee59c68197102c0aa699d690b34646ec3c) ([#2905](https://github.com/tauri-apps/plugins-workspace/pull/2905) by [@ChristianPavilonis](https://github.com/tauri-apps/plugins-workspace/../../ChristianPavilonis)) Fix notification scheduling on iOS.
- [`2d03e2ea`](https://github.com/tauri-apps/plugins-workspace/commit/2d03e2eac2c19ad997d81d23836ab6a219252ffb) ([#2678](https://github.com/tauri-apps/plugins-workspace/pull/2678) by [@Keerthi421](https://github.com/tauri-apps/plugins-workspace/../../Keerthi421)) Added sound support for desktop notifications which was previously only available on mobile platforms.
## \[2.3.0] ## \[2.3.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 - [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-notification" name = "tauri-plugin-notification"
version = "2.3.0" version = "2.3.1"
description = "Send desktop and mobile notifications on your Tauri application." description = "Send desktop and mobile notifications on your Tauri application."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
+39
View File
@@ -95,6 +95,45 @@ export async function enqueueNotification(title, body) {
} }
``` ```
### Notification with Sound
You can add sound to your notifications on all platforms (desktop and mobile):
```javascript
import { sendNotification } from '@tauri-apps/plugin-notification'
import { platform } from '@tauri-apps/api/os'
// Basic notification with sound
sendNotification({
title: 'New Message',
body: 'You have a new message',
sound: 'notification.wav' // Path to sound file
})
// Platform-specific sounds
async function sendPlatformSpecificNotification() {
const platformName = platform()
let soundPath
if (platformName === 'darwin') {
// On macOS: use system sounds or sound files in the app bundle
soundPath = 'Ping' // macOS system sound
} else if (platformName === 'linux') {
// On Linux: use XDG theme sounds or file paths
soundPath = 'message-new-instant' // XDG theme sound
} else {
// On Windows: use file paths
soundPath = 'notification.wav'
}
sendNotification({
title: 'Platform-specific Notification',
body: 'This notification uses platform-specific sound',
sound: soundPath
})
}
```
## Contributing ## Contributing
PRs accepted. Please make sure to read the Contributing Guide before making a pull request. PRs accepted. Please make sure to read the Contributing Guide before making a pull request.
+7 -1
View File
@@ -71,7 +71,13 @@ interface Options {
*/ */
groupSummary?: boolean groupSummary?: boolean
/** /**
* The sound resource name. Only available on mobile. * The sound resource name or file path for the notification.
*
* Platform specific behavior:
* - On macOS: use system sounds (e.g., "Ping", "Blow") or sound files in the app bundle
* - On Linux: use XDG theme sounds (e.g., "message-new-instant") or file paths
* - On Windows: use file paths to sound files (.wav format)
* - On Mobile: use resource names
*/ */
sound?: string sound?: string
/** /**
@@ -38,10 +38,17 @@ func makeNotificationContent(_ notification: Notification) throws -> UNNotificat
arguments: nil) arguments: nil)
} }
content.userInfo = [ var userInfo: [String: Any] = [:]
"__EXTRA__": notification.extra as Any,
"__SCHEDULE__": notification.schedule as Any, if let extra = notification.extra {
] userInfo["__EXTRA__"] = extra
}
if let schedule = notification.schedule {
userInfo["__SCHEDULE__"] = scheduleToDictionary(schedule)
}
content.userInfo = userInfo
if let actionTypeId = notification.actionTypeId { if let actionTypeId = notification.actionTypeId {
content.categoryIdentifier = actionTypeId content.categoryIdentifier = actionTypeId
@@ -66,6 +73,56 @@ func makeNotificationContent(_ notification: Notification) throws -> UNNotificat
return content return content
} }
func scheduleToDictionary(_ schedule: NotificationSchedule) -> [String: Any] {
switch schedule {
case .at(let date, let repeating):
return [
"type": "at",
"date": date,
"repeating": repeating
]
case .interval(let interval):
return [
"type": "interval",
"interval": scheduleIntervalToDictionary(interval)
]
case .every(let interval, let count):
return [
"type": "every",
"interval": interval.rawValue,
"count": count
]
}
}
func scheduleIntervalToDictionary(_ interval: ScheduleInterval) -> [String: Any] {
var dict: [String: Any] = [:]
if let year = interval.year {
dict["year"] = year
}
if let month = interval.month {
dict["month"] = month
}
if let day = interval.day {
dict["day"] = day
}
if let weekday = interval.weekday {
dict["weekday"] = weekday
}
if let hour = interval.hour {
dict["hour"] = hour
}
if let minute = interval.minute {
dict["minute"] = minute
}
if let second = interval.second {
dict["second"] = second
}
return dict
}
func makeAttachments(_ attachments: [NotificationAttachment]) throws -> [UNNotificationAttachment] { func makeAttachments(_ attachments: [NotificationAttachment]) throws -> [UNNotificationAttachment] {
var createdAttachments = [UNNotificationAttachment]() var createdAttachments = [UNNotificationAttachment]()
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@tauri-apps/plugin-notification", "name": "@tauri-apps/plugin-notification",
"version": "2.3.0", "version": "2.3.1",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"authors": [ "authors": [
"Tauri Programme within The Commons Conservancy" "Tauri Programme within The Commons Conservancy"
@@ -24,6 +24,6 @@
"LICENSE" "LICENSE"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.8.0"
} }
} }
+16
View File
@@ -39,6 +39,9 @@ impl<R: Runtime> crate::NotificationBuilder<R> {
if let Some(icon) = self.data.icon { if let Some(icon) = self.data.icon {
notification = notification.icon(icon); notification = notification.icon(icon);
} }
if let Some(sound) = self.data.sound {
notification = notification.sound(sound);
}
#[cfg(feature = "windows7-compat")] #[cfg(feature = "windows7-compat")]
{ {
notification.notify(&self.app)?; notification.notify(&self.app)?;
@@ -102,6 +105,8 @@ mod imp {
title: Option<String>, title: Option<String>,
/// The notification icon. /// The notification icon.
icon: Option<String>, icon: Option<String>,
/// The notification sound.
sound: Option<String>,
/// The notification identifier /// The notification identifier
identifier: String, identifier: String,
} }
@@ -136,6 +141,13 @@ mod imp {
self self
} }
/// Sets the notification sound file.
#[must_use]
pub fn sound(mut self, sound: impl Into<String>) -> Self {
self.sound = Some(sound.into());
self
}
/// Shows the notification. /// Shows the notification.
/// ///
/// # Examples /// # Examples
@@ -177,6 +189,9 @@ mod imp {
} else { } else {
notification.auto_icon(); notification.auto_icon();
} }
if let Some(sound) = self.sound {
notification.sound_name(&sound);
}
#[cfg(windows)] #[cfg(windows)]
{ {
let exe = tauri::utils::platform::current_exe()?; let exe = tauri::utils::platform::current_exe()?;
@@ -250,6 +265,7 @@ mod imp {
} }
} }
/// Shows the notification on Windows 7.
#[cfg(all(windows, feature = "windows7-compat"))] #[cfg(all(windows, feature = "windows7-compat"))]
fn notify_win7<R: tauri::Runtime>(self, app: &tauri::AppHandle<R>) -> crate::Result<()> { fn notify_win7<R: tauri::Runtime>(self, app: &tauri::AppHandle<R>) -> crate::Result<()> {
let app_ = app.clone(); let app_ = app.clone();
+1 -1
View File
@@ -132,7 +132,7 @@ impl<R: Runtime> NotificationBuilder<R> {
self self
} }
/// The sound resource name. Only available on mobile. /// The sound resource name for the notification.
pub fn sound(mut self, sound: impl Into<String>) -> Self { pub fn sound(mut self, sound: impl Into<String>) -> Self {
self.data.sound.replace(sound.into()); self.data.sound.replace(sound.into());
self self
+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## \[2.5.0]
### enhance
- [`b8056f48`](https://github.com/tauri-apps/plugins-workspace/commit/b8056f484c7144af095d4d6ded1e8adbb9b8a865) ([#2897](https://github.com/tauri-apps/plugins-workspace/pull/2897) by [@petersamokhin](https://github.com/tauri-apps/plugins-workspace/../../petersamokhin)) Allow reveal multiple items in the file explorer.
## \[2.4.0] ## \[2.4.0]
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6 - [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
+1 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "tauri-plugin-opener" name = "tauri-plugin-opener"
version = "2.4.0" version = "2.5.0"
description = "Open files and URLs using their default application." description = "Open files and URLs using their default application."
edition = { workspace = true } edition = { workspace = true }
authors = { workspace = true } authors = { workspace = true }
@@ -35,8 +35,6 @@ tauri = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
open = { version = "5", features = ["shellexecute-on-windows"] } open = { version = "5", features = ["shellexecute-on-windows"] }
glob = { workspace = true } glob = { workspace = true }
[target."cfg(windows)".dependencies]
dunce = { workspace = true } dunce = { workspace = true }
[target."cfg(windows)".dependencies.windows] [target."cfg(windows)".dependencies.windows]
+7
View File
@@ -75,6 +75,10 @@ await openPath('/path/to/file', 'firefox')
// Reveal a path with the system's default explorer // Reveal a path with the system's default explorer
await revealItemInDir('/path/to/file') await revealItemInDir('/path/to/file')
// Reveal multiple paths with the system's default explorer
// Note: will be renamed to `revealItemsInDir` in the next major version
await revealItemInDir(['/path/to/file', '/path/to/another/file'])
``` ```
### Usage from Rust ### Usage from Rust
@@ -102,6 +106,9 @@ fn main() {
// Reveal a path with the system's default explorer // Reveal a path with the system's default explorer
opener.reveal_item_in_dir("/path/to/file")?; opener.reveal_item_in_dir("/path/to/file")?;
// Reveal multiple paths with the system's default explorer
opener.reveal_items_in_dir(["/path/to/file"])?;
Ok(()) Ok(())
}) })
.run(tauri::generate_context!()) .run(tauri::generate_context!())
Loaded 100 of 143 files, more files were not shown because too many files have changed in this diff. Show more