mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-05-03 12:15:11 +02:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15fc071773 | |||
| 73c6047b78 | |||
| a4b71a1992 | |||
| 4eb36b0ff5 | |||
| b8056f484c | |||
| 5ac8fbb1fa | |||
| e0323ec752 | |||
| cc98e6a892 | |||
| af22ae0a97 | |||
| 2f24c7a70c | |||
| fe23a5e013 | |||
| ff6d23ede1 | |||
| 449dd117a4 | |||
| 9b43f48856 | |||
| af08c66faa | |||
| 7974acae22 | |||
| a985359e69 | |||
| 97bebcf6e8 | |||
| 27ddcd0abe | |||
| d7fb5623d6 | |||
| d4f8299b12 | |||
| 341919ed57 | |||
| 124f2191aa | |||
| 0970b94949 | |||
| 7340242d4e | |||
| d66aa6ff78 | |||
| 6f345870df | |||
| 708fa4e2b7 | |||
| b729203059 | |||
| 2f9c71aae7 | |||
| 80d4d8e128 | |||
| e7a98b0d2e | |||
| 44a1f65912 | |||
| 6210cd31df | |||
| 467f07b7de | |||
| 7ba6e08a86 | |||
| 989470f0d7 | |||
| ca3c3aa28a | |||
| aa9140e1ac | |||
| 6a8f255878 | |||
| 8ac494da7c | |||
| 1635282868 | |||
| 4587e4a2b3 | |||
| 1135dc7ed3 | |||
| fe01894e7f | |||
| 36400b5678 | |||
| ead3c268e1 |
@@ -0,0 +1,6 @@
|
||||
---
|
||||
fs: patch
|
||||
fs-js: patch
|
||||
---
|
||||
|
||||
Fixed calling `writeFile` with `data: ReadableStream` throws `Invalid argument`
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"opener": 'minor:enhance'
|
||||
"opener-js": 'minor:enhance'
|
||||
---
|
||||
|
||||
Allow reveal multiple items in the file explorer.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
store: minor
|
||||
store-js: minor
|
||||
---
|
||||
|
||||
Allow setting defaults from the JavaScript API
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
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
|
||||
Generated
+37
-62
@@ -207,7 +207,7 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||
|
||||
[[package]]
|
||||
name = "api"
|
||||
version = "2.0.30"
|
||||
version = "2.0.32"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
@@ -233,6 +233,7 @@ dependencies = [
|
||||
"tauri-plugin-shell",
|
||||
"tauri-plugin-store",
|
||||
"tauri-plugin-updater",
|
||||
"tauri-plugin-upload",
|
||||
"tauri-plugin-window-state",
|
||||
"time",
|
||||
"tiny_http",
|
||||
@@ -403,17 +404,6 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-fs"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"blocking",
|
||||
"futures-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.4.0"
|
||||
@@ -1920,11 +1910,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "file-id"
|
||||
version = "0.2.2"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bc904b9bbefcadbd8e3a9fb0d464a9b979de6324c03b3c663e8994f46a5be36"
|
||||
checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3253,9 +3243,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.8"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
|
||||
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
|
||||
dependencies = [
|
||||
"kqueue-sys",
|
||||
"libc",
|
||||
@@ -3701,9 +3691,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"cfg-if",
|
||||
@@ -3730,12 +3720,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "8.0.0"
|
||||
version = "8.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943"
|
||||
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
@@ -3744,14 +3733,14 @@ dependencies = [
|
||||
"mio",
|
||||
"notify-types",
|
||||
"walkdir",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-debouncer-full"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d88b1a7538054351c8258338df7c931a590513fb3745e8c15eb9ff4199b8d1"
|
||||
checksum = "375bd3a138be7bfeff3480e4a623df4cbfb55b79df617c055cd810ba466fa078"
|
||||
dependencies = [
|
||||
"file-id",
|
||||
"log",
|
||||
@@ -6495,7 +6484,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-barcode-scanner"
|
||||
version = "2.3.0"
|
||||
version = "2.4.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
@@ -6546,7 +6535,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-deep-link"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"rust-ini",
|
||||
@@ -6564,7 +6553,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.3.0"
|
||||
version = "2.3.2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"raw-window-handle",
|
||||
@@ -6580,7 +6569,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
@@ -6641,7 +6630,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-http"
|
||||
version = "2.5.0"
|
||||
version = "2.5.1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cookie_store",
|
||||
@@ -6718,7 +6707,7 @@ dependencies = [
|
||||
"log",
|
||||
"maplit",
|
||||
"notify-rust",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
@@ -6769,7 +6758,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-persisted-scope"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bincode",
|
||||
@@ -6823,7 +6812,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-single-instance"
|
||||
version = "2.3.0"
|
||||
version = "2.3.2"
|
||||
dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
@@ -6875,9 +6864,9 @@ dependencies = [
|
||||
"iota-crypto",
|
||||
"iota_stronghold",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
"rand 0.9.0",
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
"rust-argon2 2.1.0",
|
||||
"rusty-fork",
|
||||
"serde",
|
||||
@@ -6920,7 +6909,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-upload"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
@@ -6943,7 +6932,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@@ -6955,7 +6944,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-window-state"
|
||||
version = "2.3.0"
|
||||
version = "2.4.0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"log",
|
||||
@@ -8843,16 +8832,6 @@ dependencies = [
|
||||
"rustix 1.0.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xdg-home"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xkeysym"
|
||||
version = "0.2.1"
|
||||
@@ -8885,13 +8864,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "5.5.0"
|
||||
version = "5.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59c333f648ea1b647bc95dc1d34807c8e25ed7a6feff3394034dc4776054b236"
|
||||
checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
"async-fs",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-process",
|
||||
@@ -8904,17 +8882,15 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-lite",
|
||||
"hex",
|
||||
"nix 0.29.0",
|
||||
"nix 0.30.1",
|
||||
"ordered-stream",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"static_assertions",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"windows-sys 0.59.0",
|
||||
"winnow 0.7.6",
|
||||
"xdg-home",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
@@ -8922,9 +8898,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "5.5.0"
|
||||
version = "5.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f325ad10eb0d0a3eb060203494c3b7ec3162a01a59db75d2deee100339709fc0"
|
||||
checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.3.0",
|
||||
"proc-macro2",
|
||||
@@ -9122,14 +9098,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.4.0"
|
||||
version = "5.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2df9ee044893fcffbdc25de30546edef3e32341466811ca18421e3cd6c5a3ac"
|
||||
checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"url",
|
||||
"winnow 0.7.6",
|
||||
"zvariant_derive",
|
||||
@@ -9138,9 +9113,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "5.4.0"
|
||||
version = "5.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74170caa85b8b84cc4935f2d56a57c7a15ea6185ccdd7eadb57e6edd90f94b2f"
|
||||
checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.3.0",
|
||||
"proc-macro2",
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ schemars = "0.8"
|
||||
dunce = "1"
|
||||
specta = "^2.0.0-rc.16"
|
||||
glob = "0.3"
|
||||
zbus = "5"
|
||||
zbus = "5.9"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
|
||||
@@ -33,7 +33,7 @@ This repo and all plugins require a Rust version of at least **1.77.2**
|
||||
| [store](plugins/store) | Persistent key value storage. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [stronghold](plugins/stronghold) | Encrypted, secure database. | ✅ | ✅ | ✅ | ? | ? |
|
||||
| [updater](plugins/updater) | In-app updates for Tauri applications. | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| [upload](plugins/upload) | Tauri plugin for file uploads through HTTP. | ✅ | ✅ | ✅ | ? | ? |
|
||||
| [upload](plugins/upload) | Tauri plugin for file uploads through HTTP. | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| [websocket](plugins/websocket) | Open a WebSocket connection using a Rust client in JS. | ✅ | ✅ | ✅ | ? | ? |
|
||||
| [window-state](plugins/window-state) | Persist window sizes and positions. | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.0.28]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `dialog-js@2.3.2`
|
||||
|
||||
## \[2.0.27]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `barcode-scanner-js@2.4.0`
|
||||
- Upgraded to `fs-js@2.4.1`
|
||||
- Upgraded to `dialog-js@2.3.1`
|
||||
- Upgraded to `http-js@2.5.1`
|
||||
|
||||
## \[2.0.26]
|
||||
|
||||
### Dependencies
|
||||
|
||||
+12
-11
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "api",
|
||||
"private": true,
|
||||
"version": "2.0.26",
|
||||
"version": "2.0.28",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --clearScreen false",
|
||||
@@ -10,17 +10,17 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "2.6.0",
|
||||
"@tauri-apps/plugin-barcode-scanner": "^2.3.0",
|
||||
"@tauri-apps/api": "2.7.0",
|
||||
"@tauri-apps/plugin-barcode-scanner": "^2.4.0",
|
||||
"@tauri-apps/plugin-biometric": "^2.3.0",
|
||||
"@tauri-apps/plugin-cli": "^2.4.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.3.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.3.2",
|
||||
"@tauri-apps/plugin-fs": "^2.4.1",
|
||||
"@tauri-apps/plugin-geolocation": "^2.2.0",
|
||||
"@tauri-apps/plugin-global-shortcut": "^2.3.0",
|
||||
"@tauri-apps/plugin-haptics": "^2.2.0",
|
||||
"@tauri-apps/plugin-http": "^2.5.0",
|
||||
"@tauri-apps/plugin-http": "^2.5.1",
|
||||
"@tauri-apps/plugin-nfc": "^2.3.0",
|
||||
"@tauri-apps/plugin-notification": "^2.3.0",
|
||||
"@tauri-apps/plugin-opener": "^2.4.0",
|
||||
@@ -29,16 +29,17 @@
|
||||
"@tauri-apps/plugin-shell": "^2.3.0",
|
||||
"@tauri-apps/plugin-store": "^2.3.0",
|
||||
"@tauri-apps/plugin-updater": "^2.9.0",
|
||||
"@tauri-apps/plugin-upload": "^2.3.0",
|
||||
"@zerodevx/svelte-json-view": "1.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/codicon": "^1.2.12",
|
||||
"@iconify-json/ph": "^1.2.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tauri-apps/cli": "2.6.0",
|
||||
"@unocss/extractor-svelte": "^66.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||
"@tauri-apps/cli": "2.7.1",
|
||||
"@unocss/extractor-svelte": "^66.3.3",
|
||||
"svelte": "^5.20.4",
|
||||
"unocss": "^66.0.0",
|
||||
"vite": "^6.2.6"
|
||||
"unocss": "^66.3.3",
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.0.32]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `dialog@2.3.2`
|
||||
|
||||
## \[2.0.31]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `barcode-scanner@2.4.0`
|
||||
- Upgraded to `fs@2.4.1`
|
||||
- Upgraded to `dialog@2.3.1`
|
||||
- Upgraded to `http@2.5.1`
|
||||
|
||||
## \[2.0.30]
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "api"
|
||||
publish = false
|
||||
version = "2.0.30"
|
||||
version = "2.0.32"
|
||||
description = "An example Tauri Application showcasing the api"
|
||||
edition = "2021"
|
||||
rust-version = { workspace = true }
|
||||
@@ -21,15 +21,15 @@ tiny_http = "0.12"
|
||||
time = "0.3"
|
||||
log = { workspace = true }
|
||||
tauri-plugin-log = { path = "../../../plugins/log", version = "2.6.0" }
|
||||
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.0", features = [
|
||||
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.1", features = [
|
||||
"watch",
|
||||
] }
|
||||
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.0" }
|
||||
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.3.0" }
|
||||
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.3.2" }
|
||||
tauri-plugin-http = { path = "../../../plugins/http", features = [
|
||||
"multipart",
|
||||
"cookies",
|
||||
], version = "2.5.0" }
|
||||
], version = "2.5.1" }
|
||||
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.0", features = [
|
||||
"windows7-compat",
|
||||
] }
|
||||
@@ -38,6 +38,7 @@ tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.0" }
|
||||
tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.4.0" }
|
||||
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.0" }
|
||||
tauri-plugin-store = { path = "../../../plugins/store", version = "2.3.0" }
|
||||
tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" }
|
||||
|
||||
[dependencies.tauri]
|
||||
workspace = true
|
||||
@@ -60,7 +61,7 @@ tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.9.0" }
|
||||
tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.2.0" }
|
||||
|
||||
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
|
||||
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.3.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-biometric = { path = "../../../plugins/biometric/", version = "2.3.0" }
|
||||
tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.0" }
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
{
|
||||
"identifier": "opener:allow-open-path",
|
||||
"allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**" }]
|
||||
}
|
||||
},
|
||||
"upload:default"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_store::Builder::default().build())
|
||||
.plugin(tauri_plugin_upload::init())
|
||||
.setup(move |app| {
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import Opener from './views/Opener.svelte'
|
||||
import Store from './views/Store.svelte'
|
||||
import Updater from './views/Updater.svelte'
|
||||
import Upload from './views/Upload.svelte'
|
||||
import Clipboard from './views/Clipboard.svelte'
|
||||
import WebRTC from './views/WebRTC.svelte'
|
||||
import Scanner from './views/Scanner.svelte'
|
||||
@@ -107,6 +108,11 @@
|
||||
component: Updater,
|
||||
icon: 'i-codicon-cloud-download'
|
||||
},
|
||||
{
|
||||
label: 'Upload',
|
||||
component: Upload,
|
||||
icon: 'i-codicon-cloud-upload'
|
||||
},
|
||||
{
|
||||
label: 'Clipboard',
|
||||
component: Clipboard,
|
||||
|
||||
@@ -1,194 +1,190 @@
|
||||
<script>
|
||||
import * as fs from "@tauri-apps/plugin-fs";
|
||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
||||
import { arrayBufferToBase64 } from "../lib/utils";
|
||||
import { onDestroy } from "svelte";
|
||||
import * as fs from '@tauri-apps/plugin-fs'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { arrayBufferToBase64 } from '../lib/utils'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
export let onMessage;
|
||||
export let insecureRenderHtml;
|
||||
const { onMessage, insecureRenderHtml } = $props()
|
||||
|
||||
let path = "";
|
||||
let img;
|
||||
let path = $state('')
|
||||
let img
|
||||
/** @type {fs.FileHandle} */
|
||||
let file;
|
||||
let renameTo;
|
||||
let watchPath = "";
|
||||
let watchDebounceDelay = "0";
|
||||
let watchRecursive = false;
|
||||
let unwatchFn;
|
||||
let unwatchPath = "";
|
||||
let file = $state()
|
||||
let renameTo = $state()
|
||||
let watchPath = $state('')
|
||||
let watchDebounceDelay = $state(0)
|
||||
let watchRecursive = $state(false)
|
||||
/** @type {fs.BaseDirectory | undefined} */
|
||||
let baseDir = $state()
|
||||
let unwatchFn
|
||||
let unwatchPath = ''
|
||||
|
||||
function getDir() {
|
||||
const dirSelect = document.getElementById("dir");
|
||||
return dirSelect.value ? parseInt(dir.value) : null;
|
||||
}
|
||||
|
||||
const DirOptions = Object.keys(fs.BaseDirectory)
|
||||
.filter((key) => isNaN(parseInt(key)))
|
||||
.map((dir) => [dir, fs.BaseDirectory[dir]]);
|
||||
const dirOptions = Object.keys(fs.BaseDirectory).filter((key) =>
|
||||
isNaN(parseInt(key))
|
||||
)
|
||||
|
||||
function open() {
|
||||
fs.open(path, {
|
||||
baseDir: getDir(),
|
||||
baseDir,
|
||||
read: true,
|
||||
write: true,
|
||||
create: true,
|
||||
create: true
|
||||
})
|
||||
.then((f) => {
|
||||
file = f;
|
||||
onMessage(`Opened ${path}`);
|
||||
file = f
|
||||
onMessage(`Opened ${path}`)
|
||||
})
|
||||
.catch(onMessage);
|
||||
.catch(onMessage)
|
||||
}
|
||||
|
||||
function mkdir() {
|
||||
fs.mkdir(path, { baseDir: getDir() })
|
||||
fs.mkdir(path, { baseDir })
|
||||
.then(() => {
|
||||
onMessage(`Created dir ${path}`);
|
||||
onMessage(`Created dir ${path}`)
|
||||
})
|
||||
.catch(onMessage);
|
||||
.catch(onMessage)
|
||||
}
|
||||
|
||||
function remove() {
|
||||
fs.remove(path, { baseDir: getDir() })
|
||||
fs.remove(path, { baseDir })
|
||||
.then(() => {
|
||||
onMessage(`Removed ${path}`);
|
||||
onMessage(`Removed ${path}`)
|
||||
})
|
||||
.catch(onMessage);
|
||||
.catch(onMessage)
|
||||
}
|
||||
|
||||
function rename() {
|
||||
fs.rename(path, renameTo, {
|
||||
oldPathBaseDir: getDir(),
|
||||
newPathBaseDir: getDir(),
|
||||
oldPathBaseDir,
|
||||
newPathBaseDir
|
||||
})
|
||||
.then(() => {
|
||||
onMessage(`Renamed ${path} to ${renameTo}`);
|
||||
onMessage(`Renamed ${path} to ${renameTo}`)
|
||||
})
|
||||
.catch(onMessage);
|
||||
.catch(onMessage)
|
||||
}
|
||||
|
||||
function truncate() {
|
||||
file
|
||||
.truncate(0)
|
||||
.then(() => {
|
||||
onMessage(`Truncated file`);
|
||||
onMessage(`Truncated file`)
|
||||
})
|
||||
.catch(onMessage);
|
||||
.catch(onMessage)
|
||||
}
|
||||
|
||||
function stat() {
|
||||
file
|
||||
.stat()
|
||||
.then((stat) => {
|
||||
onMessage(`File stat ${JSON.stringify(stat)}`);
|
||||
onMessage(`File stat ${JSON.stringify(stat)}`)
|
||||
})
|
||||
.catch(onMessage);
|
||||
.catch(onMessage)
|
||||
}
|
||||
|
||||
function read() {
|
||||
const opts = {
|
||||
baseDir: getDir(),
|
||||
};
|
||||
baseDir
|
||||
}
|
||||
fs.stat(path, opts)
|
||||
.then((stat) => {
|
||||
const isFile = stat.isFile;
|
||||
const isFile = stat.isFile
|
||||
|
||||
const promise = isFile
|
||||
? fs.readFile(path, opts)
|
||||
: fs.readDir(path, opts);
|
||||
: fs.readDir(path, opts)
|
||||
promise
|
||||
.then(function (response) {
|
||||
if (isFile) {
|
||||
if (path.includes(".png") || path.includes(".jpg")) {
|
||||
if (path.includes('.png') || path.includes('.jpg')) {
|
||||
arrayBufferToBase64(
|
||||
new Uint8Array(response),
|
||||
function (base64) {
|
||||
const src = "data:image/png;base64," + base64;
|
||||
insecureRenderHtml('<img src="' + src + '"></img>');
|
||||
const src = 'data:image/png;base64,' + base64
|
||||
insecureRenderHtml('<img src="' + src + '"></img>')
|
||||
}
|
||||
);
|
||||
)
|
||||
} else {
|
||||
const value = String.fromCharCode.apply(null, response);
|
||||
const value = String.fromCharCode.apply(null, response)
|
||||
insecureRenderHtml(
|
||||
'<textarea id="file-response"></textarea><button id="file-save">Save</button>'
|
||||
);
|
||||
)
|
||||
setTimeout(() => {
|
||||
const fileInput = document.getElementById("file-response");
|
||||
fileInput.value = value;
|
||||
const fileInput = document.getElementById('file-response')
|
||||
fileInput.value = value
|
||||
document
|
||||
.getElementById("file-save")
|
||||
.addEventListener("click", function () {
|
||||
.getElementById('file-save')
|
||||
.addEventListener('click', function () {
|
||||
fs.writeTextFile(path, fileInput.value, {
|
||||
baseDir: getDir(),
|
||||
}).catch(onMessage);
|
||||
});
|
||||
});
|
||||
baseDir
|
||||
}).catch(onMessage)
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
onMessage(response);
|
||||
onMessage(response)
|
||||
}
|
||||
})
|
||||
.catch(onMessage);
|
||||
.catch(onMessage)
|
||||
})
|
||||
.catch(onMessage);
|
||||
.catch(onMessage)
|
||||
}
|
||||
|
||||
function setSrc() {
|
||||
img.src = convertFileSrc(path);
|
||||
img.src = convertFileSrc(path)
|
||||
}
|
||||
|
||||
function watch() {
|
||||
unwatch();
|
||||
unwatch()
|
||||
if (watchPath) {
|
||||
onMessage(`Watching ${watchPath} for changes`);
|
||||
onMessage(`Watching ${watchPath} for changes`)
|
||||
let options = {
|
||||
recursive: watchRecursive,
|
||||
delayMs: parseInt(watchDebounceDelay),
|
||||
};
|
||||
delayMs: watchDebounceDelay
|
||||
}
|
||||
if (options.delayMs === 0) {
|
||||
fs.watchImmediate(watchPath, onMessage, options)
|
||||
.then((fn) => {
|
||||
unwatchFn = fn;
|
||||
unwatchPath = watchPath;
|
||||
unwatchFn = fn
|
||||
unwatchPath = watchPath
|
||||
})
|
||||
.catch(onMessage);
|
||||
.catch(onMessage)
|
||||
} else {
|
||||
fs.watch(watchPath, onMessage, options)
|
||||
.then((fn) => {
|
||||
unwatchFn = fn;
|
||||
unwatchPath = watchPath;
|
||||
unwatchFn = fn
|
||||
unwatchPath = watchPath
|
||||
})
|
||||
.catch(onMessage);
|
||||
.catch(onMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function unwatch() {
|
||||
if (unwatchFn) {
|
||||
onMessage(`Stopped watching ${unwatchPath} for changes`);
|
||||
unwatchFn();
|
||||
onMessage(`Stopped watching ${unwatchPath} for changes`)
|
||||
unwatchFn()
|
||||
}
|
||||
unwatchFn = undefined;
|
||||
unwatchPath = undefined;
|
||||
unwatchFn = undefined
|
||||
unwatchPath = undefined
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
if (file) {
|
||||
file.close();
|
||||
file.close()
|
||||
}
|
||||
if (unwatchFn) {
|
||||
unwatchFn();
|
||||
unwatchFn()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="flex gap-1">
|
||||
<select class="input" id="dir">
|
||||
<option value="">None</option>
|
||||
{#each DirOptions as dir}
|
||||
<option value={dir[1]}>{dir[0]}</option>
|
||||
<select class="input" bind:value={baseDir}>
|
||||
<option value={undefined} selected>None</option>
|
||||
{#each dirOptions as dir}
|
||||
<option value={fs.BaseDirectory[dir]}>{dir}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<input
|
||||
@@ -199,20 +195,20 @@
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<button class="btn" on:click={open}>Open</button>
|
||||
<button class="btn" on:click={read}>Read</button>
|
||||
<button class="btn" on:click={mkdir}>Mkdir</button>
|
||||
<button class="btn" on:click={remove}>Remove</button>
|
||||
<button class="btn" onclick={open}>Open</button>
|
||||
<button class="btn" onclick={read}>Read</button>
|
||||
<button class="btn" onclick={mkdir}>Mkdir</button>
|
||||
<button class="btn" onclick={remove}>Remove</button>
|
||||
<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" />
|
||||
</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>
|
||||
{#if file}
|
||||
<div>
|
||||
<button class="btn" on:click={truncate}>Truncate</button>
|
||||
<button class="btn" on:click={stat}>Stat</button>
|
||||
<button class="btn" onclick={truncate}>Truncate</button>
|
||||
<button class="btn" onclick={stat}>Stat</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -241,8 +237,8 @@
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<button class="btn" on:click={watch}>Watch</button>
|
||||
<button class="btn" on:click={unwatch}>Unwatch</button>
|
||||
<button class="btn" onclick={watch}>Watch</button>
|
||||
<button class="btn" onclick={unwatch}>Unwatch</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,71 +1,85 @@
|
||||
<script>
|
||||
import { LazyStore } from "@tauri-apps/plugin-store";
|
||||
import { onMount } from "svelte";
|
||||
import { appDataDir, resolve } from '@tauri-apps/api/path'
|
||||
import { LazyStore } from '@tauri-apps/plugin-store'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
export let onMessage;
|
||||
let { onMessage } = $props()
|
||||
|
||||
let key;
|
||||
let value;
|
||||
let key = $state()
|
||||
let value = $state()
|
||||
|
||||
let store = new LazyStore("cache.json");
|
||||
let cache = {};
|
||||
const storeName = 'cache.json'
|
||||
|
||||
let store = new LazyStore(storeName)
|
||||
let path = $state('')
|
||||
let cache = $state({})
|
||||
|
||||
async function refreshEntries() {
|
||||
try {
|
||||
const values = await store.entries();
|
||||
cache = {};
|
||||
const values = await store.entries()
|
||||
cache = {}
|
||||
for (const [key, value] of values) {
|
||||
cache[key] = value;
|
||||
cache[key] = value
|
||||
}
|
||||
} catch (error) {
|
||||
onMessage(error);
|
||||
onMessage(error)
|
||||
}
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await refreshEntries();
|
||||
});
|
||||
path = await resolve(await appDataDir(), storeName)
|
||||
await refreshEntries()
|
||||
})
|
||||
|
||||
async function write(key, value) {
|
||||
try {
|
||||
if (value) {
|
||||
await store.set(key, value);
|
||||
await store.set(key, value)
|
||||
} else {
|
||||
await store.delete(key);
|
||||
await store.delete(key)
|
||||
}
|
||||
const v = await store.get(key);
|
||||
const v = await store.get(key)
|
||||
if (v === undefined) {
|
||||
delete cache[key];
|
||||
cache = cache;
|
||||
delete cache[key]
|
||||
cache = cache
|
||||
} else {
|
||||
cache[key] = v;
|
||||
cache[key] = v
|
||||
}
|
||||
} catch (error) {
|
||||
onMessage(error);
|
||||
onMessage(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
try {
|
||||
await store.reset();
|
||||
await store.reset()
|
||||
} catch (error) {
|
||||
onMessage(error);
|
||||
onMessage(error)
|
||||
}
|
||||
await refreshEntries();
|
||||
await refreshEntries()
|
||||
}
|
||||
|
||||
async function reload() {
|
||||
try {
|
||||
await store.reload({ overrideDefaults: true })
|
||||
} catch (error) {
|
||||
onMessage(error)
|
||||
}
|
||||
await refreshEntries()
|
||||
}
|
||||
|
||||
async function close() {
|
||||
try {
|
||||
await store.close();
|
||||
onMessage("Store is now closed, any new operations will error out");
|
||||
await store.close()
|
||||
onMessage('Store is now closed, any new operations will error out')
|
||||
} catch (error) {
|
||||
onMessage(error);
|
||||
onMessage(error)
|
||||
}
|
||||
}
|
||||
|
||||
function reopen() {
|
||||
store = new LazyStore("cache.json");
|
||||
onMessage("We made a new `LazyStore` instance, operations will now work");
|
||||
store = new LazyStore(storeName)
|
||||
onMessage('We made a new `LazyStore` instance, operations will now work')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -82,14 +96,17 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="btn" on:click={() => write(key, value)}>Write</button>
|
||||
<button class="btn" on:click={() => reset()}>Reset</button>
|
||||
<button class="btn" on:click={() => close()}>Close</button>
|
||||
<button class="btn" on:click={() => reopen()}>Re-open</button>
|
||||
<button class="btn" onclick={() => write(key, value)}>Write</button>
|
||||
<button class="btn" onclick={() => reset()}>Reset</button>
|
||||
<button class="btn" onclick={() => reload()}>Reload</button>
|
||||
<button class="btn" onclick={() => close()}>Close</button>
|
||||
<button class="btn" onclick={() => reopen()}>Re-open</button>
|
||||
</div>
|
||||
<div>Store at <code>{path}</code> on disk</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Store Values</h2>
|
||||
{#each Object.entries(cache) as [k, v]}
|
||||
<div>{k} = {v}</div>
|
||||
{/each}
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
<script>
|
||||
import { download, upload } from '@tauri-apps/plugin-upload'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { JsonView } from '@zerodevx/svelte-json-view'
|
||||
import { appDataDir } from '@tauri-apps/api/path'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
export let onMessage
|
||||
|
||||
let downloadUrl = 'https://httpbin.org/json'
|
||||
let downloadFolder = ''
|
||||
let downloadPath = ''
|
||||
let downloadProgress = null
|
||||
let downloadResult = null
|
||||
let isDownloading = false
|
||||
|
||||
let uploadUrl = 'https://httpbin.org/post'
|
||||
let uploadFilePath = ''
|
||||
let uploadProgress = null
|
||||
let uploadResult = null
|
||||
let isUploading = false
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const defaultDir = await appDataDir()
|
||||
if (!downloadFolder) {
|
||||
downloadFolder = defaultDir
|
||||
updateDownloadPath()
|
||||
}
|
||||
} catch (error) {
|
||||
onMessage({ error: `Failed to get default directory: ${error.toString()}` })
|
||||
}
|
||||
})
|
||||
|
||||
async function selectDownloadFolder() {
|
||||
try {
|
||||
const selected = await open({
|
||||
directory: true,
|
||||
multiple: false,
|
||||
defaultPath: downloadFolder || undefined
|
||||
})
|
||||
if (selected) {
|
||||
downloadFolder = selected
|
||||
updateDownloadPath()
|
||||
}
|
||||
} catch (error) {
|
||||
onMessage({ error: error.toString() })
|
||||
}
|
||||
}
|
||||
|
||||
function getFilenameFromUrl(url) {
|
||||
try {
|
||||
const urlObj = new URL(url)
|
||||
let pathname = urlObj.pathname
|
||||
|
||||
// Remove leading slash
|
||||
if (pathname.startsWith('/')) {
|
||||
pathname = pathname.substring(1)
|
||||
}
|
||||
|
||||
// If pathname is empty or ends with slash, use a default name
|
||||
if (!pathname || pathname.endsWith('/')) {
|
||||
return 'downloaded-file.json'
|
||||
}
|
||||
|
||||
// Extract filename from pathname
|
||||
const segments = pathname.split('/')
|
||||
let filename = segments[segments.length - 1]
|
||||
|
||||
// If no extension, try to infer from URL or use default
|
||||
if (!filename.includes('.')) {
|
||||
// Check if URL suggests a file type
|
||||
if (url.includes('json') || urlObj.searchParams.has('format') && urlObj.searchParams.get('format') === 'json') {
|
||||
filename += '.json'
|
||||
} else if (url.includes('xml')) {
|
||||
filename += '.xml'
|
||||
} else if (url.includes('csv')) {
|
||||
filename += '.csv'
|
||||
} else {
|
||||
filename += '.txt'
|
||||
}
|
||||
}
|
||||
|
||||
return filename
|
||||
} catch (error) {
|
||||
return 'downloaded-file.json'
|
||||
}
|
||||
}
|
||||
|
||||
function updateDownloadPath() {
|
||||
if (downloadFolder && downloadUrl) {
|
||||
const filename = getFilenameFromUrl(downloadUrl)
|
||||
downloadPath = `${downloadFolder}/${filename}`
|
||||
} else {
|
||||
downloadPath = ''
|
||||
}
|
||||
}
|
||||
|
||||
// Update download path when URL changes
|
||||
$: if (downloadUrl) {
|
||||
updateDownloadPath()
|
||||
}
|
||||
|
||||
async function selectUploadFile() {
|
||||
try {
|
||||
const selected = await open({
|
||||
directory: false,
|
||||
multiple: false
|
||||
})
|
||||
if (selected) {
|
||||
uploadFilePath = selected
|
||||
}
|
||||
} catch (error) {
|
||||
onMessage({ error: error.toString() })
|
||||
}
|
||||
}
|
||||
|
||||
async function startDownload() {
|
||||
if (!downloadUrl || !downloadFolder) {
|
||||
onMessage({ error: 'Please provide both URL and download folder' })
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure download path is updated
|
||||
updateDownloadPath()
|
||||
|
||||
if (!downloadPath) {
|
||||
onMessage({ error: 'Could not generate download path' })
|
||||
return
|
||||
}
|
||||
|
||||
isDownloading = true
|
||||
downloadProgress = null
|
||||
downloadResult = null
|
||||
|
||||
try {
|
||||
await download(
|
||||
downloadUrl,
|
||||
downloadPath,
|
||||
(progress) => {
|
||||
downloadProgress = {
|
||||
progress: progress.progress,
|
||||
progressTotal: progress.progressTotal,
|
||||
total: progress.total,
|
||||
transferSpeed: progress.transferSpeed,
|
||||
percentage: progress.total > 0 ? Math.round((progress.progressTotal / progress.total) * 100) : 0
|
||||
}
|
||||
},
|
||||
new Map([
|
||||
['User-Agent', 'Tauri Upload Plugin Demo']
|
||||
])
|
||||
)
|
||||
|
||||
downloadResult = {
|
||||
success: true,
|
||||
message: `File downloaded successfully to: ${downloadPath}`,
|
||||
finalProgress: downloadProgress
|
||||
}
|
||||
|
||||
onMessage({
|
||||
type: 'download',
|
||||
result: downloadResult
|
||||
})
|
||||
} catch (error) {
|
||||
downloadResult = {
|
||||
success: false,
|
||||
error: error.toString()
|
||||
}
|
||||
onMessage({ error: error.toString() })
|
||||
} finally {
|
||||
isDownloading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function startUpload() {
|
||||
if (!uploadUrl || !uploadFilePath) {
|
||||
onMessage({ error: 'Please provide both URL and file path' })
|
||||
return
|
||||
}
|
||||
|
||||
isUploading = true
|
||||
uploadProgress = null
|
||||
uploadResult = null
|
||||
|
||||
try {
|
||||
const response = await upload(
|
||||
uploadUrl,
|
||||
uploadFilePath,
|
||||
(progress) => {
|
||||
uploadProgress = {
|
||||
progress: progress.progress,
|
||||
progressTotal: progress.progressTotal,
|
||||
total: progress.total,
|
||||
transferSpeed: progress.transferSpeed,
|
||||
percentage: progress.total > 0 ? Math.round((progress.progressTotal / progress.total) * 100) : 0
|
||||
}
|
||||
},
|
||||
new Map([
|
||||
['User-Agent', 'Tauri Upload Plugin Demo']
|
||||
])
|
||||
)
|
||||
|
||||
uploadResult = {
|
||||
success: true,
|
||||
response: response,
|
||||
finalProgress: uploadProgress
|
||||
}
|
||||
|
||||
onMessage({
|
||||
type: 'upload',
|
||||
result: uploadResult
|
||||
})
|
||||
} catch (error) {
|
||||
uploadResult = {
|
||||
success: false,
|
||||
error: error.toString()
|
||||
}
|
||||
onMessage({ error: error.toString() })
|
||||
} finally {
|
||||
isUploading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<h3 class="text-lg font-semibold mb-4 text-gray-800">File Download</h3>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label for="download-url" class="block text-sm font-medium text-gray-700 mb-1">Download URL:</label>
|
||||
<input
|
||||
id="download-url"
|
||||
bind:value={downloadUrl}
|
||||
type="url"
|
||||
placeholder="https://example.com/file.json"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
disabled={isDownloading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="download-folder" class="block text-sm font-medium text-gray-700 mb-1">Download folder:</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
id="download-folder"
|
||||
bind:value={downloadFolder}
|
||||
type="text"
|
||||
placeholder="Select download folder..."
|
||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
disabled={isDownloading}
|
||||
/>
|
||||
<button
|
||||
on:click={selectDownloadFolder}
|
||||
class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 disabled:opacity-50"
|
||||
disabled={isDownloading}
|
||||
>
|
||||
Browse
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if downloadPath}
|
||||
<div class="bg-blue-50 border border-blue-200 p-3 rounded-md">
|
||||
<div class="text-sm text-blue-800">
|
||||
<strong>File will be saved as:</strong>
|
||||
<div class="font-mono text-xs mt-1 break-all">{downloadPath}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
on:click={startDownload}
|
||||
class="w-full px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled={isDownloading || !downloadUrl || !downloadFolder}
|
||||
>
|
||||
{isDownloading ? 'Downloading...' : 'Download File'}
|
||||
</button>
|
||||
|
||||
{#if downloadProgress}
|
||||
<div class="bg-white p-3 rounded border">
|
||||
<div class="flex justify-between text-sm text-gray-600 mb-1">
|
||||
<span>Progress: {downloadProgress.percentage}%</span>
|
||||
<span>Speed: {Math.round(downloadProgress.transferSpeed / 1024)} KB/s</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
class="bg-blue-500 h-2 rounded-full transition-all duration-300"
|
||||
style="width: {downloadProgress.percentage}%"
|
||||
></div>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
{Math.round(downloadProgress.progressTotal / 1024)} KB / {Math.round(downloadProgress.total / 1024)} KB
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if downloadResult}
|
||||
<div class="bg-white p-3 rounded border">
|
||||
<JsonView json={downloadResult} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-gray-50 p-4 rounded-lg">
|
||||
<h3 class="text-lg font-semibold mb-4 text-gray-800">File Upload</h3>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label for="upload-url" class="block text-sm font-medium text-gray-700 mb-1">Upload URL:</label>
|
||||
<input
|
||||
id="upload-url"
|
||||
bind:value={uploadUrl}
|
||||
type="url"
|
||||
placeholder="https://httpbin.org/post"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
disabled={isUploading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="upload-file" class="block text-sm font-medium text-gray-700 mb-1">File to upload:</label>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
id="upload-file"
|
||||
bind:value={uploadFilePath}
|
||||
type="text"
|
||||
placeholder="Select file to upload..."
|
||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
disabled={isUploading}
|
||||
/>
|
||||
<button
|
||||
on:click={selectUploadFile}
|
||||
class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 disabled:opacity-50"
|
||||
disabled={isUploading}
|
||||
>
|
||||
Browse
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
on:click={startUpload}
|
||||
class="w-full px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled={isUploading || !uploadUrl || !uploadFilePath}
|
||||
>
|
||||
{isUploading ? 'Uploading...' : 'Upload File'}
|
||||
</button>
|
||||
|
||||
{#if uploadProgress}
|
||||
<div class="bg-white p-3 rounded border">
|
||||
<div class="flex justify-between text-sm text-gray-600 mb-1">
|
||||
<span>Progress: {uploadProgress.percentage}%</span>
|
||||
<span>Speed: {Math.round(uploadProgress.transferSpeed / 1024)} KB/s</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||
<div
|
||||
class="bg-green-500 h-2 rounded-full transition-all duration-300"
|
||||
style="width: {uploadProgress.percentage}%"
|
||||
></div>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
{Math.round(uploadProgress.progressTotal / 1024)} KB / {Math.round(uploadProgress.total / 1024)} KB
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if uploadResult}
|
||||
<div class="bg-white p-3 rounded border">
|
||||
<JsonView json={uploadResult} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+8
-8
@@ -11,19 +11,19 @@
|
||||
"example:api:dev": "pnpm run --filter \"api\" tauri dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.29.0",
|
||||
"@eslint/js": "9.32.0",
|
||||
"@rollup/plugin-node-resolve": "16.0.1",
|
||||
"@rollup/plugin-terser": "0.4.4",
|
||||
"@rollup/plugin-typescript": "12.1.3",
|
||||
"@rollup/plugin-typescript": "12.1.4",
|
||||
"covector": "^0.12.4",
|
||||
"eslint": "9.29.0",
|
||||
"eslint-config-prettier": "10.1.5",
|
||||
"eslint": "9.32.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-security": "3.0.1",
|
||||
"prettier": "3.6.1",
|
||||
"rollup": "4.44.0",
|
||||
"prettier": "3.6.2",
|
||||
"rollup": "4.46.2",
|
||||
"tslib": "2.8.1",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.35.0"
|
||||
"typescript": "5.9.2",
|
||||
"typescript-eslint": "8.39.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
|
||||
@@ -33,21 +33,12 @@ tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspac
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-autostart
|
||||
# or
|
||||
npm add @tauri-apps/plugin-autostart
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-autostart
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-autostart#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-autostart#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-autostart#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.4.0]
|
||||
|
||||
- [`aa9140e1`](https://github.com/tauri-apps/plugins-workspace/commit/aa9140e1ac239ab9f015f92b2ed52bbf0eda7c12) ([#2437](https://github.com/tauri-apps/plugins-workspace/pull/2437) by [@enkhjile](https://github.com/tauri-apps/plugins-workspace/../../enkhjile)) Added support for GS1 DataBar on iOS 15.4+
|
||||
|
||||
## \[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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-barcode-scanner"
|
||||
version = "2.3.0"
|
||||
version = "2.4.0"
|
||||
description = "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
@@ -33,21 +33,12 @@ tauri-plugin-barcode-scanner = { git = "https://github.com/tauri-apps/plugins-wo
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-barcode-scanner
|
||||
# or
|
||||
npm add @tauri-apps/plugin-barcode-scanner
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-barcode-scanner
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-barcode-scanner#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-barcode-scanner#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-barcode-scanner#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -1 +1 @@
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_BARCODE_SCANNER__=function(n){"use strict";async function e(n,e={},r){return window.__TAURI_INTERNALS__.invoke(n,e,r)}var r;return"function"==typeof SuppressedError&&SuppressedError,n.Format=void 0,(r=n.Format||(n.Format={})).QRCode="QR_CODE",r.UPC_A="UPC_A",r.UPC_E="UPC_E",r.EAN8="EAN_8",r.EAN13="EAN_13",r.Code39="CODE_39",r.Code93="CODE_93",r.Code128="CODE_128",r.Codabar="CODABAR",r.ITF="ITF",r.Aztec="AZTEC",r.DataMatrix="DATA_MATRIX",r.PDF417="PDF_417",n.cancel=async function(){await e("plugin:barcode-scanner|cancel")},n.checkPermissions=async function(){return await async function(n){return e(`plugin:${n}|check_permissions`)}("barcode-scanner").then((n=>n.camera))},n.openAppSettings=async function(){await e("plugin:barcode-scanner|open_app_settings")},n.requestPermissions=async function(){return await async function(n){return e(`plugin:${n}|request_permissions`)}("barcode-scanner").then((n=>n.camera))},n.scan=async function(n){return await e("plugin:barcode-scanner|scan",{...n})},n}({});Object.defineProperty(window.__TAURI__,"barcodeScanner",{value:__TAURI_PLUGIN_BARCODE_SCANNER__})}
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_BARCODE_SCANNER__=function(n){"use strict";async function a(n,a={},e){return window.__TAURI_INTERNALS__.invoke(n,a,e)}var e;return"function"==typeof SuppressedError&&SuppressedError,n.Format=void 0,(e=n.Format||(n.Format={})).QRCode="QR_CODE",e.UPC_A="UPC_A",e.UPC_E="UPC_E",e.EAN8="EAN_8",e.EAN13="EAN_13",e.Code39="CODE_39",e.Code93="CODE_93",e.Code128="CODE_128",e.Codabar="CODABAR",e.ITF="ITF",e.Aztec="AZTEC",e.DataMatrix="DATA_MATRIX",e.PDF417="PDF_417",e.GS1DataBar="GS1_DATA_BAR",e.GS1DataBarLimited="GS1_DATA_BAR_LIMITED",e.GS1DataBarExpanded="GS1_DATA_BAR_EXPANDED",n.cancel=async function(){await a("plugin:barcode-scanner|cancel")},n.checkPermissions=async function(){return await async function(n){return a(`plugin:${n}|check_permissions`)}("barcode-scanner").then((n=>n.camera))},n.openAppSettings=async function(){await a("plugin:barcode-scanner|open_app_settings")},n.requestPermissions=async function(){return await async function(n){return a(`plugin:${n}|request_permissions`)}("barcode-scanner").then((n=>n.camera))},n.scan=async function(n){return await a("plugin:barcode-scanner|scan",{...n})},n}({});Object.defineProperty(window.__TAURI__,"barcodeScanner",{value:__TAURI_PLUGIN_BARCODE_SCANNER__})}
|
||||
|
||||
@@ -12,6 +12,9 @@ export type { PermissionState } from '@tauri-apps/api/core'
|
||||
|
||||
export enum Format {
|
||||
QRCode = 'QR_CODE',
|
||||
/**
|
||||
* Not supported on iOS.
|
||||
*/
|
||||
UPC_A = 'UPC_A',
|
||||
UPC_E = 'UPC_E',
|
||||
EAN8 = 'EAN_8',
|
||||
@@ -19,11 +22,26 @@ export enum Format {
|
||||
Code39 = 'CODE_39',
|
||||
Code93 = 'CODE_93',
|
||||
Code128 = 'CODE_128',
|
||||
/**
|
||||
* Not supported on iOS.
|
||||
*/
|
||||
Codabar = 'CODABAR',
|
||||
ITF = 'ITF',
|
||||
Aztec = 'AZTEC',
|
||||
DataMatrix = 'DATA_MATRIX',
|
||||
PDF417 = 'PDF_417'
|
||||
PDF417 = 'PDF_417',
|
||||
/**
|
||||
* Not supported on Android. Requires iOS 15.4+
|
||||
*/
|
||||
GS1DataBar = 'GS1_DATA_BAR',
|
||||
/**
|
||||
* Not supported on Android. Requires iOS 15.4+
|
||||
*/
|
||||
GS1DataBarLimited = 'GS1_DATA_BAR_LIMITED',
|
||||
/**
|
||||
* Not supported on Android. Requires iOS 15.4+
|
||||
*/
|
||||
GS1DataBarExpanded = 'GS1_DATA_BAR_EXPANDED'
|
||||
}
|
||||
|
||||
export interface ScanOptions {
|
||||
|
||||
@@ -27,8 +27,11 @@ enum SupportedFormat: String, CaseIterable, Decodable {
|
||||
case DATA_MATRIX
|
||||
case PDF_417
|
||||
case QR_CODE
|
||||
case GS1_DATA_BAR
|
||||
case GS1_DATA_BAR_LIMITED
|
||||
case GS1_DATA_BAR_EXPANDED
|
||||
|
||||
var value: AVMetadataObject.ObjectType {
|
||||
var value: AVMetadataObject.ObjectType? {
|
||||
switch self {
|
||||
case .UPC_E: return AVMetadataObject.ObjectType.upce
|
||||
case .EAN_8: return AVMetadataObject.ObjectType.ean8
|
||||
@@ -41,6 +44,24 @@ enum SupportedFormat: String, CaseIterable, Decodable {
|
||||
case .DATA_MATRIX: return AVMetadataObject.ObjectType.dataMatrix
|
||||
case .PDF_417: return AVMetadataObject.ObjectType.pdf417
|
||||
case .QR_CODE: return AVMetadataObject.ObjectType.qr
|
||||
case .GS1_DATA_BAR:
|
||||
if #available(iOS 15.4, *) {
|
||||
return AVMetadataObject.ObjectType.gs1DataBar
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .GS1_DATA_BAR_LIMITED:
|
||||
if #available(iOS 15.4, *) {
|
||||
return AVMetadataObject.ObjectType.gs1DataBarLimited
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .GS1_DATA_BAR_EXPANDED:
|
||||
if #available(iOS 15.4, *) {
|
||||
return AVMetadataObject.ObjectType.gs1DataBarExpanded
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,13 +263,20 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate {
|
||||
scanFormats = [AVMetadataObject.ObjectType]()
|
||||
|
||||
(args.formats ?? []).forEach { format in
|
||||
scanFormats.append(format.value)
|
||||
if let formatValue = format.value {
|
||||
scanFormats.append(formatValue)
|
||||
} else {
|
||||
invoke.reject("Unsupported barcode format on this iOS version: \(format)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if scanFormats.count == 0 {
|
||||
for supportedFormat in SupportedFormat.allCases {
|
||||
scanFormats.append(supportedFormat.value)
|
||||
}
|
||||
if scanFormats.isEmpty {
|
||||
for supportedFormat in SupportedFormat.allCases {
|
||||
if let formatValue = supportedFormat.value {
|
||||
scanFormats.append(formatValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.metaOutput!.metadataObjectTypes = self.scanFormats
|
||||
|
||||
@@ -52,6 +52,15 @@ func discoverCaptureDevices() -> [AVCaptureDevice] {
|
||||
}
|
||||
|
||||
func formatStringFromMetadata(_ type: AVMetadataObject.ObjectType) -> String {
|
||||
if #available(iOS 15.4, *) {
|
||||
if type == .gs1DataBar {
|
||||
return "GS1_DATA_BAR"
|
||||
} else if type == .gs1DataBarLimited {
|
||||
return "GS1_DATA_BAR_LIMITED"
|
||||
} else if type == .gs1DataBarExpanded {
|
||||
return "GS1_DATA_BAR_EXPANDED"
|
||||
}
|
||||
}
|
||||
switch type {
|
||||
case AVMetadataObject.ObjectType.upce:
|
||||
return "UPC_E"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@tauri-apps/plugin-barcode-scanner",
|
||||
"version": "2.3.0",
|
||||
"version": "2.4.0",
|
||||
"description": "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"authors": [
|
||||
|
||||
@@ -33,8 +33,6 @@ tauri-plugin-biometric = { git = "https://github.com/tauri-apps/plugins-workspac
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
<!-- Add the branch for installations using git! -->
|
||||
|
||||
```sh
|
||||
@@ -43,13 +41,6 @@ pnpm add @tauri-apps/plugin-biometric
|
||||
npm add @tauri-apps/plugin-biometric
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-biometric
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-biometric#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-biometric#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-biometric#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -34,21 +34,12 @@ tauri-plugin-cli = { git = "https://github.com/tauri-apps/plugins-workspace", br
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-cli
|
||||
# or
|
||||
npm add @tauri-apps/plugin-cli
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-cli
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-cli#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-cli#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-cli#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -33,21 +33,12 @@ tauri-plugin-clipboard-manager = { git = "https://github.com/tauri-apps/plugins-
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-clipboard-manager
|
||||
# or
|
||||
npm add @tauri-apps/plugin-clipboard-manager
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-clipboard-manager
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-clipboard-manager#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-clipboard-manager#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-clipboard-manager#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## \[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
|
||||
Fix duplicate protocols added to MimeType section in .desktop files on linux
|
||||
|
||||
## \[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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-deep-link"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
description = "Set your Tauri application as the default handler for an URL"
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
@@ -17,9 +17,9 @@ targets = ["x86_64-linux-android"]
|
||||
[package.metadata.platforms.support]
|
||||
windows = { level = "full", notes = "" }
|
||||
linux = { level = "full", notes = "" }
|
||||
macos = { level = "partial", notes = "Runtime deep link registration is not supported" }
|
||||
android = { level = "partial", notes = "Runtime deep link registration is not supported" }
|
||||
ios = { level = "partial", notes = "Runtime deep link registration is not supported" }
|
||||
macos = { level = "partial", notes = "Deep links must be registered in config. Dynamic registration at runtime is not supported." }
|
||||
android = { level = "partial", notes = "Deep links must be registered in config. Dynamic registration at runtime is not supported." }
|
||||
ios = { level = "partial", notes = "Deep links must be registered in config. Dynamic registration at runtime is not supported." }
|
||||
|
||||
[build-dependencies]
|
||||
serde = { workspace = true }
|
||||
|
||||
@@ -33,21 +33,12 @@ tauri-plugin-deep-link = { git = "https://github.com/tauri-apps/plugins-workspac
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-deep-link
|
||||
# or
|
||||
npm add @tauri-apps/plugin-deep-link
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-deep-link
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-deep-link#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-deep-link#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-deep-link#v2
|
||||
```
|
||||
|
||||
## Setting up
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.2.4]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `deep-link-js@2.4.1`
|
||||
|
||||
## \[2.2.3]
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "deep-link-example",
|
||||
"private": true,
|
||||
"version": "2.2.3",
|
||||
"version": "2.2.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -10,12 +10,12 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "2.6.0",
|
||||
"@tauri-apps/plugin-deep-link": "2.4.0"
|
||||
"@tauri-apps/api": "2.7.0",
|
||||
"@tauri-apps/plugin-deep-link": "2.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.6.0",
|
||||
"@tauri-apps/cli": "2.7.1",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.2.6"
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@tauri-apps/plugin-deep-link",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "Set your Tauri application as the default handler for an URL",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"authors": [
|
||||
|
||||
@@ -303,12 +303,14 @@ mod imp {
|
||||
|
||||
if let Ok(mut desktop_file) = ini::Ini::load_from_file(&target_file) {
|
||||
if let Some(section) = desktop_file.section_mut(Some("Desktop Entry")) {
|
||||
let old_mimes = section.remove("MimeType");
|
||||
section.append(
|
||||
"MimeType",
|
||||
format!("{mime_type};{}", old_mimes.unwrap_or_default()),
|
||||
);
|
||||
desktop_file.write_to_file(&target_file)?;
|
||||
// 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();
|
||||
|
||||
if !old_mimes.split(';').any(|mime| mime == mime_type) {
|
||||
section.append("MimeType", format!("{mime_type};{old_mimes}"));
|
||||
desktop_file.write_to_file(&target_file)?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut file = File::create(target_file)?;
|
||||
@@ -333,7 +335,7 @@ mod imp {
|
||||
.status()?;
|
||||
|
||||
Command::new("xdg-mime")
|
||||
.args(["default", &file_name, _protocol.as_ref()])
|
||||
.args(["default", &file_name, mime_type.as_str()])
|
||||
.status()?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## \[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.
|
||||
|
||||
## \[2.3.1]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `fs-js@2.4.1`
|
||||
|
||||
## \[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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.3.0"
|
||||
version = "2.3.2"
|
||||
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -34,7 +34,7 @@ tauri = { workspace = true }
|
||||
log = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
url = { workspace = true }
|
||||
tauri-plugin-fs = { path = "../fs", version = "2.4.0" }
|
||||
tauri-plugin-fs = { path = "../fs", version = "2.4.1" }
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
tauri = { workspace = true, features = ["wry"] }
|
||||
|
||||
@@ -33,21 +33,12 @@ tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace",
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-dialog
|
||||
# or
|
||||
npm add @tauri-apps/plugin-dialog
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-dialog
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-dialog#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-dialog#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-dialog#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -56,20 +56,18 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
|
||||
try {
|
||||
val args = invoke.parseArgs(FilePickerOptions::class.java)
|
||||
val parsedTypes = parseFiltersOption(args.filters)
|
||||
|
||||
val intent = if (parsedTypes.isNotEmpty()) {
|
||||
val intent = Intent(Intent.ACTION_PICK)
|
||||
setIntentMimeTypes(intent, parsedTypes)
|
||||
intent
|
||||
} else {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
intent.type = "*/*"
|
||||
intent
|
||||
|
||||
// TODO: ACTION_OPEN_DOCUMENT ??
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
intent.type = "*/*"
|
||||
|
||||
if (parsedTypes.isNotEmpty()) {
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes)
|
||||
}
|
||||
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, args.multiple ?: false)
|
||||
|
||||
|
||||
startActivityForResult(invoke, intent, "filePickerResult")
|
||||
} catch (ex: Exception) {
|
||||
val message = ex.message ?: "Failed to pick file"
|
||||
@@ -115,7 +113,7 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
|
||||
callResult.put("files", JSArray.from(uris.toTypedArray()))
|
||||
return callResult
|
||||
}
|
||||
|
||||
|
||||
private fun parseFiltersOption(filters: Array<Filter>): Array<String> {
|
||||
val mimeTypes = mutableListOf<String>()
|
||||
for (filter in filters) {
|
||||
@@ -132,38 +130,10 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
|
||||
return mimeTypes.toTypedArray()
|
||||
}
|
||||
|
||||
private fun setIntentMimeTypes(intent: Intent, mimeTypes: Array<String>) {
|
||||
if (mimeTypes.isNotEmpty()) {
|
||||
var uniqueMimeKind = true
|
||||
var mimeKind: String? = null
|
||||
for (mime in mimeTypes) {
|
||||
val kind = mime.split("/")[0]
|
||||
if (mimeKind == null) {
|
||||
mimeKind = kind
|
||||
} else if (mimeKind != kind) {
|
||||
uniqueMimeKind = false
|
||||
}
|
||||
}
|
||||
|
||||
if (uniqueMimeKind) {
|
||||
if (mimeTypes.size > 1) {
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
|
||||
intent.type = Intent.normalizeMimeType("$mimeKind/*")
|
||||
} else {
|
||||
intent.type = mimeTypes[0]
|
||||
}
|
||||
} else {
|
||||
intent.type = "*/*"
|
||||
}
|
||||
} else {
|
||||
intent.type = "*/*"
|
||||
}
|
||||
}
|
||||
|
||||
@Command
|
||||
fun showMessageDialog(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(MessageOptions::class.java)
|
||||
|
||||
|
||||
if (activity.isFinishing) {
|
||||
invoke.reject("App is finishing")
|
||||
return
|
||||
@@ -179,7 +149,7 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
|
||||
Handler(Looper.getMainLooper())
|
||||
.post {
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
|
||||
|
||||
if (args.title != null) {
|
||||
builder.setTitle(args.title)
|
||||
}
|
||||
@@ -213,10 +183,14 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
|
||||
val parsedTypes = parseFiltersOption(args.filters)
|
||||
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
setIntentMimeTypes(intent, parsedTypes)
|
||||
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
intent.putExtra(Intent.EXTRA_TITLE, args.fileName ?: "")
|
||||
intent.type = "*/*"
|
||||
|
||||
if (parsedTypes.isNotEmpty()) {
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes)
|
||||
}
|
||||
|
||||
startActivityForResult(invoke, intent, "saveFileDialogResult")
|
||||
} catch (ex: Exception) {
|
||||
val message = ex.message ?: "Failed to pick save file"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@tauri-apps/plugin-dialog",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.2",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"authors": [
|
||||
"Tauri Programme within The Commons Conservancy"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## \[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`
|
||||
|
||||
## \[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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
description = "Access the file system."
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
@@ -41,7 +41,7 @@ notify = { version = "8", optional = true, features = [
|
||||
"serde",
|
||||
"serialization-compat-6",
|
||||
] }
|
||||
notify-debouncer-full = { version = "0.5", optional = true }
|
||||
notify-debouncer-full = { version = "0.6", optional = true }
|
||||
dunce = { workspace = true }
|
||||
percent-encoding = "2"
|
||||
|
||||
|
||||
@@ -33,21 +33,12 @@ tauri-plugin-fs = { git = "https://github.com/tauri-apps/plugins-workspace", bra
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-fs
|
||||
# or
|
||||
npm add @tauri-apps/plugin-fs
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-fs
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-fs#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-fs#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-fs#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1074,7 +1074,12 @@ async function writeFile(
|
||||
}
|
||||
|
||||
if (data instanceof ReadableStream) {
|
||||
const file = await open(path, options)
|
||||
const file = await open(path, {
|
||||
read: false,
|
||||
create: true,
|
||||
write: true,
|
||||
...options
|
||||
})
|
||||
const reader = data.getReader()
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@tauri-apps/plugin-fs",
|
||||
"version": "2.4.0",
|
||||
"version": "2.4.1",
|
||||
"description": "Access the file system.",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"authors": [
|
||||
|
||||
@@ -33,8 +33,6 @@ tauri-plugin-geolocation = { git = "https://github.com/tauri-apps/plugins-worksp
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
<!-- Add the branch for installations using git! -->
|
||||
|
||||
```sh
|
||||
@@ -43,13 +41,6 @@ pnpm add @tauri-apps/plugin-geolocation
|
||||
npm add @tauri-apps/plugin-geolocation
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-geolocation
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-geolocation#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-geolocation#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-geolocation#v2
|
||||
```
|
||||
|
||||
## Setting up
|
||||
|
||||
@@ -34,21 +34,12 @@ tauri-plugin-global-shortcut = { git = "https://github.com/tauri-apps/plugins-wo
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-global-shortcut
|
||||
# or
|
||||
npm add @tauri-apps/plugin-global-shortcut
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-global-shortcut
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-global-shortcut#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-global-shortcut#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-global-shortcut#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -35,8 +35,6 @@ tauri-plugin-haptics = { git = "https://github.com/tauri-apps/plugins-workspace"
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
<!-- Add the branch for installations using git! -->
|
||||
|
||||
```sh
|
||||
@@ -45,13 +43,6 @@ pnpm add @tauri-apps/plugin-haptics
|
||||
npm add @tauri-apps/plugin-haptics
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-haptics
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-haptics#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-haptics#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-haptics#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.5.1]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `fs-js@2.4.1`
|
||||
|
||||
## \[2.5.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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-http"
|
||||
version = "2.5.0"
|
||||
version = "2.5.1"
|
||||
description = "Access an HTTP client written in Rust."
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -34,7 +34,7 @@ serde_json = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { version = "1", features = ["sync", "macros"] }
|
||||
tauri-plugin-fs = { path = "../fs", version = "2.4.0" }
|
||||
tauri-plugin-fs = { path = "../fs", version = "2.4.1" }
|
||||
urlpattern = "0.3"
|
||||
regex = "1"
|
||||
http = "1"
|
||||
|
||||
@@ -33,21 +33,12 @@ tauri-plugin-http = { git = "https://github.com/tauri-apps/plugins-workspace", b
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-http
|
||||
# or
|
||||
npm add @tauri-apps/plugin-http
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-http
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-http#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-http#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-http#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@tauri-apps/plugin-http",
|
||||
"version": "2.5.0",
|
||||
"version": "2.5.1",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"authors": [
|
||||
"Tauri Programme within The Commons Conservancy"
|
||||
|
||||
@@ -30,7 +30,7 @@ fn set_cookies(
|
||||
fn cookies(cookie_store: &CookieStore, url: &url::Url) -> Option<HeaderValue> {
|
||||
let s = cookie_store
|
||||
.get_request_values(url)
|
||||
.map(|(name, value)| format!("{}={}", name, value))
|
||||
.map(|(name, value)| format!("{name}={value}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("; ");
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ impl<'de> Deserialize<'de> for Entry {
|
||||
};
|
||||
Ok(Entry {
|
||||
url: parse_url_pattern(&url).map_err(|e| {
|
||||
serde::de::Error::custom(format!("`{}` is not a valid URL pattern: {e}", url))
|
||||
serde::de::Error::custom(format!("`{url}` is not a valid URL pattern: {e}"))
|
||||
})?,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -35,21 +35,12 @@ If you want the single instance mechanism to only trigger for semver compatible
|
||||
|
||||
Then you can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-log
|
||||
# or
|
||||
npm add @tauri-apps/plugin-log
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-log
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-log#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-log#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-log#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -33,8 +33,6 @@ tauri-plugin-nfc = { git = "https://github.com/tauri-apps/plugins-workspace", br
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
<!-- Add the branch for installations using git! -->
|
||||
|
||||
```sh
|
||||
@@ -43,13 +41,6 @@ pnpm add @tauri-apps/plugin-nfc
|
||||
npm add @tauri-apps/plugin-nfc
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-nfc
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-nfc#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-nfc#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-nfc#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 60;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
8E12E6982E4A879F0019CC26 /* CoreNFC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8E12E6962E4A862B0019CC26 /* CoreNFC.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
A0E2115A2BF552D2003BCF4D /* ExamplePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0E211592BF552D2003BCF4D /* ExamplePlugin.swift */; };
|
||||
A0E211622BF55305003BCF4D /* Tauri in Frameworks */ = {isa = PBXBuildFile; productRef = A0E211612BF55305003BCF4D /* Tauri */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
A0E211542BF552D2003BCF4D /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
8E12E6962E4A862B0019CC26 /* CoreNFC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreNFC.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/System/iOSSupport/System/Library/Frameworks/CoreNFC.framework; sourceTree = DEVELOPER_DIR; };
|
||||
A0E211562BF552D2003BCF4D /* libtauri-plugin-nfc.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libtauri-plugin-nfc.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A0E211592BF552D2003BCF4D /* ExamplePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplePlugin.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
A0E211532BF552D2003BCF4D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8E12E6982E4A879F0019CC26 /* CoreNFC.framework in Frameworks */,
|
||||
A0E211622BF55305003BCF4D /* Tauri in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
8E12E6952E4A862B0019CC26 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8E12E6962E4A862B0019CC26 /* CoreNFC.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A0E2114D2BF552D2003BCF4D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A0E211582BF552D2003BCF4D /* tauri-plugin-nfc */,
|
||||
8E12E6952E4A862B0019CC26 /* Frameworks */,
|
||||
A0E211572BF552D2003BCF4D /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A0E211572BF552D2003BCF4D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A0E211562BF552D2003BCF4D /* libtauri-plugin-nfc.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A0E211582BF552D2003BCF4D /* tauri-plugin-nfc */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A0E211592BF552D2003BCF4D /* ExamplePlugin.swift */,
|
||||
);
|
||||
path = "tauri-plugin-nfc";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
A0E211552BF552D2003BCF4D /* tauri-plugin-nfc */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = A0E2115D2BF552D2003BCF4D /* Build configuration list for PBXNativeTarget "tauri-plugin-nfc" */;
|
||||
buildPhases = (
|
||||
A0E211522BF552D2003BCF4D /* Sources */,
|
||||
A0E211532BF552D2003BCF4D /* Frameworks */,
|
||||
A0E211542BF552D2003BCF4D /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "tauri-plugin-nfc";
|
||||
packageProductDependencies = (
|
||||
A0E211612BF55305003BCF4D /* Tauri */,
|
||||
);
|
||||
productName = "tauri-plugin-nfc";
|
||||
productReference = A0E211562BF552D2003BCF4D /* libtauri-plugin-nfc.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
A0E2114E2BF552D2003BCF4D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1540;
|
||||
LastUpgradeCheck = 1540;
|
||||
TargetAttributes = {
|
||||
A0E211552BF552D2003BCF4D = {
|
||||
CreatedOnToolsVersion = 15.4;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = A0E211512BF552D2003BCF4D /* Build configuration list for PBXProject "tauri-plugin-nfc" */;
|
||||
compatibilityVersion = "Xcode 14.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = A0E2114D2BF552D2003BCF4D;
|
||||
packageReferences = (
|
||||
A0E211602BF55305003BCF4D /* XCLocalSwiftPackageReference "../.tauri/tauri-api" */,
|
||||
);
|
||||
productRefGroup = A0E211572BF552D2003BCF4D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
A0E211552BF552D2003BCF4D /* tauri-plugin-nfc */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
A0E211522BF552D2003BCF4D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A0E2115A2BF552D2003BCF4D /* ExamplePlugin.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
A0E2115B2BF552D2003BCF4D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
A0E2115C2BF552D2003BCF4D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
A0E2115E2BF552D2003BCF4D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
A0E2115F2BF552D2003BCF4D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
A0E211512BF552D2003BCF4D /* Build configuration list for PBXProject "tauri-plugin-nfc" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
A0E2115B2BF552D2003BCF4D /* Debug */,
|
||||
A0E2115C2BF552D2003BCF4D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
A0E2115D2BF552D2003BCF4D /* Build configuration list for PBXNativeTarget "tauri-plugin-nfc" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
A0E2115E2BF552D2003BCF4D /* Debug */,
|
||||
A0E2115F2BF552D2003BCF4D /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
A0E211602BF55305003BCF4D /* XCLocalSwiftPackageReference "../.tauri/tauri-api" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = "../.tauri/tauri-api";
|
||||
};
|
||||
/* End XCLocalSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
A0E211612BF55305003BCF4D /* Tauri */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Tauri;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = A0E2114E2BF552D2003BCF4D /* Project object */;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?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
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"originHash" : "cd669d6eb9e52a836461d0d6168afc46839b3c2631131b83d6c1996da63315ec",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "swift-rs",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Brendonovich/swift-rs",
|
||||
"state" : {
|
||||
"revision" : "f64a4514de07f450ec5b6aa297624cd3479d9579",
|
||||
"version" : "1.0.7"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
}
|
||||
BIN
Binary file not shown.
+14
@@ -0,0 +1,14 @@
|
||||
<?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>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>tauri-plugin-nfc.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,523 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// https://developer.apple.com/documentation/corenfc/building_an_nfc_tag-reader_app
|
||||
|
||||
import CoreNFC
|
||||
import SwiftRs
|
||||
import Tauri
|
||||
import UIKit
|
||||
import WebKit
|
||||
|
||||
enum ScanKind: Decodable {
|
||||
case ndef, tag
|
||||
}
|
||||
|
||||
struct ScanOptions: Decodable {
|
||||
let kind: ScanKind
|
||||
var keepSessionAlive: Bool?
|
||||
var message: String?
|
||||
var successMessage: String?
|
||||
}
|
||||
|
||||
struct NDEFRecord: Decodable {
|
||||
var format: UInt8?
|
||||
var kind: [UInt8]?
|
||||
var identifier: [UInt8]?
|
||||
var payload: [UInt8]?
|
||||
}
|
||||
|
||||
struct WriteOptions: Decodable {
|
||||
var kind: ScanKind?
|
||||
let records: [NDEFRecord]
|
||||
var message: String?
|
||||
var successMessage: String?
|
||||
var successfulReadMessage: String?
|
||||
}
|
||||
|
||||
enum TagProcessMode {
|
||||
case write(message: NFCNDEFMessage)
|
||||
case read
|
||||
}
|
||||
|
||||
class Session {
|
||||
let nfcSession: NFCReaderSession?
|
||||
let invoke: Invoke
|
||||
var keepAlive: Bool
|
||||
let tagProcessMode: TagProcessMode
|
||||
var tagStatus: NFCNDEFStatus?
|
||||
var tag: NFCNDEFTag?
|
||||
let successfulReadMessage: String?
|
||||
let successfulWriteAlertMessage: String?
|
||||
|
||||
init(
|
||||
nfcSession: NFCReaderSession?,
|
||||
invoke: Invoke,
|
||||
keepAlive: Bool,
|
||||
tagProcessMode: TagProcessMode,
|
||||
successfulReadMessage: String?,
|
||||
successfulWriteAlertMessage: String?
|
||||
) {
|
||||
self.nfcSession = nfcSession
|
||||
self.invoke = invoke
|
||||
self.keepAlive = keepAlive
|
||||
self.tagProcessMode = tagProcessMode
|
||||
self.successfulReadMessage = successfulReadMessage
|
||||
self.successfulWriteAlertMessage = successfulWriteAlertMessage
|
||||
}
|
||||
}
|
||||
|
||||
class NfcStatus {
|
||||
let available: Bool
|
||||
let errorReason: String?
|
||||
|
||||
init(available: Bool, errorReason: String?) {
|
||||
self.available = available
|
||||
self.errorReason = errorReason
|
||||
}
|
||||
}
|
||||
|
||||
class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelegate {
|
||||
var session: Session?
|
||||
var status: NfcStatus!
|
||||
|
||||
public override func load(webview: WKWebView) {
|
||||
var available = false
|
||||
var errorReason: String?
|
||||
|
||||
let entry = Bundle.main.infoDictionary?["NFCReaderUsageDescription"] as? String
|
||||
|
||||
if entry == nil || entry?.count == 0 {
|
||||
errorReason = "missing NFCReaderUsageDescription configuration on the Info.plist file"
|
||||
} else if !NFCNDEFReaderSession.readingAvailable {
|
||||
errorReason =
|
||||
"NFC tag reading unavailable, make sure the Near-Field Communication capability on Xcode is enabled and the device supports NFC tag reading"
|
||||
} else {
|
||||
available = true
|
||||
}
|
||||
|
||||
if let error = errorReason {
|
||||
Logger.error("\(error)")
|
||||
}
|
||||
|
||||
self.status = NfcStatus(available: available, errorReason: errorReason)
|
||||
}
|
||||
|
||||
func tagReaderSessionDidBecomeActive(
|
||||
_ session: NFCTagReaderSession
|
||||
) {
|
||||
Logger.info("tagReaderSessionDidBecomeActive")
|
||||
}
|
||||
|
||||
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
|
||||
let tag = tags.first!
|
||||
|
||||
session.connect(
|
||||
to: tag,
|
||||
completionHandler: { [self] (error) in
|
||||
if let error = error {
|
||||
self.closeSession(session, error: "cannot connect to tag: \(error)")
|
||||
|
||||
} else {
|
||||
let ndefTag: NFCNDEFTag
|
||||
switch tag {
|
||||
case let .feliCa(tag):
|
||||
ndefTag = tag as NFCNDEFTag
|
||||
break
|
||||
case let .miFare(tag):
|
||||
ndefTag = tag as NFCNDEFTag
|
||||
break
|
||||
case let .iso15693(tag):
|
||||
ndefTag = tag as NFCNDEFTag
|
||||
break
|
||||
case let .iso7816(tag):
|
||||
ndefTag = tag as NFCNDEFTag
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
self.processTag(
|
||||
session: session, tag: ndefTag, metadata: tagMetadata(tag),
|
||||
mode: self.session!.tagProcessMode)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
|
||||
Logger.error("Tag reader session error \(error)")
|
||||
self.session?.invoke.reject("session invalidated with error: \(error)")
|
||||
self.session = nil
|
||||
}
|
||||
|
||||
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
|
||||
let message = messages.first!
|
||||
// TODO: do we really need this hook?
|
||||
self.session?.invoke.resolve(["records": ndefMessageRecords(message)])
|
||||
}
|
||||
|
||||
func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
|
||||
let tag = tags.first!
|
||||
|
||||
session.connect(
|
||||
to: tag,
|
||||
completionHandler: { [self] (error) in
|
||||
if let error = error {
|
||||
self.closeSession(session, error: "cannot connect to tag: \(error)")
|
||||
|
||||
} else {
|
||||
var metadata: JsonObject = [:]
|
||||
if tag.isKind(of: NFCFeliCaTag.self) {
|
||||
metadata["kind"] = ["FeliCa"]
|
||||
metadata["id"] = nil
|
||||
} else if let t = tag as? NFCMiFareTag {
|
||||
metadata["kind"] = ["MiFare"]
|
||||
metadata["id"] = byteArrayFromData(t.identifier)
|
||||
} else if let t = tag as? NFCISO15693Tag {
|
||||
metadata["kind"] = ["ISO15693"]
|
||||
metadata["id"] = byteArrayFromData(t.identifier)
|
||||
} else if let t = tag as? NFCISO7816Tag {
|
||||
metadata["kind"] = ["ISO7816Compatible"]
|
||||
metadata["id"] = byteArrayFromData(t.identifier)
|
||||
}
|
||||
|
||||
self.processTag(
|
||||
session: session, tag: tag, metadata: metadata,
|
||||
mode: self.session!.tagProcessMode)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
|
||||
if (error as NSError).code
|
||||
== NFCReaderError.Code.readerSessionInvalidationErrorFirstNDEFTagRead.rawValue
|
||||
{
|
||||
// not an error because we're using invalidateAfterFirstRead: true
|
||||
Logger.debug("readerSessionInvalidationErrorFirstNDEFTagRead")
|
||||
} else {
|
||||
Logger.error("NDEF reader session error \(error)")
|
||||
self.session?.invoke.reject("session invalidated with error: \(error)")
|
||||
self.session = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func tagMetadata(_ tag: NFCTag) -> JsonObject {
|
||||
var metadata: JsonObject = [:]
|
||||
|
||||
switch tag {
|
||||
case .feliCa:
|
||||
metadata["kind"] = ["FeliCa"]
|
||||
metadata["id"] = []
|
||||
break
|
||||
case let .miFare(tag):
|
||||
metadata["kind"] = ["MiFare"]
|
||||
metadata["id"] = byteArrayFromData(tag.identifier)
|
||||
break
|
||||
case let .iso15693(tag):
|
||||
metadata["kind"] = ["ISO15693"]
|
||||
metadata["id"] = byteArrayFromData(tag.identifier)
|
||||
break
|
||||
case let .iso7816(tag):
|
||||
metadata["kind"] = ["ISO7816Compatible"]
|
||||
metadata["id"] = byteArrayFromData(tag.identifier)
|
||||
break
|
||||
default:
|
||||
metadata["kind"] = ["Unknown"]
|
||||
metadata["id"] = []
|
||||
break
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
private func closeSession(_ session: NFCReaderSession) {
|
||||
session.invalidate()
|
||||
self.session = nil
|
||||
}
|
||||
|
||||
private func closeSession(_ session: NFCReaderSession, error: String) {
|
||||
session.invalidate(errorMessage: error)
|
||||
self.session = nil
|
||||
}
|
||||
|
||||
private func processTag<T: NFCNDEFTag>(
|
||||
session: NFCReaderSession, tag: T, metadata: JsonObject, mode: TagProcessMode
|
||||
) {
|
||||
tag.queryNDEFStatus(completionHandler: {
|
||||
[self] (status, capacity, error) in
|
||||
if let error = error {
|
||||
self.closeSession(session, error: "cannot connect to tag: \(error)")
|
||||
} else {
|
||||
switch mode {
|
||||
case .write(let message):
|
||||
self.writeNDEFTag(
|
||||
session: session, status: status, tag: tag, message: message,
|
||||
alertMessage: self.session?.successfulWriteAlertMessage)
|
||||
break
|
||||
case .read:
|
||||
if self.session?.keepAlive == true {
|
||||
self.session!.tagStatus = status
|
||||
self.session!.tag = tag
|
||||
}
|
||||
self.readNDEFTag(
|
||||
session: session, status: status, tag: tag, metadata: metadata,
|
||||
alertMessage: self.session?.successfulReadMessage)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func writeNDEFTag<T: NFCNDEFTag>(
|
||||
session: NFCReaderSession, status: NFCNDEFStatus, tag: T, message: NFCNDEFMessage,
|
||||
alertMessage: String?
|
||||
) {
|
||||
switch status {
|
||||
case .notSupported:
|
||||
self.closeSession(session, error: "Tag is not an NDEF-formatted tag")
|
||||
break
|
||||
case .readOnly:
|
||||
self.closeSession(session, error: "Read only tag")
|
||||
break
|
||||
case .readWrite:
|
||||
if let currentSession = self.session {
|
||||
tag.writeNDEF(
|
||||
message,
|
||||
completionHandler: { (error) in
|
||||
if let error = error {
|
||||
self.closeSession(session, error: "cannot write to tag: \(error)")
|
||||
} else {
|
||||
if let message = alertMessage {
|
||||
session.alertMessage = message
|
||||
}
|
||||
currentSession.invoke.resolve()
|
||||
|
||||
self.closeSession(session)
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
break
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func readNDEFTag<T: NFCNDEFTag>(
|
||||
session: NFCReaderSession, status: NFCNDEFStatus, tag: T, metadata m: JsonObject,
|
||||
alertMessage: String?
|
||||
) {
|
||||
var metadata: JsonObject = [:]
|
||||
metadata.merge(m) { (_, new) in new }
|
||||
|
||||
switch status {
|
||||
case .notSupported:
|
||||
self.resolveInvoke(message: nil, metadata: metadata)
|
||||
self.closeSession(session)
|
||||
return
|
||||
case .readOnly:
|
||||
metadata["readOnly"] = true
|
||||
break
|
||||
case .readWrite:
|
||||
metadata["readOnly"] = false
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
tag.readNDEF(completionHandler: {
|
||||
[self] (message, error) in
|
||||
if let error = error {
|
||||
let code = (error as NSError).code
|
||||
if code != 403 {
|
||||
self.closeSession(session, error: "Failed to read: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let message = alertMessage {
|
||||
session.alertMessage = message
|
||||
}
|
||||
self.resolveInvoke(message: message, metadata: metadata)
|
||||
|
||||
if self.session?.keepAlive != true {
|
||||
self.closeSession(session)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func resolveInvoke(message: NFCNDEFMessage?, metadata: JsonObject) {
|
||||
var data: JsonObject = [:]
|
||||
|
||||
data.merge(metadata) { (_, new) in new }
|
||||
|
||||
if let message = message {
|
||||
data["records"] = ndefMessageRecords(message)
|
||||
} else {
|
||||
data["records"] = []
|
||||
}
|
||||
|
||||
self.session?.invoke.resolve(data)
|
||||
}
|
||||
|
||||
private func ndefMessageRecords(_ message: NFCNDEFMessage) -> [JsonObject] {
|
||||
var records: [JsonObject] = []
|
||||
for record in message.records {
|
||||
var recordJson: JsonObject = [:]
|
||||
recordJson["tnf"] = record.typeNameFormat.rawValue
|
||||
recordJson["kind"] = byteArrayFromData(record.type)
|
||||
recordJson["id"] = byteArrayFromData(record.identifier)
|
||||
recordJson["payload"] = byteArrayFromData(record.payload)
|
||||
|
||||
records.append(recordJson)
|
||||
}
|
||||
|
||||
return records
|
||||
}
|
||||
|
||||
private func byteArrayFromData(_ data: Data) -> [UInt8] {
|
||||
var arr: [UInt8] = []
|
||||
for b in data {
|
||||
arr.append(b)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
private func dataFromByteArray(_ array: [UInt8]) -> Data {
|
||||
var data = Data(capacity: array.count)
|
||||
|
||||
data.append(contentsOf: array)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
@objc func isAvailable(_ invoke: Invoke) {
|
||||
invoke.resolve([
|
||||
"available": self.status.available
|
||||
])
|
||||
}
|
||||
|
||||
@objc public func write(_ invoke: Invoke) throws {
|
||||
if !self.status.available {
|
||||
invoke.reject("NFC reading unavailable: \(self.status.errorReason ?? "")")
|
||||
return
|
||||
}
|
||||
|
||||
let args = try invoke.parseArgs(WriteOptions.self)
|
||||
|
||||
var ndefPayloads = [NFCNDEFPayload]()
|
||||
|
||||
for record in args.records {
|
||||
ndefPayloads.append(
|
||||
NFCNDEFPayload(
|
||||
format: NFCTypeNameFormat(rawValue: record.format ?? 0) ?? .unknown,
|
||||
type: dataFromByteArray(record.kind ?? []),
|
||||
identifier: dataFromByteArray(record.identifier ?? []),
|
||||
payload: dataFromByteArray(record.payload ?? [])
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if let session = self.session {
|
||||
if let nfcSession = session.nfcSession, let tagStatus = session.tagStatus,
|
||||
let tag = session.tag
|
||||
{
|
||||
session.keepAlive = false
|
||||
self.writeNDEFTag(
|
||||
session: nfcSession, status: tagStatus, tag: tag,
|
||||
message: NFCNDEFMessage(records: ndefPayloads),
|
||||
alertMessage: args.successMessage
|
||||
)
|
||||
} else {
|
||||
invoke.reject(
|
||||
"connected tag not found, please wait for it to be available and then call write()")
|
||||
}
|
||||
} else {
|
||||
self.startScanSession(
|
||||
invoke: invoke,
|
||||
kind: args.kind ?? .ndef,
|
||||
keepAlive: true,
|
||||
invalidateAfterFirstRead: false,
|
||||
tagProcessMode: .write(
|
||||
message: NFCNDEFMessage(records: ndefPayloads)
|
||||
),
|
||||
alertMessage: args.message,
|
||||
successfulReadMessage: args.successfulReadMessage,
|
||||
successfulWriteAlertMessage: args.successMessage
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@objc public func scan(_ invoke: Invoke) throws {
|
||||
if !self.status.available {
|
||||
invoke.reject("NFC reading unavailable: \(self.status.errorReason ?? "")")
|
||||
return
|
||||
}
|
||||
|
||||
let args = try invoke.parseArgs(ScanOptions.self)
|
||||
|
||||
self.startScanSession(
|
||||
invoke: invoke,
|
||||
kind: args.kind,
|
||||
keepAlive: args.keepSessionAlive ?? false,
|
||||
invalidateAfterFirstRead: true,
|
||||
tagProcessMode: .read,
|
||||
alertMessage: args.message,
|
||||
successfulReadMessage: args.successMessage,
|
||||
successfulWriteAlertMessage: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func startScanSession(
|
||||
invoke: Invoke,
|
||||
kind: ScanKind,
|
||||
keepAlive: Bool,
|
||||
invalidateAfterFirstRead: Bool,
|
||||
tagProcessMode: TagProcessMode,
|
||||
alertMessage: String?,
|
||||
successfulReadMessage: String?,
|
||||
successfulWriteAlertMessage: String?
|
||||
) {
|
||||
let nfcSession: NFCReaderSession?
|
||||
|
||||
switch kind {
|
||||
case .tag:
|
||||
nfcSession = NFCTagReaderSession(
|
||||
pollingOption: [.iso14443, .iso15693],
|
||||
delegate: self,
|
||||
queue: DispatchQueue.main
|
||||
)
|
||||
break
|
||||
case .ndef:
|
||||
nfcSession = NFCNDEFReaderSession(
|
||||
delegate: self,
|
||||
queue: DispatchQueue.main,
|
||||
invalidateAfterFirstRead: invalidateAfterFirstRead
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
if let message = alertMessage {
|
||||
nfcSession?.alertMessage = message
|
||||
}
|
||||
nfcSession?.begin()
|
||||
|
||||
self.session = Session(
|
||||
nfcSession: nfcSession,
|
||||
invoke: invoke,
|
||||
keepAlive: keepAlive,
|
||||
tagProcessMode: tagProcessMode,
|
||||
successfulReadMessage: successfulReadMessage,
|
||||
successfulWriteAlertMessage: successfulWriteAlertMessage
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("init_plugin_nfc")
|
||||
func initPlugin() -> Plugin {
|
||||
return NfcPlugin()
|
||||
}
|
||||
@@ -149,6 +149,7 @@ class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelega
|
||||
func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
|
||||
Logger.error("Tag reader session error \(error)")
|
||||
self.session?.invoke.reject("session invalidated with error: \(error)")
|
||||
self.session = nil
|
||||
}
|
||||
|
||||
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
|
||||
@@ -200,6 +201,7 @@ class NfcPlugin: Plugin, NFCTagReaderSessionDelegate, NFCNDEFReaderSessionDelega
|
||||
} else {
|
||||
Logger.error("NDEF reader session error \(error)")
|
||||
self.session?.invoke.reject("session invalidated with error: \(error)")
|
||||
self.session = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ serde_json = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
log = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
rand = "0.8"
|
||||
rand = "0.9"
|
||||
time = { version = "0.3", features = ["serde", "parsing", "formatting"] }
|
||||
url = { version = "2", features = ["serde"] }
|
||||
serde_repr = "0.1"
|
||||
|
||||
@@ -33,21 +33,12 @@ tauri-plugin-notification = { git = "https://github.com/tauri-apps/plugins-works
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-notification
|
||||
# or
|
||||
npm add @tauri-apps/plugin-notification
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-notification
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-notification#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-notification#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-notification#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -35,8 +35,6 @@ tauri = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
open = { version = "5", features = ["shellexecute-on-windows"] }
|
||||
glob = { workspace = true }
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
dunce = { workspace = true }
|
||||
|
||||
[target."cfg(windows)".dependencies.windows]
|
||||
|
||||
@@ -33,8 +33,6 @@ tauri-plugin-opener = { git = "https://github.com/tauri-apps/plugins-workspace",
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
<!-- Add the branch for installations using git! -->
|
||||
|
||||
```sh
|
||||
@@ -43,13 +41,6 @@ pnpm add @tauri-apps/plugin-opener
|
||||
npm add @tauri-apps/plugin-opener
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-opener
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-opener#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-opener#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-opener#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -84,6 +75,10 @@ await openPath('/path/to/file', 'firefox')
|
||||
|
||||
// Reveal a path with the system's default explorer
|
||||
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
|
||||
@@ -111,6 +106,9 @@ fn main() {
|
||||
|
||||
// Reveal a path with the system's default explorer
|
||||
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(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
|
||||
@@ -1 +1 @@
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_OPENER__=function(n){"use strict";async function e(n,e={},_){return window.__TAURI_INTERNALS__.invoke(n,e,_)}return"function"==typeof SuppressedError&&SuppressedError,n.openPath=async function(n,_){await e("plugin:opener|open_path",{path:n,with:_})},n.openUrl=async function(n,_){await e("plugin:opener|open_url",{url:n,with:_})},n.revealItemInDir=async function(n){return e("plugin:opener|reveal_item_in_dir",{path:n})},n}({});Object.defineProperty(window.__TAURI__,"opener",{value:__TAURI_PLUGIN_OPENER__})}
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_OPENER__=function(n){"use strict";async function e(n,e={},r){return window.__TAURI_INTERNALS__.invoke(n,e,r)}return"function"==typeof SuppressedError&&SuppressedError,n.openPath=async function(n,r){await e("plugin:opener|open_path",{path:n,with:r})},n.openUrl=async function(n,r){await e("plugin:opener|open_url",{url:n,with:r})},n.revealItemInDir=async function(n){return e("plugin:opener|reveal_item_in_dir",{paths:"string"==typeof n?[n]:n})},n}({});Object.defineProperty(window.__TAURI__,"opener",{value:__TAURI_PLUGIN_OPENER__})}
|
||||
|
||||
@@ -86,12 +86,14 @@ export async function openPath(path: string, openWith?: string): Promise<void> {
|
||||
* ```typescript
|
||||
* import { revealItemInDir } from '@tauri-apps/plugin-opener';
|
||||
* await revealItemInDir('/path/to/file');
|
||||
* await revealItemInDir([ '/path/to/file', '/path/to/another/file' ]);
|
||||
* ```
|
||||
*
|
||||
* @param path The path to reveal.
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
export async function revealItemInDir(path: string) {
|
||||
return invoke('plugin:opener|reveal_item_in_dir', { path })
|
||||
export async function revealItemInDir(path: string | string[]): Promise<void> {
|
||||
const paths = typeof path === 'string' ? [path] : path
|
||||
return invoke('plugin:opener|reveal_item_in_dir', { paths })
|
||||
}
|
||||
|
||||
@@ -69,7 +69,8 @@ pub async fn open_path<R: Runtime>(
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO: in the next major version, rename to `reveal_items_in_dir`
|
||||
#[tauri::command]
|
||||
pub async fn reveal_item_in_dir(path: PathBuf) -> crate::Result<()> {
|
||||
crate::reveal_item_in_dir(path)
|
||||
pub async fn reveal_item_in_dir(paths: Vec<PathBuf>) -> crate::Result<()> {
|
||||
crate::reveal_items_in_dir(&paths)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ pub enum Error {
|
||||
Win32Error(#[from] windows::core::Error),
|
||||
#[error("Path doesn't have a parent: {0}")]
|
||||
NoParent(PathBuf),
|
||||
#[cfg(windows)]
|
||||
#[error("Failed to convert path '{0}' to ITEMIDLIST")]
|
||||
FailedToConvertPathToItemIdList(PathBuf),
|
||||
#[error("Failed to convert path to file:// url")]
|
||||
FailedToConvertPathToFileUrl,
|
||||
#[error(transparent)]
|
||||
|
||||
@@ -25,7 +25,7 @@ pub use error::Error;
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub use open::{open_path, open_url};
|
||||
pub use reveal_item_in_dir::reveal_item_in_dir;
|
||||
pub use reveal_item_in_dir::{reveal_item_in_dir, reveal_items_in_dir};
|
||||
|
||||
pub struct Opener<R: Runtime> {
|
||||
// we use `fn() -> R` to silence the unused generic error
|
||||
@@ -146,7 +146,15 @@ impl<R: Runtime> Opener<R> {
|
||||
}
|
||||
|
||||
pub fn reveal_item_in_dir<P: AsRef<Path>>(&self, p: P) -> Result<()> {
|
||||
crate::reveal_item_in_dir::reveal_item_in_dir(p)
|
||||
reveal_item_in_dir(p)
|
||||
}
|
||||
|
||||
pub fn reveal_items_in_dir<I, P>(&self, paths: I) -> Result<()>
|
||||
where
|
||||
I: IntoIterator<Item = P>,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
reveal_items_in_dir(paths)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +221,7 @@ impl Builder {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::open_url,
|
||||
commands::open_path,
|
||||
commands::reveal_item_in_dir
|
||||
commands::reveal_item_in_dir,
|
||||
]);
|
||||
|
||||
if self.open_js_links_on_click {
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::path::Path;
|
||||
///
|
||||
/// - **Android / iOS:** Unsupported.
|
||||
pub fn reveal_item_in_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {
|
||||
let path = path.as_ref().canonicalize()?;
|
||||
let path = dunce::canonicalize(path.as_ref())?;
|
||||
|
||||
#[cfg(any(
|
||||
windows,
|
||||
@@ -21,7 +21,47 @@ pub fn reveal_item_in_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
return imp::reveal_item_in_dir(&path);
|
||||
return imp::reveal_items_in_dir(&[path]);
|
||||
|
||||
#[cfg(not(any(
|
||||
windows,
|
||||
target_os = "macos",
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
)))]
|
||||
Err(crate::Error::UnsupportedPlatform)
|
||||
}
|
||||
|
||||
/// Reveal the paths the system's default explorer.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// - **Android / iOS:** Unsupported.
|
||||
pub fn reveal_items_in_dir<I, P>(paths: I) -> crate::Result<()>
|
||||
where
|
||||
I: IntoIterator<Item = P>,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let mut canonicalized = vec![];
|
||||
|
||||
for path in paths {
|
||||
let path = dunce::canonicalize(path.as_ref())?;
|
||||
canonicalized.push(path);
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
windows,
|
||||
target_os = "macos",
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
return imp::reveal_items_in_dir(&canonicalized);
|
||||
|
||||
#[cfg(not(any(
|
||||
windows,
|
||||
@@ -37,8 +77,10 @@ pub fn reveal_item_in_dir<P: AsRef<Path>>(path: P) -> crate::Result<()> {
|
||||
|
||||
#[cfg(windows)]
|
||||
mod imp {
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use windows::Win32::UI::Shell::Common::ITEMIDLIST;
|
||||
use windows::{
|
||||
core::{w, HSTRING, PCWSTR},
|
||||
Win32::{
|
||||
@@ -54,57 +96,98 @@ mod imp {
|
||||
},
|
||||
};
|
||||
|
||||
pub fn reveal_item_in_dir(path: &Path) -> crate::Result<()> {
|
||||
let file = dunce::simplified(path);
|
||||
pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> {
|
||||
if paths.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut grouped_paths: HashMap<&Path, Vec<&Path>> = HashMap::new();
|
||||
for path in paths {
|
||||
let parent = path
|
||||
.parent()
|
||||
.ok_or_else(|| crate::Error::NoParent(path.to_path_buf()))?;
|
||||
grouped_paths.entry(parent).or_default().push(path);
|
||||
}
|
||||
|
||||
let _ = unsafe { CoInitialize(None) };
|
||||
|
||||
let dir = file
|
||||
.parent()
|
||||
.ok_or_else(|| crate::Error::NoParent(file.to_path_buf()))?;
|
||||
|
||||
let dir = HSTRING::from(dir);
|
||||
let dir_item = unsafe { ILCreateFromPathW(&dir) };
|
||||
|
||||
let file_h = HSTRING::from(file);
|
||||
let file_item = unsafe { ILCreateFromPathW(&file_h) };
|
||||
|
||||
unsafe {
|
||||
if let Err(e) = SHOpenFolderAndSelectItems(dir_item, Some(&[file_item]), 0) {
|
||||
for (parent, to_reveals) in grouped_paths {
|
||||
let parent_item_id_list = OwnedItemIdList::new(parent)?;
|
||||
let to_reveals_item_id_list = to_reveals
|
||||
.iter()
|
||||
.map(|to_reveal| OwnedItemIdList::new(*to_reveal))
|
||||
.collect::<crate::Result<Vec<_>>>()?;
|
||||
if let Err(e) = unsafe {
|
||||
SHOpenFolderAndSelectItems(
|
||||
parent_item_id_list.item,
|
||||
Some(
|
||||
&to_reveals_item_id_list
|
||||
.iter()
|
||||
.map(|item| item.item)
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
0,
|
||||
)
|
||||
} {
|
||||
// from https://github.com/electron/electron/blob/10d967028af2e72382d16b7e2025d243b9e204ae/shell/common/platform_util_win.cc#L302
|
||||
// On some systems, the above call mysteriously fails with "file not
|
||||
// found" even though the file is there. In these cases, ShellExecute()
|
||||
// seems to work as a fallback (although it won't select the file).
|
||||
//
|
||||
// Note: we only handle the first file here if multiple of are present
|
||||
if e.code().0 == ERROR_FILE_NOT_FOUND.0 as i32 {
|
||||
let is_dir = file.is_dir();
|
||||
let first_path = to_reveals[0];
|
||||
let is_dir = first_path.is_dir();
|
||||
let mut info = SHELLEXECUTEINFOW {
|
||||
cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as _,
|
||||
nShow: SW_SHOWNORMAL.0,
|
||||
lpFile: PCWSTR(dir.as_ptr()),
|
||||
lpFile: PCWSTR(parent_item_id_list.hstring.as_ptr()),
|
||||
lpClass: if is_dir { w!("folder") } else { PCWSTR::null() },
|
||||
lpVerb: if is_dir {
|
||||
w!("explore")
|
||||
} else {
|
||||
PCWSTR::null()
|
||||
},
|
||||
..std::mem::zeroed()
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
ShellExecuteExW(&mut info).inspect_err(|_| {
|
||||
ILFree(Some(dir_item));
|
||||
ILFree(Some(file_item));
|
||||
})?;
|
||||
unsafe { ShellExecuteExW(&mut info) }?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
ILFree(Some(dir_item));
|
||||
ILFree(Some(file_item));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct OwnedItemIdList {
|
||||
hstring: HSTRING,
|
||||
item: *const ITEMIDLIST,
|
||||
}
|
||||
|
||||
impl OwnedItemIdList {
|
||||
fn new(path: &Path) -> crate::Result<Self> {
|
||||
let path_hstring = HSTRING::from(path);
|
||||
let item_id_list = unsafe { ILCreateFromPathW(&path_hstring) };
|
||||
if item_id_list.is_null() {
|
||||
Err(crate::Error::FailedToConvertPathToItemIdList(
|
||||
path.to_owned(),
|
||||
))
|
||||
} else {
|
||||
Ok(Self {
|
||||
hstring: path_hstring,
|
||||
item: item_id_list,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OwnedItemIdList {
|
||||
fn drop(&mut self) {
|
||||
if !self.item.is_null() {
|
||||
unsafe { ILFree(Some(self.item)) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
@@ -115,24 +198,36 @@ mod imp {
|
||||
target_os = "openbsd"
|
||||
))]
|
||||
mod imp {
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn reveal_item_in_dir(path: &Path) -> crate::Result<()> {
|
||||
pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> {
|
||||
let connection = zbus::blocking::Connection::session()?;
|
||||
|
||||
reveal_with_filemanager1(path, &connection)
|
||||
.or_else(|_| reveal_with_open_uri_portal(path, &connection))
|
||||
reveal_with_filemanager1(paths, &connection).or_else(|e| {
|
||||
// Fallback to opening the directory of the first item if revealing multiple items fails.
|
||||
if let Some(first_path) = paths.first() {
|
||||
reveal_with_open_uri_portal(first_path, &connection)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn reveal_with_filemanager1(
|
||||
path: &Path,
|
||||
paths: &[PathBuf],
|
||||
connection: &zbus::blocking::Connection,
|
||||
) -> crate::Result<()> {
|
||||
let uri = url::Url::from_file_path(path)
|
||||
.map_err(|_| crate::Error::FailedToConvertPathToFileUrl)?;
|
||||
let uris: Result<Vec<_>, _> = paths
|
||||
.iter()
|
||||
.map(|path| {
|
||||
url::Url::from_file_path(path)
|
||||
.map_err(|_| crate::Error::FailedToConvertPathToFileUrl)
|
||||
})
|
||||
.collect();
|
||||
let uris = uris?;
|
||||
let uri_strs: Vec<&str> = uris.iter().map(|uri| uri.as_str()).collect();
|
||||
|
||||
#[zbus::proxy(
|
||||
interface = "org.freedesktop.FileManager1",
|
||||
@@ -145,7 +240,7 @@ mod imp {
|
||||
|
||||
let proxy = FileManager1ProxyBlocking::new(connection)?;
|
||||
|
||||
proxy.ShowItems(vec![uri.as_str()], "")
|
||||
proxy.ShowItems(uri_strs, "")
|
||||
}
|
||||
|
||||
fn reveal_with_open_uri_portal(
|
||||
@@ -177,14 +272,22 @@ mod imp {
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod imp {
|
||||
use super::*;
|
||||
use objc2_app_kit::NSWorkspace;
|
||||
use objc2_foundation::{NSArray, NSString, NSURL};
|
||||
pub fn reveal_item_in_dir(path: &Path) -> crate::Result<()> {
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn reveal_items_in_dir(paths: &[PathBuf]) -> crate::Result<()> {
|
||||
unsafe {
|
||||
let path = path.to_string_lossy();
|
||||
let path = NSString::from_str(&path);
|
||||
let urls = vec![NSURL::fileURLWithPath(&path)];
|
||||
let mut urls = Vec::new();
|
||||
|
||||
for path in paths {
|
||||
let path = path.to_string_lossy();
|
||||
let path = NSString::from_str(&path);
|
||||
let url = NSURL::fileURLWithPath(&path);
|
||||
|
||||
urls.push(url);
|
||||
}
|
||||
|
||||
let urls = NSArray::from_retained_slice(&urls);
|
||||
|
||||
let workspace = NSWorkspace::new();
|
||||
|
||||
@@ -33,21 +33,12 @@ tauri-plugin-os = { git = "https://github.com/tauri-apps/plugins-workspace", bra
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-os
|
||||
# or
|
||||
npm add @tauri-apps/plugin-os
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-os
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-os#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-os#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-os#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.3.1]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `fs@2.4.1`
|
||||
|
||||
## \[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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-persisted-scope"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
description = "Save filesystem and asset scopes and restore them when the app is reopened."
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
@@ -27,7 +27,7 @@ log = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
aho-corasick = "1"
|
||||
bincode = "1"
|
||||
tauri-plugin-fs = { path = "../fs", version = "2.4.0" }
|
||||
tauri-plugin-fs = { path = "../fs", version = "2.4.1" }
|
||||
|
||||
[features]
|
||||
protocol-asset = ["tauri/protocol-asset"]
|
||||
|
||||
@@ -35,21 +35,12 @@ tauri-plugin-positioner = { git = "https://github.com/tauri-apps/plugins-workspa
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-positioner
|
||||
# or
|
||||
npm add @tauri-apps/plugin-positioner
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-positioner
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-positioner#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-positioner#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-positioner#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -33,21 +33,12 @@ tauri-plugin-process = { git = "https://github.com/tauri-apps/plugins-workspace"
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-process
|
||||
# or
|
||||
npm add @tauri-apps/plugin-process
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-process
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-process#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-process#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-process#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -33,21 +33,12 @@ tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace",
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-shell
|
||||
# or
|
||||
npm add @tauri-apps/plugin-shell
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-shell
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-shell#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-shell#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-shell#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -388,7 +388,7 @@ class Command<O extends IOPayload> extends EventEmitter<CommandEvents> {
|
||||
* Creates a new `Command` instance.
|
||||
*
|
||||
* @param program The program name to execute.
|
||||
* It must be configured on `tauri.conf.json > plugins > shell > scope`.
|
||||
* It must be configured in your project's capabilities.
|
||||
* @param args Program arguments.
|
||||
* @param options Spawn options.
|
||||
*/
|
||||
@@ -425,7 +425,7 @@ class Command<O extends IOPayload> extends EventEmitter<CommandEvents> {
|
||||
* ```
|
||||
*
|
||||
* @param program The program to execute.
|
||||
* It must be configured on `tauri.conf.json > plugins > shell > scope`.
|
||||
* It must be configured in your project's capabilities.
|
||||
*/
|
||||
static create<O extends IOPayload>(
|
||||
program: string,
|
||||
@@ -457,7 +457,7 @@ class Command<O extends IOPayload> extends EventEmitter<CommandEvents> {
|
||||
* ```
|
||||
*
|
||||
* @param program The program to execute.
|
||||
* It must be configured on `tauri.conf.json > plugins > shell > scope`.
|
||||
* It must be configured in your project's capabilities.
|
||||
*/
|
||||
static sidecar<O extends IOPayload>(
|
||||
program: string,
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# Changelog
|
||||
|
||||
## \[2.3.2]
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Upgraded to `deep-link@2.4.1`
|
||||
|
||||
## \[2.3.1]
|
||||
|
||||
- [`6f345870`](https://github.com/tauri-apps/plugins-workspace/commit/6f345870df4e7b187deb869df03b79858e03b4fe) ([#2860](https://github.com/tauri-apps/plugins-workspace/pull/2860) by [@MorpheusXAUT](https://github.com/tauri-apps/plugins-workspace/../../MorpheusXAUT)) Fix D-Bus name replacement logic on Linux to prevent multiple instances from acquiring the same well-known name.\
|
||||
This patch updates the `zbus` dependency to the latest compatible version (`^5.9`) and explicitly sets `RequestNameFlags` to ensure a second instance fails to acquire the D-Bus name when another one is already running.
|
||||
|
||||
## \[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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "tauri-plugin-single-instance"
|
||||
version = "2.3.0"
|
||||
version = "2.3.2"
|
||||
description = "Ensure a single instance of your tauri app is running."
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
@@ -26,7 +26,7 @@ serde_json = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tauri-plugin-deep-link = { path = "../deep-link", version = "2.4.0", optional = true }
|
||||
tauri-plugin-deep-link = { path = "../deep-link", version = "2.4.1", optional = true }
|
||||
semver = { version = "1", optional = true }
|
||||
|
||||
[target."cfg(target_os = \"windows\")".dependencies.windows-sys]
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.6.0"
|
||||
"@tauri-apps/cli": "2.7.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ pub fn init<R: Runtime>(f: Box<SingleInstanceCallback<R>>) -> TauriPlugin<R> {
|
||||
.unwrap()
|
||||
.name(dbus_name.as_str())
|
||||
.unwrap()
|
||||
.replace_existing_names(false)
|
||||
.allow_name_replacements(false)
|
||||
.serve_at(dbus_path.as_str(), single_instance_dbus)
|
||||
.unwrap()
|
||||
.build()
|
||||
|
||||
@@ -35,21 +35,12 @@ branch = "v2"
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-sql
|
||||
# or
|
||||
npm add @tauri-apps/plugin-sql
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-sql
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-sql#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-sql#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-sql#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -33,21 +33,12 @@ tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace",
|
||||
|
||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||
|
||||
> Note: Since most JavaScript package managers are unable to install packages from git monorepos we provide read-only mirrors of each plugin. This makes installation option 2 more ergonomic to use.
|
||||
|
||||
```sh
|
||||
pnpm add @tauri-apps/plugin-store
|
||||
# or
|
||||
npm add @tauri-apps/plugin-store
|
||||
# or
|
||||
yarn add @tauri-apps/plugin-store
|
||||
|
||||
# alternatively with Git:
|
||||
pnpm add https://github.com/tauri-apps/tauri-plugin-store#v2
|
||||
# or
|
||||
npm add https://github.com/tauri-apps/tauri-plugin-store#v2
|
||||
# or
|
||||
yarn add https://github.com/tauri-apps/tauri-plugin-store#v2
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -1 +1 @@
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,a;function r(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},a){return window.__TAURI_INTERNALS__.invoke(t,e,a)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,a,r){if("function"==typeof e?t!==e||!r:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===a?r:"a"===a?r.call(t):r?r.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,a){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,a)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,a){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:r(e)}).then((e=>async()=>async function(t,e){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(t,e),await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.load(t,e)}e=new WeakMap,function(t){t.WINDOW_RESIZED="tauri://resize",t.WINDOW_MOVED="tauri://move",t.WINDOW_CLOSE_REQUESTED="tauri://close-requested",t.WINDOW_DESTROYED="tauri://destroyed",t.WINDOW_FOCUS="tauri://focus",t.WINDOW_BLUR="tauri://blur",t.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",t.WINDOW_THEME_CHANGED="tauri://theme-changed",t.WINDOW_CREATED="tauri://window-created",t.WEBVIEW_CREATED="tauri://webview-created",t.DRAG_ENTER="tauri://drag-enter",t.DRAG_OVER="tauri://drag-over",t.DRAG_DROP="tauri://drag-drop",t.DRAG_LEAVE="tauri://drag-leave"}(a||(a={}));class u extends i{constructor(t){super(t)}static async load(t,e){const a=await s("plugin:store|load",{path:t,...e});return new u(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new u(t):null))}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,a]=await s("plugin:store|get",{rid:this.rid,key:t});return a?e:void 0}async has(t){return await s("plugin:store|has",{rid:this.rid,key:t})}async delete(t){return await s("plugin:store|delete",{rid:this.rid,key:t})}async clear(){await s("plugin:store|clear",{rid:this.rid})}async reset(){await s("plugin:store|reset",{rid:this.rid})}async keys(){return await s("plugin:store|keys",{rid:this.rid})}async values(){return await s("plugin:store|values",{rid:this.rid})}async entries(){return await s("plugin:store|entries",{rid:this.rid})}async length(){return await s("plugin:store|length",{rid:this.rid})}async reload(){await s("plugin:store|reload",{rid:this.rid})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(a=>{a.payload.resourceId===this.rid&&a.payload.key===t&&e(a.payload.exists?a.payload.value:void 0)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.exists?e.payload.value:void 0)}))}}return t.LazyStore=class{get store(){return this._store||(this._store=o(this.path,this.options)),this._store}constructor(t,e){this.path=t,this.options=e}async init(){await this.store}async set(t,e){return(await this.store).set(t,e)}async get(t){return(await this.store).get(t)}async has(t){return(await this.store).has(t)}async delete(t){return(await this.store).delete(t)}async clear(){await(await this.store).clear()}async reset(){await(await this.store).reset()}async keys(){return(await this.store).keys()}async values(){return(await this.store).values()}async entries(){return(await this.store).entries()}async length(){return(await this.store).length()}async reload(){await(await this.store).reload()}async save(){await(await this.store).save()}async onKeyChange(t,e){return(await this.store).onKeyChange(t,e)}async onChange(t){return(await this.store).onChange(t)}async close(){this._store&&await(await this._store).close()}},t.Store=u,t.getStore=async function(t){return await u.get(t)},t.load=o,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})}
|
||||
if("__TAURI__"in window){var __TAURI_PLUGIN_STORE__=function(t){"use strict";var e,a;function r(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}async function s(t,e={},a){return window.__TAURI_INTERNALS__.invoke(t,e,a)}"function"==typeof SuppressedError&&SuppressedError;class i{get rid(){return function(t,e,a,r){if("function"==typeof e?t!==e||!r:!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===a?r:"a"===a?r.call(t):r?r.value:e.get(t)}(this,e,"f")}constructor(t){e.set(this,void 0),function(t,e,a){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");e.set(t,a)}(this,e,t)}async close(){return s("plugin:resources|close",{rid:this.rid})}}async function n(t,e,a){const i={kind:"Any"};return s("plugin:event|listen",{event:t,target:i,handler:r(e)}).then((e=>async()=>async function(t,e){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(t,e),await s("plugin:event|unlisten",{event:t,eventId:e})}(t,e)))}async function o(t,e){return await u.load(t,e)}e=new WeakMap,function(t){t.WINDOW_RESIZED="tauri://resize",t.WINDOW_MOVED="tauri://move",t.WINDOW_CLOSE_REQUESTED="tauri://close-requested",t.WINDOW_DESTROYED="tauri://destroyed",t.WINDOW_FOCUS="tauri://focus",t.WINDOW_BLUR="tauri://blur",t.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",t.WINDOW_THEME_CHANGED="tauri://theme-changed",t.WINDOW_CREATED="tauri://window-created",t.WEBVIEW_CREATED="tauri://webview-created",t.DRAG_ENTER="tauri://drag-enter",t.DRAG_OVER="tauri://drag-over",t.DRAG_DROP="tauri://drag-drop",t.DRAG_LEAVE="tauri://drag-leave"}(a||(a={}));class u extends i{constructor(t){super(t)}static async load(t,e){const a=await s("plugin:store|load",{path:t,options:e});return new u(a)}static async get(t){return await s("plugin:store|get_store",{path:t}).then((t=>t?new u(t):null))}async set(t,e){await s("plugin:store|set",{rid:this.rid,key:t,value:e})}async get(t){const[e,a]=await s("plugin:store|get",{rid:this.rid,key:t});return a?e:void 0}async has(t){return await s("plugin:store|has",{rid:this.rid,key:t})}async delete(t){return await s("plugin:store|delete",{rid:this.rid,key:t})}async clear(){await s("plugin:store|clear",{rid:this.rid})}async reset(){await s("plugin:store|reset",{rid:this.rid})}async keys(){return await s("plugin:store|keys",{rid:this.rid})}async values(){return await s("plugin:store|values",{rid:this.rid})}async entries(){return await s("plugin:store|entries",{rid:this.rid})}async length(){return await s("plugin:store|length",{rid:this.rid})}async reload(t){await s("plugin:store|reload",{rid:this.rid,...t})}async save(){await s("plugin:store|save",{rid:this.rid})}async onKeyChange(t,e){return await n("store://change",(a=>{a.payload.resourceId===this.rid&&a.payload.key===t&&e(a.payload.exists?a.payload.value:void 0)}))}async onChange(t){return await n("store://change",(e=>{e.payload.resourceId===this.rid&&t(e.payload.key,e.payload.exists?e.payload.value:void 0)}))}}return t.LazyStore=class{get store(){return this._store||(this._store=o(this.path,this.options)),this._store}constructor(t,e){this.path=t,this.options=e}async init(){await this.store}async set(t,e){return(await this.store).set(t,e)}async get(t){return(await this.store).get(t)}async has(t){return(await this.store).has(t)}async delete(t){return(await this.store).delete(t)}async clear(){await(await this.store).clear()}async reset(){await(await this.store).reset()}async keys(){return(await this.store).keys()}async values(){return(await this.store).values()}async entries(){return(await this.store).entries()}async length(){return(await this.store).length()}async reload(t){await(await this.store).reload(t)}async save(){await(await this.store).save()}async onKeyChange(t,e){return(await this.store).onKeyChange(t,e)}async onChange(t){return(await this.store).onChange(t)}async close(){this._store&&await(await this._store).close()}},t.Store=u,t.getStore=async function(t){return await u.get(t)},t.load=o,t}({});Object.defineProperty(window.__TAURI__,"store",{value:__TAURI_PLUGIN_STORE__})}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.6.0",
|
||||
"@tauri-apps/cli": "2.7.1",
|
||||
"typescript": "^5.7.3",
|
||||
"vite": "^6.2.6"
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ interface ChangePayload<T> {
|
||||
* Options to create a store
|
||||
*/
|
||||
export type StoreOptions = {
|
||||
/**
|
||||
* Default value of the store
|
||||
*/
|
||||
defaults: { [key: string]: unknown }
|
||||
/**
|
||||
* Auto save on modification with debounce duration in milliseconds, it's 100ms by default, pass in `false` to disable it
|
||||
*/
|
||||
@@ -34,6 +38,10 @@ export type StoreOptions = {
|
||||
* Force create a new store with default values even if it already exists.
|
||||
*/
|
||||
createNew?: boolean
|
||||
/**
|
||||
* When creating the store, override the store with the on-disk state if it exists, ignoring defaults
|
||||
*/
|
||||
overrideDefaults?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,8 +153,8 @@ export class LazyStore implements IStore {
|
||||
return (await this.store).length()
|
||||
}
|
||||
|
||||
async reload(): Promise<void> {
|
||||
await (await this.store).reload()
|
||||
async reload(options?: ReloadOptions): Promise<void> {
|
||||
await (await this.store).reload(options)
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
@@ -196,7 +204,7 @@ export class Store extends Resource implements IStore {
|
||||
static async load(path: string, options?: StoreOptions): Promise<Store> {
|
||||
const rid = await invoke<number>('plugin:store|load', {
|
||||
path,
|
||||
...options
|
||||
options
|
||||
})
|
||||
return new Store(rid)
|
||||
}
|
||||
@@ -280,8 +288,8 @@ export class Store extends Resource implements IStore {
|
||||
return await invoke('plugin:store|length', { rid: this.rid })
|
||||
}
|
||||
|
||||
async reload(): Promise<void> {
|
||||
await invoke('plugin:store|reload', { rid: this.rid })
|
||||
async reload(options?: ReloadOptions): Promise<void> {
|
||||
await invoke('plugin:store|reload', { rid: this.rid, ...options })
|
||||
}
|
||||
|
||||
async save(): Promise<void> {
|
||||
@@ -396,10 +404,15 @@ interface IStore {
|
||||
*
|
||||
* This method is useful if the on-disk state was edited by the user and you want to synchronize the changes.
|
||||
*
|
||||
* Note: This method does not emit change events.
|
||||
* Note:
|
||||
* - This method loads the data and merges it with the current store,
|
||||
* this behavior will be changed to resetting to default first and then merging with the on-disk state in v3,
|
||||
* to fully match the store with the on-disk state, set {@linkcode ReloadOptions.ignoreDefaults} to `true`
|
||||
* - This method does not emit change events.
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
reload(): Promise<void>
|
||||
reload(options?: ReloadOptions): Promise<void>
|
||||
|
||||
/**
|
||||
* Saves the store to disk at the store's `path`.
|
||||
@@ -437,3 +450,13 @@ interface IStore {
|
||||
*/
|
||||
close(): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Options to {@linkcode IStore.reload} a {@linkcode IStore}
|
||||
*/
|
||||
export type ReloadOptions = {
|
||||
/**
|
||||
* To fully match the store with the on-disk state, ignoring defaults
|
||||
*/
|
||||
ignoreDefaults?: boolean
|
||||
}
|
||||
|
||||
+43
-23
@@ -53,17 +53,36 @@ enum AutoSave {
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct LoadStoreOptions {
|
||||
defaults: Option<HashMap<String, JsonValue>>,
|
||||
auto_save: Option<AutoSave>,
|
||||
serialize_fn_name: Option<String>,
|
||||
deserialize_fn_name: Option<String>,
|
||||
#[serde(default)]
|
||||
create_new: bool,
|
||||
#[serde(default)]
|
||||
override_defaults: bool,
|
||||
}
|
||||
|
||||
fn builder<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
store_state: State<'_, StoreState>,
|
||||
path: PathBuf,
|
||||
auto_save: Option<AutoSave>,
|
||||
serialize_fn_name: Option<String>,
|
||||
deserialize_fn_name: Option<String>,
|
||||
create_new: bool,
|
||||
options: Option<LoadStoreOptions>,
|
||||
) -> Result<StoreBuilder<R>> {
|
||||
let mut builder = app.store_builder(path);
|
||||
if let Some(auto_save) = auto_save {
|
||||
|
||||
let Some(options) = options else {
|
||||
return Ok(builder);
|
||||
};
|
||||
|
||||
if let Some(defaults) = options.defaults {
|
||||
builder = builder.defaults(defaults);
|
||||
}
|
||||
|
||||
if let Some(auto_save) = options.auto_save {
|
||||
match auto_save {
|
||||
AutoSave::DebounceDuration(duration) => {
|
||||
builder = builder.auto_save(Duration::from_millis(duration));
|
||||
@@ -75,7 +94,7 @@ fn builder<R: Runtime>(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(serialize_fn_name) = serialize_fn_name {
|
||||
if let Some(serialize_fn_name) = options.serialize_fn_name {
|
||||
let serialize_fn = store_state
|
||||
.serialize_fns
|
||||
.get(&serialize_fn_name)
|
||||
@@ -83,7 +102,7 @@ fn builder<R: Runtime>(
|
||||
builder = builder.serialize(*serialize_fn);
|
||||
}
|
||||
|
||||
if let Some(deserialize_fn_name) = deserialize_fn_name {
|
||||
if let Some(deserialize_fn_name) = options.deserialize_fn_name {
|
||||
let deserialize_fn = store_state
|
||||
.deserialize_fns
|
||||
.get(&deserialize_fn_name)
|
||||
@@ -91,10 +110,14 @@ fn builder<R: Runtime>(
|
||||
builder = builder.deserialize(*deserialize_fn);
|
||||
}
|
||||
|
||||
if create_new {
|
||||
if options.create_new {
|
||||
builder = builder.create_new();
|
||||
}
|
||||
|
||||
if options.override_defaults {
|
||||
builder = builder.override_defaults();
|
||||
}
|
||||
|
||||
Ok(builder)
|
||||
}
|
||||
|
||||
@@ -103,20 +126,9 @@ async fn load<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
store_state: State<'_, StoreState>,
|
||||
path: PathBuf,
|
||||
auto_save: Option<AutoSave>,
|
||||
serialize_fn_name: Option<String>,
|
||||
deserialize_fn_name: Option<String>,
|
||||
create_new: Option<bool>,
|
||||
options: Option<LoadStoreOptions>,
|
||||
) -> Result<ResourceId> {
|
||||
let builder = builder(
|
||||
app,
|
||||
store_state,
|
||||
path,
|
||||
auto_save,
|
||||
serialize_fn_name,
|
||||
deserialize_fn_name,
|
||||
create_new.unwrap_or_default(),
|
||||
)?;
|
||||
let builder = builder(app, store_state, path, options)?;
|
||||
let (_, rid) = builder.build_inner()?;
|
||||
Ok(rid)
|
||||
}
|
||||
@@ -209,9 +221,17 @@ async fn length<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<usize>
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn reload<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> Result<()> {
|
||||
async fn reload<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
rid: ResourceId,
|
||||
ignore_defaults: Option<bool>,
|
||||
) -> Result<()> {
|
||||
let store = app.resources_table().get::<Store<R>>(rid)?;
|
||||
store.reload()
|
||||
if ignore_defaults.unwrap_or_default() {
|
||||
store.reload_ignore_defaults()
|
||||
} else {
|
||||
store.reload()
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -39,6 +39,7 @@ pub struct StoreBuilder<R: Runtime> {
|
||||
deserialize_fn: DeserializeFn,
|
||||
auto_save: Option<Duration>,
|
||||
create_new: bool,
|
||||
override_defaults: bool,
|
||||
}
|
||||
|
||||
impl<R: Runtime> StoreBuilder<R> {
|
||||
@@ -66,6 +67,7 @@ impl<R: Runtime> StoreBuilder<R> {
|
||||
deserialize_fn,
|
||||
auto_save: Some(Duration::from_millis(100)),
|
||||
create_new: false,
|
||||
override_defaults: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +180,12 @@ impl<R: Runtime> StoreBuilder<R> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the store values when creating the store, ignoring defaults.
|
||||
pub fn override_defaults(mut self) -> Self {
|
||||
self.override_defaults = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn build_inner(mut self) -> crate::Result<(Arc<Store<R>>, ResourceId)> {
|
||||
let stores = self.app.state::<StoreState>().stores.clone();
|
||||
let mut stores = stores.lock().unwrap();
|
||||
@@ -205,7 +213,11 @@ impl<R: Runtime> StoreBuilder<R> {
|
||||
);
|
||||
|
||||
if !self.create_new {
|
||||
let _ = store_inner.load();
|
||||
if self.override_defaults {
|
||||
let _ = store_inner.load_ignore_defaults();
|
||||
} else {
|
||||
let _ = store_inner.load();
|
||||
}
|
||||
}
|
||||
|
||||
let store = Store {
|
||||
@@ -284,6 +296,8 @@ impl<R: Runtime> StoreInner<R> {
|
||||
}
|
||||
|
||||
/// Update the store from the on-disk state
|
||||
///
|
||||
/// Note: This method loads the data and merges it with the current store
|
||||
pub fn load(&mut self) -> crate::Result<()> {
|
||||
let bytes = fs::read(&self.path)?;
|
||||
|
||||
@@ -293,6 +307,13 @@ impl<R: Runtime> StoreInner<R> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load the store from the on-disk state, ignoring defaults
|
||||
pub fn load_ignore_defaults(&mut self) -> crate::Result<()> {
|
||||
let bytes = fs::read(&self.path)?;
|
||||
self.cache = (self.deserialize_fn)(&bytes).map_err(crate::Error::Deserialize)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Inserts a key-value pair into the store.
|
||||
pub fn set(&mut self, key: impl Into<String>, value: impl Into<JsonValue>) {
|
||||
let key = key.into();
|
||||
@@ -499,10 +520,24 @@ impl<R: Runtime> Store<R> {
|
||||
}
|
||||
|
||||
/// Update the store from the on-disk state
|
||||
///
|
||||
/// Note:
|
||||
/// - This method loads the data and merges it with the current store,
|
||||
/// this behavior will be changed to resetting to default first and then merging with the on-disk state in v3,
|
||||
/// to fully match the store with the on-disk state,
|
||||
/// use [`reload_override_defaults`](Self::reload_override_defaults) instead
|
||||
/// - This method does not emit change events
|
||||
pub fn reload(&self) -> crate::Result<()> {
|
||||
self.store.lock().unwrap().load()
|
||||
}
|
||||
|
||||
/// Load the store from the on-disk state, ignoring defaults
|
||||
///
|
||||
/// Note: This method does not emit change events
|
||||
pub fn reload_ignore_defaults(&self) -> crate::Result<()> {
|
||||
self.store.lock().unwrap().load_ignore_defaults()
|
||||
}
|
||||
|
||||
/// Saves the store to disk at the store's `path`.
|
||||
pub fn save(&self) -> crate::Result<()> {
|
||||
if let Some(sender) = self.auto_save_debounce_sender.lock().unwrap().take() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user