mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-06-16 14:30:06 +02:00
Compare commits
197 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 57f6e45b27 | |||
| 35aa52f45e | |||
| b65a193e6d | |||
| bbc177150f | |||
| e6e2edca11 | |||
| 7ecd19da51 | |||
| a0b6c8ff3b | |||
| c12ea9306a | |||
| 98e2c11eef | |||
| 50b159f668 | |||
| 305c4f6b4e | |||
| 89f3e17952 | |||
| d4613ff002 | |||
| 69a9d5771c | |||
| 61e9b0ab64 | |||
| 25ad21beff | |||
| 2dc3f3f039 | |||
| c27af9128d | |||
| b60dd88702 | |||
| a97033bcd9 | |||
| 4375c98bed | |||
| 82fbb0c790 | |||
| de6bf68585 | |||
| 734c627084 | |||
| 05c5da072b | |||
| 0d126ff0ad | |||
| f122ee98c6 | |||
| 2308f2299d | |||
| 82c404635b | |||
| b1dbee2c55 | |||
| d7a0bb325d | |||
| f3d75f7abb | |||
| 6e2e7e48c3 | |||
| 9a2c98f450 | |||
| 4a2ecb6287 | |||
| 31415effdf | |||
| 04b33ea0b0 | |||
| 54e21f142b | |||
| d528c88b4f | |||
| 69146fa852 | |||
| 9f68f2d827 | |||
| 3d0d2e041b | |||
| 1e3d7ef16e | |||
| ce6835d50f | |||
| 1d9f47c882 | |||
| e221a04ef4 | |||
| eebfd2ed3e | |||
| 521cd8b372 | |||
| e4a40f4423 | |||
| fa8b11f19d | |||
| baefd761e0 | |||
| 2804803949 | |||
| d9d51eb8ea | |||
| 1483c63101 | |||
| 26ed78989a | |||
| f1564e58b6 | |||
| f2d4abb9e2 | |||
| 3dcf7522b1 | |||
| 02068550e8 | |||
| 66a75ece27 | |||
| fa601e8754 | |||
| d8bfe61f20 | |||
| e8915f17e4 | |||
| 6de61f854b | |||
| ad7a8d3e42 | |||
| d5509a9ac8 | |||
| 06727b19c1 | |||
| 2f9fa79747 | |||
| 1db4b0719d | |||
| dff6fa986a | |||
| a4aa53ab90 | |||
| ae278ddf60 | |||
| e644f38673 | |||
| 8bfa445023 | |||
| 14fb36e347 | |||
| 5767b848fa | |||
| 277a45f56c | |||
| 8dbe7e3233 | |||
| c23fa03f07 | |||
| 631d0e256a | |||
| b4348cee92 | |||
| 3019063ae1 | |||
| 944614f46a | |||
| a368cef912 | |||
| 27790aa67c | |||
| ad910b1135 | |||
| 6b854421a1 | |||
| 5438a5cd22 | |||
| 5cd7778723 | |||
| 1a03e9761f | |||
| 1d4bffadda | |||
| b8794272ae | |||
| 2a625adff3 | |||
| 371cd8227c | |||
| 70ef6f8d3e | |||
| 5f0ac1436f | |||
| ea172bfa3c | |||
| 6aead24047 | |||
| e7a68fa637 | |||
| b5550a3b0d | |||
| 93426f8512 | |||
| 4ee61e055e | |||
| 06124af8d6 | |||
| 060219e597 | |||
| c7e9766ff5 | |||
| d4a8ce962b | |||
| cdc7eec415 | |||
| 6314b004ab | |||
| fb4c8ae54c | |||
| fccc1cfb7e | |||
| 3702308e6f | |||
| c9c8b39b56 | |||
| 3c396feedf | |||
| b514d482fa | |||
| 60e5714ceb | |||
| 654bf4891a | |||
| 6c9b61fb65 | |||
| 6b5b1053ba | |||
| 67a7bf80f8 | |||
| e25a9339f4 | |||
| 92f98a93b4 | |||
| c0f82e5a5e | |||
| 5204145c6a | |||
| b4ee33dd01 | |||
| e933acb004 | |||
| e4a9154f4a | |||
| d44ac9afed | |||
| 1b197c2d4d | |||
| 650ff5ff23 | |||
| 2930aa39a4 | |||
| 3e92ce260b | |||
| 12da195fce | |||
| 30dd109d9f | |||
| 5e5bc30ad0 | |||
| f6dca71343 | |||
| 021e573248 | |||
| 51b430be98 | |||
| fd439b143e | |||
| 2522b71f6b | |||
| 9021a73247 | |||
| 625bb1c096 | |||
| 6215afe023 | |||
| 8cf8eeab02 | |||
| 509eba8d44 | |||
| 9ac5fe84e7 | |||
| c247410319 | |||
| de45034082 | |||
| 50c6b7c644 | |||
| b79d02d896 | |||
| b75f9f5cd3 | |||
| d865ed4768 | |||
| 1107c46425 | |||
| 23a3705857 | |||
| 6f65e68340 | |||
| 1a0b791650 | |||
| 2d03e2eac2 | |||
| 21d721a0c2 | |||
| 8abb31ee59 | |||
| 0354046817 | |||
| 04a0954857 | |||
| 9e4e859bea | |||
| 2371804172 | |||
| 90f9b9310a | |||
| 1c58f3372c | |||
| 75617a6a92 | |||
| 5a963a0496 | |||
| 76f4e7bb84 | |||
| 670ac1d7c1 | |||
| ed0deef2cf | |||
| 50cebdb6d5 | |||
| dd2ea9cfa5 | |||
| 515182a179 | |||
| d3d290ab8a | |||
| b51c827b2c | |||
| a4b71a1992 | |||
| 4eb36b0ff5 | |||
| b8056f484c | |||
| 5ac8fbb1fa | |||
| e0323ec752 | |||
| cc98e6a892 | |||
| af22ae0a97 | |||
| 2f24c7a70c | |||
| fe23a5e013 | |||
| ff6d23ede1 | |||
| 449dd117a4 | |||
| 9b43f48856 | |||
| af08c66faa | |||
| 7974acae22 | |||
| a985359e69 | |||
| 97bebcf6e8 | |||
| 27ddcd0abe | |||
| d7fb5623d6 | |||
| d4f8299b12 | |||
| 341919ed57 | |||
| 124f2191aa | |||
| 0970b94949 | |||
| 7340242d4e |
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
"single-instance": minor:fix
|
||||||
|
---
|
||||||
|
|
||||||
|
**Breaking Change:** On Linux, the DBus ID/name will now be `<bundle-id>.SingleInstance` instead of `org.<bundle_id_underscores>.SingleInstance` to follow DBus specifications.
|
||||||
|
|
||||||
|
This will break the single-instance mechanism across different app versions if the app was installed multiple times.
|
||||||
|
|
||||||
|
Added `dbus_id` builder method, which can be used to restore previous behavior. For a bundle identifier of `com.tauri.my-example` this would be `dbus_id("org.com_tauri_my_example")`.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"single-instance": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add `setup` function to run the single instance initialization manually, without waiting for the tauri setup hook to run.
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
name: 🐞 Bug Report
|
||||||
|
description: Report a bug
|
||||||
|
labels: ['type: bug']
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## First of all
|
||||||
|
1. Please search for [existing issues](https://github.com/tauri-apps/plugins-workspace/issues?q=is%3Aissue) about this problem first.
|
||||||
|
2. Make sure `rustc` and all relevant Tauri packages are up to date.
|
||||||
|
3. Make sure it's an issue with a tauri plugin and not something else you are using.
|
||||||
|
4. Remember to follow our community guidelines and be friendly.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
description: A clear description of what the bug is. Include screenshots if applicable.
|
||||||
|
placeholder: Bug description
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction
|
||||||
|
attributes:
|
||||||
|
label: Reproduction
|
||||||
|
description: A link to a reproduction repo or steps to reproduce the behaviour.
|
||||||
|
placeholder: |
|
||||||
|
Please provide a minimal reproduction or steps to reproduce, see this guide https://stackoverflow.com/help/minimal-reproducible-example
|
||||||
|
Why reproduction is required? see this article https://antfu.me/posts/why-reproductions-are-required
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
description: A clear description of what you expected to happen.
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: info
|
||||||
|
attributes:
|
||||||
|
label: Full `tauri info` output
|
||||||
|
description: 'Output of `npm run tauri info` or `cargo tauri info`. Issues without this will be closed!'
|
||||||
|
render: text
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Stack trace
|
||||||
|
description: A stack trace or ANY error messages you may see.
|
||||||
|
render: text
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context about the problem here. For example a link to a Discord discussion.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: 💡 Request a new plugin
|
||||||
|
url: https://github.com/orgs/tauri-apps/discussions/new?category=plugin-requests
|
||||||
|
about: Propose a new Plugin to the community or the Tauri org.
|
||||||
|
- name: 💬 Discord Chat
|
||||||
|
url: https://discord.com/invite/tauri
|
||||||
|
about: Ask questions and talk to other Tauri users and the maintainers
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
name: 📚 Docs Report
|
||||||
|
about: Create a report to help us improve the docs
|
||||||
|
labels: 'type: documentation'
|
||||||
|
---
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
name: 💡 Feature Request
|
||||||
|
description: Request a feature or enhancement for an existing plugin
|
||||||
|
labels: ['type: feature request']
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
## First of all
|
||||||
|
- Please search for [existing issues](https://github.com/tauri-apps/plugins-workspace/issues?q=is%3Aissue) for this request first.
|
||||||
|
- Only requests for plugins that exist in this repo are allowed.
|
||||||
|
- You can request new plugins [here](https://github.com/orgs/tauri-apps/discussions/new?category=plugin-requests)
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Describe the problem
|
||||||
|
description: A clear description of the problem this feature would solve
|
||||||
|
placeholder: "I'm always frustrated when..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: "Describe the solution you'd like"
|
||||||
|
description: A clear description of what change you would like
|
||||||
|
placeholder: 'I would like to...'
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternatives considered
|
||||||
|
description: "Any alternative solutions you've considered"
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add any other context about the problem here.
|
||||||
@@ -11,7 +11,7 @@ on:
|
|||||||
- v2
|
- v2
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
# required for npm provenance
|
# required for oidc token
|
||||||
id-token: write
|
id-token: write
|
||||||
# required to create the GitHub Release
|
# required to create the GitHub Release
|
||||||
contents: write
|
contents: write
|
||||||
@@ -62,7 +62,6 @@ jobs:
|
|||||||
id: covector
|
id: covector
|
||||||
env:
|
env:
|
||||||
CARGO_TARGET_DIR: /mnt/target
|
CARGO_TARGET_DIR: /mnt/target
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
|
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
command: 'version-or-publish'
|
command: 'version-or-publish'
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
name: integration tests
|
name: integration tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- v1
|
- v1
|
||||||
|
|||||||
@@ -154,4 +154,5 @@ jobs:
|
|||||||
run: cargo clippy --package ${{ matrix.package }} --all-targets -- -D warnings
|
run: cargo clippy --package ${{ matrix.package }} --all-targets -- -D warnings
|
||||||
|
|
||||||
- name: clippy ${{ matrix.package }} --all-features
|
- name: clippy ${{ matrix.package }} --all-features
|
||||||
|
if: matrix.package != 'tauri-plugin-dialog'
|
||||||
run: cargo clippy --package ${{ matrix.package }} --all-targets --all-features -- -D warnings
|
run: cargo clippy --package ${{ matrix.package }} --all-targets --all-features -- -D warnings
|
||||||
|
|||||||
@@ -246,9 +246,9 @@ jobs:
|
|||||||
run: cargo +stable install cross --git https://github.com/cross-rs/cross
|
run: cargo +stable install cross --git https://github.com/cross-rs/cross
|
||||||
|
|
||||||
- name: test ${{ matrix.package }}
|
- name: test ${{ matrix.package }}
|
||||||
if: matrix.package != 'tauri-plugin-http'
|
if: ${{ matrix.package != 'tauri-plugin-http' && matrix.package != 'tauri-plugin-dialog' }}
|
||||||
run: ${{ matrix.platform.runner }} ${{ matrix.platform.command }} --package ${{ matrix.package }} --target ${{ matrix.platform.target }} --all-targets --all-features
|
run: ${{ matrix.platform.runner }} ${{ matrix.platform.command }} --package ${{ matrix.package }} --target ${{ matrix.platform.target }} --all-targets --all-features
|
||||||
|
|
||||||
- name: test ${{ matrix.package }}
|
- name: test ${{ matrix.package }}
|
||||||
if: matrix.package == 'tauri-plugin-http'
|
if: ${{ matrix.package == 'tauri-plugin-http' || matrix.package == 'tauri-plugin-dialog' }}
|
||||||
run: ${{ matrix.platform.runner }} ${{ matrix.platform.command }} --package ${{ matrix.package }} --target ${{ matrix.platform.target }} --all-targets
|
run: ${{ matrix.platform.runner }} ${{ matrix.platform.command }} --package ${{ matrix.package }} --target ${{ matrix.platform.target }} --all-targets
|
||||||
|
|||||||
+2
-1
@@ -21,6 +21,7 @@ target/
|
|||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
bun.lockb
|
bun.lockb
|
||||||
|
bun.lock
|
||||||
|
|
||||||
# rust compiled folders
|
# rust compiled folders
|
||||||
target/
|
target/
|
||||||
@@ -58,4 +59,4 @@ pids
|
|||||||
.idea
|
.idea
|
||||||
debug.log
|
debug.log
|
||||||
TODO.md
|
TODO.md
|
||||||
.aider*
|
.aider.*
|
||||||
|
|||||||
Generated
+436
-204
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -12,10 +12,10 @@ resolver = "2"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
tauri = { version = "2.6", default-features = false }
|
tauri = { version = "2.10", default-features = false }
|
||||||
tauri-build = "2.3"
|
tauri-build = "2.5"
|
||||||
tauri-plugin = "2.3"
|
tauri-plugin = "2.5"
|
||||||
tauri-utils = "2.5"
|
tauri-utils = "2.8"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
url = "2"
|
url = "2"
|
||||||
|
|||||||
+2
-1
@@ -3,11 +3,12 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import eslint from '@eslint/js'
|
import eslint from '@eslint/js'
|
||||||
|
import { defineConfig } from 'eslint/config'
|
||||||
import eslintConfigPrettier from 'eslint-config-prettier'
|
import eslintConfigPrettier from 'eslint-config-prettier'
|
||||||
import eslintPluginSecurity from 'eslint-plugin-security'
|
import eslintPluginSecurity from 'eslint-plugin-security'
|
||||||
import tseslint from 'typescript-eslint'
|
import tseslint from 'typescript-eslint'
|
||||||
|
|
||||||
export default tseslint.config(
|
export default defineConfig(
|
||||||
{
|
{
|
||||||
ignores: [
|
ignores: [
|
||||||
'**/target',
|
'**/target',
|
||||||
|
|||||||
@@ -1,5 +1,108 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.0.37]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `updater-js@2.10.0`
|
||||||
|
- Upgraded to `barcode-scanner-js@2.4.4`
|
||||||
|
- Upgraded to `http-js@2.5.7`
|
||||||
|
- Upgraded to `shell-js@2.3.5`
|
||||||
|
|
||||||
|
## \[2.0.36]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `http-js@2.5.6`
|
||||||
|
- Upgraded to `dialog-js@2.6.0`
|
||||||
|
|
||||||
|
## \[2.0.35]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `dialog-js@2.5.0`
|
||||||
|
- Upgraded to `log-js@2.8.0`
|
||||||
|
- Upgraded to `shell-js@2.3.4`
|
||||||
|
- Upgraded to `barcode-scanner-js@2.4.3`
|
||||||
|
- Upgraded to `fs-js@2.4.5`
|
||||||
|
- Upgraded to `http-js@2.5.5`
|
||||||
|
- Upgraded to `nfc-js@2.3.4`
|
||||||
|
- Upgraded to `opener-js@2.5.3`
|
||||||
|
- Upgraded to `store-js@2.4.2`
|
||||||
|
|
||||||
|
## \[2.0.34]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `barcode-scanner-js@2.4.2`
|
||||||
|
- Upgraded to `biometric-js@2.3.2`
|
||||||
|
- Upgraded to `cli-js@2.4.1`
|
||||||
|
- Upgraded to `clipboard-manager-js@2.3.2`
|
||||||
|
- Upgraded to `dialog-js@2.4.2`
|
||||||
|
- Upgraded to `fs-js@2.4.4`
|
||||||
|
- Upgraded to `global-shortcut-js@2.3.1`
|
||||||
|
- Upgraded to `http-js@2.5.4`
|
||||||
|
- Upgraded to `log-js@2.7.1`
|
||||||
|
- Upgraded to `nfc-js@2.3.3`
|
||||||
|
- Upgraded to `notification-js@2.3.3`
|
||||||
|
- Upgraded to `opener-js@2.5.2`
|
||||||
|
- Upgraded to `os-js@2.3.2`
|
||||||
|
- Upgraded to `process-js@2.3.1`
|
||||||
|
- Upgraded to `shell-js@2.3.3`
|
||||||
|
- Upgraded to `store-js@2.4.1`
|
||||||
|
|
||||||
|
## \[2.0.33]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `barcode-scanner-js@2.4.1`
|
||||||
|
- Upgraded to `biometric-js@2.3.1`
|
||||||
|
- Upgraded to `clipboard-manager-js@2.3.1`
|
||||||
|
- Upgraded to `dialog-js@2.4.1`
|
||||||
|
- Upgraded to `fs-js@2.4.3`
|
||||||
|
- Upgraded to `nfc-js@2.3.2`
|
||||||
|
- Upgraded to `notification-js@2.3.2`
|
||||||
|
- Upgraded to `opener-js@2.5.1`
|
||||||
|
- Upgraded to `shell-js@2.3.2`
|
||||||
|
- Upgraded to `http-js@2.5.3`
|
||||||
|
|
||||||
|
## \[2.0.32]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `dialog-js@2.4.0`
|
||||||
|
- Upgraded to `log-js@2.7.0`
|
||||||
|
|
||||||
|
## \[2.0.31]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `shell-js@2.3.1`
|
||||||
|
|
||||||
|
## \[2.0.30]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `notification-js@2.3.1`
|
||||||
|
|
||||||
|
## \[2.0.29]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `fs-js@2.4.2`
|
||||||
|
- Upgraded to `nfc-js@2.3.1`
|
||||||
|
- Upgraded to `opener-js@2.5.0`
|
||||||
|
- Upgraded to `os-js@2.3.1`
|
||||||
|
- Upgraded to `store-js@2.4.0`
|
||||||
|
- Upgraded to `dialog-js@2.3.3`
|
||||||
|
- Upgraded to `http-js@2.5.2`
|
||||||
|
|
||||||
|
## \[2.0.28]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `dialog-js@2.3.2`
|
||||||
|
|
||||||
## \[2.0.27]
|
## \[2.0.27]
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
|
content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=0"
|
||||||
/>
|
/>
|
||||||
<title>Svelte + Vite App</title>
|
<title>Svelte + Vite App</title>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
+20
-20
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "api",
|
"name": "api",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.0.27",
|
"version": "2.0.37",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --clearScreen false",
|
"dev": "vite --clearScreen false",
|
||||||
@@ -10,25 +10,25 @@
|
|||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "2.6.0",
|
"@tauri-apps/api": "^2.10.1",
|
||||||
"@tauri-apps/plugin-barcode-scanner": "^2.4.0",
|
"@tauri-apps/plugin-barcode-scanner": "^2.4.4",
|
||||||
"@tauri-apps/plugin-biometric": "^2.3.0",
|
"@tauri-apps/plugin-biometric": "^2.3.2",
|
||||||
"@tauri-apps/plugin-cli": "^2.4.0",
|
"@tauri-apps/plugin-cli": "^2.4.1",
|
||||||
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
|
"@tauri-apps/plugin-clipboard-manager": "^2.3.2",
|
||||||
"@tauri-apps/plugin-dialog": "^2.3.1",
|
"@tauri-apps/plugin-dialog": "^2.6.0",
|
||||||
"@tauri-apps/plugin-fs": "^2.4.1",
|
"@tauri-apps/plugin-fs": "^2.4.5",
|
||||||
"@tauri-apps/plugin-geolocation": "^2.2.0",
|
"@tauri-apps/plugin-geolocation": "^2.2.0",
|
||||||
"@tauri-apps/plugin-global-shortcut": "^2.3.0",
|
"@tauri-apps/plugin-global-shortcut": "^2.3.1",
|
||||||
"@tauri-apps/plugin-haptics": "^2.2.0",
|
"@tauri-apps/plugin-haptics": "^2.2.0",
|
||||||
"@tauri-apps/plugin-http": "^2.5.1",
|
"@tauri-apps/plugin-http": "^2.5.7",
|
||||||
"@tauri-apps/plugin-nfc": "^2.3.0",
|
"@tauri-apps/plugin-nfc": "^2.3.4",
|
||||||
"@tauri-apps/plugin-notification": "^2.3.0",
|
"@tauri-apps/plugin-notification": "^2.3.3",
|
||||||
"@tauri-apps/plugin-opener": "^2.4.0",
|
"@tauri-apps/plugin-opener": "^2.5.3",
|
||||||
"@tauri-apps/plugin-os": "^2.3.0",
|
"@tauri-apps/plugin-os": "^2.3.2",
|
||||||
"@tauri-apps/plugin-process": "^2.3.0",
|
"@tauri-apps/plugin-process": "^2.3.1",
|
||||||
"@tauri-apps/plugin-shell": "^2.3.0",
|
"@tauri-apps/plugin-shell": "^2.3.5",
|
||||||
"@tauri-apps/plugin-store": "^2.3.0",
|
"@tauri-apps/plugin-store": "^2.4.2",
|
||||||
"@tauri-apps/plugin-updater": "^2.9.0",
|
"@tauri-apps/plugin-updater": "^2.10.0",
|
||||||
"@tauri-apps/plugin-upload": "^2.3.0",
|
"@tauri-apps/plugin-upload": "^2.3.0",
|
||||||
"@zerodevx/svelte-json-view": "1.0.11"
|
"@zerodevx/svelte-json-view": "1.0.11"
|
||||||
},
|
},
|
||||||
@@ -36,10 +36,10 @@
|
|||||||
"@iconify-json/codicon": "^1.2.12",
|
"@iconify-json/codicon": "^1.2.12",
|
||||||
"@iconify-json/ph": "^1.2.2",
|
"@iconify-json/ph": "^1.2.2",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||||
"@tauri-apps/cli": "2.6.2",
|
"@tauri-apps/cli": "2.10.0",
|
||||||
"@unocss/extractor-svelte": "^66.3.3",
|
"@unocss/extractor-svelte": "^66.3.3",
|
||||||
"svelte": "^5.20.4",
|
"svelte": "^5.20.4",
|
||||||
"unocss": "^66.3.3",
|
"unocss": "^66.3.3",
|
||||||
"vite": "^7.0.4"
|
"vite": "^7.0.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,112 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.0.41]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `updater@2.10.0`
|
||||||
|
- Upgraded to `barcode-scanner@2.4.4`
|
||||||
|
- Upgraded to `http@2.5.7`
|
||||||
|
- Upgraded to `shell@2.3.5`
|
||||||
|
|
||||||
|
## \[2.0.40]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `http@2.5.6`
|
||||||
|
- Upgraded to `dialog@2.6.0`
|
||||||
|
|
||||||
|
## \[2.0.39]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `dialog@2.5.0`
|
||||||
|
- Upgraded to `log@2.8.0`
|
||||||
|
- Upgraded to `shell@2.3.4`
|
||||||
|
- Upgraded to `barcode-scanner@2.4.3`
|
||||||
|
- Upgraded to `fs@2.4.5`
|
||||||
|
- Upgraded to `http@2.5.5`
|
||||||
|
- Upgraded to `nfc@2.3.4`
|
||||||
|
- Upgraded to `opener@2.5.3`
|
||||||
|
- Upgraded to `store@2.4.2`
|
||||||
|
|
||||||
|
## \[2.0.38]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `barcode-scanner@2.4.2`
|
||||||
|
- Upgraded to `biometric@2.3.2`
|
||||||
|
- Upgraded to `cli@2.4.1`
|
||||||
|
- Upgraded to `clipboard-manager@2.3.2`
|
||||||
|
- Upgraded to `dialog@2.4.2`
|
||||||
|
- Upgraded to `fs@2.4.4`
|
||||||
|
- Upgraded to `geolocation@2.3.2`
|
||||||
|
- Upgraded to `global-shortcut@2.3.1`
|
||||||
|
- Upgraded to `haptics@2.3.2`
|
||||||
|
- Upgraded to `http@2.5.4`
|
||||||
|
- Upgraded to `log@2.7.1`
|
||||||
|
- Upgraded to `nfc@2.3.3`
|
||||||
|
- Upgraded to `notification@2.3.3`
|
||||||
|
- Upgraded to `opener@2.5.2`
|
||||||
|
- Upgraded to `os@2.3.2`
|
||||||
|
- Upgraded to `process@2.3.1`
|
||||||
|
- Upgraded to `shell@2.3.3`
|
||||||
|
- Upgraded to `store@2.4.1`
|
||||||
|
|
||||||
|
## \[2.0.37]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `barcode-scanner@2.4.1`
|
||||||
|
- Upgraded to `biometric@2.3.1`
|
||||||
|
- Upgraded to `clipboard-manager@2.3.1`
|
||||||
|
- Upgraded to `dialog@2.4.1`
|
||||||
|
- Upgraded to `fs@2.4.3`
|
||||||
|
- Upgraded to `geolocation@2.3.1`
|
||||||
|
- Upgraded to `haptics@2.3.1`
|
||||||
|
- Upgraded to `nfc@2.3.2`
|
||||||
|
- Upgraded to `notification@2.3.2`
|
||||||
|
- Upgraded to `opener@2.5.1`
|
||||||
|
- Upgraded to `shell@2.3.2`
|
||||||
|
- Upgraded to `http@2.5.3`
|
||||||
|
|
||||||
|
## \[2.0.36]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `dialog@2.4.0`
|
||||||
|
- Upgraded to `log@2.7.0`
|
||||||
|
|
||||||
|
## \[2.0.35]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `shell@2.3.1`
|
||||||
|
|
||||||
|
## \[2.0.34]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `notification@2.3.1`
|
||||||
|
|
||||||
|
## \[2.0.33]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `fs@2.4.2`
|
||||||
|
- Upgraded to `nfc@2.3.1`
|
||||||
|
- Upgraded to `opener@2.5.0`
|
||||||
|
- Upgraded to `os@2.3.1`
|
||||||
|
- Upgraded to `store@2.4.0`
|
||||||
|
- Upgraded to `dialog@2.3.3`
|
||||||
|
- Upgraded to `http@2.5.2`
|
||||||
|
|
||||||
|
## \[2.0.32]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `dialog@2.3.2`
|
||||||
|
|
||||||
## \[2.0.31]
|
## \[2.0.31]
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "api"
|
name = "api"
|
||||||
publish = false
|
publish = false
|
||||||
version = "2.0.31"
|
version = "2.0.41"
|
||||||
description = "An example Tauri Application showcasing the api"
|
description = "An example Tauri Application showcasing the api"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = { workspace = true }
|
rust-version = { workspace = true }
|
||||||
@@ -20,24 +20,24 @@ serde = { workspace = true }
|
|||||||
tiny_http = "0.12"
|
tiny_http = "0.12"
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
tauri-plugin-log = { path = "../../../plugins/log", version = "2.6.0" }
|
tauri-plugin-log = { path = "../../../plugins/log", version = "2.8.0" }
|
||||||
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.1", features = [
|
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.4.5", features = [
|
||||||
"watch",
|
"watch",
|
||||||
] }
|
] }
|
||||||
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.0" }
|
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.3.2" }
|
||||||
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.3.1" }
|
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.6.0" }
|
||||||
tauri-plugin-http = { path = "../../../plugins/http", features = [
|
tauri-plugin-http = { path = "../../../plugins/http", features = [
|
||||||
"multipart",
|
"multipart",
|
||||||
"cookies",
|
"cookies",
|
||||||
], version = "2.5.1" }
|
], version = "2.5.7" }
|
||||||
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.0", features = [
|
tauri-plugin-notification = { path = "../../../plugins/notification", version = "2.3.3", features = [
|
||||||
"windows7-compat",
|
"windows7-compat",
|
||||||
] }
|
] }
|
||||||
tauri-plugin-os = { path = "../../../plugins/os", version = "2.3.0" }
|
tauri-plugin-os = { path = "../../../plugins/os", version = "2.3.2" }
|
||||||
tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.0" }
|
tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.1" }
|
||||||
tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.4.0" }
|
tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.5.3" }
|
||||||
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.0" }
|
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.5" }
|
||||||
tauri-plugin-store = { path = "../../../plugins/store", version = "2.3.0" }
|
tauri-plugin-store = { path = "../../../plugins/store", version = "2.4.2" }
|
||||||
tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" }
|
tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" }
|
||||||
|
|
||||||
[dependencies.tauri]
|
[dependencies.tauri]
|
||||||
@@ -55,17 +55,17 @@ features = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||||
tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.4.0" }
|
tauri-plugin-cli = { path = "../../../plugins/cli", version = "2.4.1" }
|
||||||
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.3.0" }
|
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut", version = "2.3.1" }
|
||||||
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.9.0" }
|
tauri-plugin-updater = { path = "../../../plugins/updater", version = "2.10.0" }
|
||||||
tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.2.0" }
|
tauri-plugin-window-state = { path = "../../../plugins/window-state", version = "2.2.0" }
|
||||||
|
|
||||||
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
|
[target."cfg(any(target_os = \"android\", target_os = \"ios\"))".dependencies]
|
||||||
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.0" }
|
tauri-plugin-barcode-scanner = { path = "../../../plugins/barcode-scanner/", version = "2.4.4" }
|
||||||
tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.0" }
|
tauri-plugin-nfc = { path = "../../../plugins/nfc", version = "2.3.4" }
|
||||||
tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.3.0" }
|
tauri-plugin-biometric = { path = "../../../plugins/biometric/", version = "2.3.2" }
|
||||||
tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.0" }
|
tauri-plugin-geolocation = { path = "../../../plugins/geolocation/", version = "2.3.2" }
|
||||||
tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.3.0" }
|
tauri-plugin-haptics = { path = "../../../plugins/haptics/", version = "2.3.2" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
prod = ["tauri/custom-protocol"]
|
prod = ["tauri/custom-protocol"]
|
||||||
|
|||||||
@@ -68,6 +68,9 @@
|
|||||||
"fs:allow-rename",
|
"fs:allow-rename",
|
||||||
"fs:allow-mkdir",
|
"fs:allow-mkdir",
|
||||||
"fs:allow-remove",
|
"fs:allow-remove",
|
||||||
|
"fs:allow-stat",
|
||||||
|
"fs:allow-fstat",
|
||||||
|
"fs:allow-lstat",
|
||||||
"fs:allow-write-text-file",
|
"fs:allow-write-text-file",
|
||||||
"fs:read-meta",
|
"fs:read-meta",
|
||||||
"fs:scope-download-recursive",
|
"fs:scope-download-recursive",
|
||||||
@@ -75,6 +78,9 @@
|
|||||||
{
|
{
|
||||||
"identifier": "fs:scope-appdata-recursive",
|
"identifier": "fs:scope-appdata-recursive",
|
||||||
"allow": [
|
"allow": [
|
||||||
|
{
|
||||||
|
"path": "$APPDATA/db/"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "$APPDATA/db/**"
|
"path": "$APPDATA/db/**"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ val tauriProperties = Properties().apply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk = 34
|
compileSdk = 36
|
||||||
namespace = "com.tauri.api"
|
namespace = "com.tauri.api"
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||||
applicationId = "com.tauri.api"
|
applicationId = "com.tauri.api"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 34
|
targetSdk = 36
|
||||||
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
|
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
|
||||||
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
|
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:8.5.1")
|
classpath("com.android.tools.build:gradle:8.11.0")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(gradleApi())
|
compileOnly(gradleApi())
|
||||||
implementation("com.android.tools.build:gradle:8.5.1")
|
implementation("com.android.tools.build:gradle:8.11.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
let filter = null;
|
let filter = null;
|
||||||
let multiple = false;
|
let multiple = false;
|
||||||
let directory = false;
|
let directory = false;
|
||||||
|
let pickerMode = "document";
|
||||||
|
let fileAccessMode = "scoped";
|
||||||
|
|
||||||
function arrayBufferToBase64(buffer, callback) {
|
function arrayBufferToBase64(buffer, callback) {
|
||||||
var blob = new Blob([buffer], {
|
var blob = new Blob([buffer], {
|
||||||
@@ -44,55 +46,69 @@
|
|||||||
await message("Tauri is awesome!");
|
await message("Tauri is awesome!");
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDialog() {
|
async function msgCustom(result) {
|
||||||
open({
|
const buttons = { yes: "awesome", no: "amazing", cancel: "stunning" };
|
||||||
title: "My wonderful open dialog",
|
await message(`Tauri is: `, { buttons })
|
||||||
defaultPath,
|
.then((res) => onMessage(`Tauri is ${res}`))
|
||||||
filters: filter
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: "Tauri Example",
|
|
||||||
extensions: filter.split(",").map((f) => f.trim()),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
multiple,
|
|
||||||
directory,
|
|
||||||
})
|
|
||||||
.then(function (res) {
|
|
||||||
if (Array.isArray(res)) {
|
|
||||||
onMessage(res);
|
|
||||||
} else {
|
|
||||||
var pathToRead = res;
|
|
||||||
var isFile = pathToRead.match(/\S+\.\S+$/g);
|
|
||||||
readFile(pathToRead)
|
|
||||||
.then(function (response) {
|
|
||||||
if (isFile) {
|
|
||||||
if (
|
|
||||||
pathToRead.includes(".png") ||
|
|
||||||
pathToRead.includes(".jpg") ||
|
|
||||||
pathToRead.includes(".jpeg")
|
|
||||||
) {
|
|
||||||
arrayBufferToBase64(
|
|
||||||
new Uint8Array(response),
|
|
||||||
function (base64) {
|
|
||||||
var src = "data:image/png;base64," + base64;
|
|
||||||
insecureRenderHtml('<img src="' + src + '"></img>');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
onMessage(res);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onMessage(res);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(onMessage(res));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(onMessage);
|
.catch(onMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openDialog() {
|
||||||
|
try {
|
||||||
|
var result = await open({
|
||||||
|
title: "My wonderful open dialog",
|
||||||
|
defaultPath,
|
||||||
|
filters: filter
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
name: "Tauri Example",
|
||||||
|
extensions: filter.split(",").map((f) => f.trim()),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
multiple,
|
||||||
|
directory,
|
||||||
|
pickerMode,
|
||||||
|
fileAccessMode,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
onMessage(result);
|
||||||
|
} else {
|
||||||
|
var pathToRead = result;
|
||||||
|
var isFile = pathToRead.match(/\S+\.\S+$/g);
|
||||||
|
|
||||||
|
await readFile(pathToRead)
|
||||||
|
.then(function (res) {
|
||||||
|
if (isFile) {
|
||||||
|
if (
|
||||||
|
pathToRead.includes(".png") ||
|
||||||
|
pathToRead.includes(".jpg") ||
|
||||||
|
pathToRead.includes(".jpeg")
|
||||||
|
) {
|
||||||
|
arrayBufferToBase64(
|
||||||
|
new Uint8Array(res),
|
||||||
|
function (base64) {
|
||||||
|
var src = "data:image/png;base64," + base64;
|
||||||
|
insecureRenderHtml('<img src="' + src + '"></img>');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Convert byte array to UTF-8 string
|
||||||
|
const decoder = new TextDecoder('utf-8');
|
||||||
|
const text = decoder.decode(new Uint8Array(res));
|
||||||
|
onMessage(text);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onMessage(res);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch(exception) {
|
||||||
|
onMessage(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function saveDialog() {
|
function saveDialog() {
|
||||||
save({
|
save({
|
||||||
title: "My wonderful save dialog",
|
title: "My wonderful save dialog",
|
||||||
@@ -105,7 +121,7 @@
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [],
|
: [],
|
||||||
})
|
})
|
||||||
.then(onMessage)
|
.then(onMessage)
|
||||||
.catch(onMessage);
|
.catch(onMessage);
|
||||||
}
|
}
|
||||||
@@ -135,13 +151,35 @@
|
|||||||
<input type="checkbox" id="dialog-directory" bind:checked={directory} />
|
<input type="checkbox" id="dialog-directory" bind:checked={directory} />
|
||||||
<label for="dialog-directory">Directory</label>
|
<label for="dialog-directory">Directory</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="dialog-picker-mode">Picker Mode:</label>
|
||||||
|
<select id="dialog-picker-mode" bind:value={pickerMode}>
|
||||||
|
<option value="">None</option>
|
||||||
|
<option value="media">Media</option>
|
||||||
|
<option value="image">Image</option>
|
||||||
|
<option value="video">Video</option>
|
||||||
|
<option value="document">Document</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="dialog-file-access-mode">File Access Mode:</label>
|
||||||
|
<select id="dialog-file-access-mode" bind:value={fileAccessMode}>
|
||||||
|
<option value="copy">Copy</option>
|
||||||
|
<option value="scoped">Scoped</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button class="btn" id="open-dialog" on:click={openDialog}>Open dialog</button>
|
|
||||||
<button class="btn" id="save-dialog" on:click={saveDialog}
|
<div class="flex flex-wrap flex-col md:flex-row gap-2 children:flex-shrink-0">
|
||||||
>Open save dialog</button
|
<button class="btn" id="open-dialog" on:click={openDialog}>Open dialog</button>
|
||||||
>
|
<button class="btn" id="save-dialog" on:click={saveDialog}
|
||||||
<button class="btn" id="prompt-dialog" on:click={prompt}>Prompt</button>
|
>Open save dialog</button
|
||||||
<button class="btn" id="custom-prompt-dialog" on:click={promptCustom}
|
>
|
||||||
>Prompt (custom)</button
|
<button class="btn" id="prompt-dialog" on:click={prompt}>Prompt</button>
|
||||||
>
|
<button class="btn" id="custom-prompt-dialog" on:click={promptCustom}
|
||||||
<button class="btn" id="message-dialog" on:click={msg}>Message</button>
|
>Prompt (custom)</button
|
||||||
|
>
|
||||||
|
<button class="btn" id="message-dialog" on:click={msg}>Message</button>
|
||||||
|
<button class="btn" id="message-dialog" on:click={msgCustom}>Message (custom)</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,194 +1,214 @@
|
|||||||
<script>
|
<script>
|
||||||
import * as fs from "@tauri-apps/plugin-fs";
|
import * as fs from '@tauri-apps/plugin-fs'
|
||||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
import * as os from '@tauri-apps/plugin-os'
|
||||||
import { arrayBufferToBase64 } from "../lib/utils";
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import { onDestroy } from "svelte";
|
import { arrayBufferToBase64 } from '../lib/utils'
|
||||||
|
import { onDestroy, onMount } from 'svelte'
|
||||||
|
|
||||||
export let onMessage;
|
const { onMessage, insecureRenderHtml } = $props()
|
||||||
export let insecureRenderHtml;
|
|
||||||
|
|
||||||
let path = "";
|
let path = $state('')
|
||||||
let img;
|
let img
|
||||||
/** @type {fs.FileHandle} */
|
/** @type {fs.FileHandle} */
|
||||||
let file;
|
let file = $state()
|
||||||
let renameTo;
|
let renameTo = $state()
|
||||||
let watchPath = "";
|
let watchPath = $state('')
|
||||||
let watchDebounceDelay = "0";
|
let watchDebounceDelay = $state(0)
|
||||||
let watchRecursive = false;
|
let watchRecursive = $state(false)
|
||||||
let unwatchFn;
|
/** @type {fs.BaseDirectory | undefined} */
|
||||||
let unwatchPath = "";
|
let baseDir = $state()
|
||||||
|
let unwatchFn
|
||||||
|
let unwatchPath = ''
|
||||||
|
let isMobile = $state(false)
|
||||||
|
|
||||||
function getDir() {
|
onMount(() => {
|
||||||
const dirSelect = document.getElementById("dir");
|
let platform = os.platform()
|
||||||
return dirSelect.value ? parseInt(dir.value) : null;
|
isMobile = platform === 'android' || platform === 'ios'
|
||||||
}
|
})
|
||||||
|
|
||||||
const DirOptions = Object.keys(fs.BaseDirectory)
|
const dirOptions = Object.keys(fs.BaseDirectory).filter((key) =>
|
||||||
.filter((key) => isNaN(parseInt(key)))
|
isNaN(parseInt(key))
|
||||||
.map((dir) => [dir, fs.BaseDirectory[dir]]);
|
)
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
fs.open(path, {
|
fs.open(path, {
|
||||||
baseDir: getDir(),
|
baseDir,
|
||||||
read: true,
|
read: true,
|
||||||
write: true,
|
write: true,
|
||||||
create: true,
|
create: true
|
||||||
})
|
})
|
||||||
.then((f) => {
|
.then((f) => {
|
||||||
file = f;
|
file = f
|
||||||
onMessage(`Opened ${path}`);
|
onMessage(`Opened ${path}`)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function mkdir() {
|
function mkdir() {
|
||||||
fs.mkdir(path, { baseDir: getDir() })
|
fs.mkdir(path, { baseDir, recursive: true })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onMessage(`Created dir ${path}`);
|
onMessage(`Created dir ${path}`)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove() {
|
function remove() {
|
||||||
fs.remove(path, { baseDir: getDir() })
|
fs.remove(path, { baseDir })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onMessage(`Removed ${path}`);
|
onMessage(`Removed ${path}`)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function rename() {
|
function rename() {
|
||||||
fs.rename(path, renameTo, {
|
fs.rename(path, renameTo, {
|
||||||
oldPathBaseDir: getDir(),
|
oldPathBaseDir,
|
||||||
newPathBaseDir: getDir(),
|
newPathBaseDir
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onMessage(`Renamed ${path} to ${renameTo}`);
|
onMessage(`Renamed ${path} to ${renameTo}`)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function truncate() {
|
function truncate() {
|
||||||
file
|
file
|
||||||
.truncate(0)
|
.truncate(0)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
onMessage(`Truncated file`);
|
onMessage(`Truncated file`)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
function write() {
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
file
|
||||||
|
.write(encoder.encode('Hello from Tauri :)'))
|
||||||
|
.then(() => {
|
||||||
|
onMessage(`wrote to file`)
|
||||||
|
})
|
||||||
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stat() {
|
function stat() {
|
||||||
file
|
file
|
||||||
.stat()
|
.stat()
|
||||||
.then((stat) => {
|
.then((stat) => {
|
||||||
onMessage(`File stat ${JSON.stringify(stat)}`);
|
onMessage(`File stat ${JSON.stringify(stat)}`)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function read() {
|
function read() {
|
||||||
const opts = {
|
const opts = {
|
||||||
baseDir: getDir(),
|
baseDir
|
||||||
};
|
}
|
||||||
fs.stat(path, opts)
|
fs.stat(path, opts)
|
||||||
.then((stat) => {
|
.then((stat) => {
|
||||||
const isFile = stat.isFile;
|
const isFile = stat.isFile
|
||||||
|
|
||||||
const promise = isFile
|
const promise = isFile
|
||||||
? fs.readFile(path, opts)
|
? fs.readFile(path, opts)
|
||||||
: fs.readDir(path, opts);
|
: fs.readDir(path, opts)
|
||||||
promise
|
promise
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
if (isFile) {
|
if (isFile) {
|
||||||
if (path.includes(".png") || path.includes(".jpg")) {
|
if (path.includes('.png') || path.includes('.jpg')) {
|
||||||
arrayBufferToBase64(
|
arrayBufferToBase64(
|
||||||
new Uint8Array(response),
|
new Uint8Array(response),
|
||||||
function (base64) {
|
function (base64) {
|
||||||
const src = "data:image/png;base64," + base64;
|
const src = 'data:image/png;base64,' + base64
|
||||||
insecureRenderHtml('<img src="' + src + '"></img>');
|
insecureRenderHtml('<img src="' + src + '"></img>')
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
const value = String.fromCharCode.apply(null, response);
|
const value = String.fromCharCode.apply(null, response)
|
||||||
insecureRenderHtml(
|
insecureRenderHtml(
|
||||||
'<textarea id="file-response"></textarea><button id="file-save">Save</button>'
|
'<textarea id="file-response"></textarea><button id="file-save">Save</button>'
|
||||||
);
|
)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const fileInput = document.getElementById("file-response");
|
const fileInput = document.getElementById('file-response')
|
||||||
fileInput.value = value;
|
fileInput.value = value
|
||||||
document
|
document
|
||||||
.getElementById("file-save")
|
.getElementById('file-save')
|
||||||
.addEventListener("click", function () {
|
.addEventListener('click', function () {
|
||||||
fs.writeTextFile(path, fileInput.value, {
|
fs.writeTextFile(path, fileInput.value, {
|
||||||
baseDir: getDir(),
|
baseDir
|
||||||
}).catch(onMessage);
|
}).catch(onMessage)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onMessage(response);
|
onMessage(response)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSrc() {
|
function setSrc() {
|
||||||
img.src = convertFileSrc(path);
|
img.src = convertFileSrc(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
function watch() {
|
function watch() {
|
||||||
unwatch();
|
unwatch()
|
||||||
if (watchPath) {
|
if (watchPath) {
|
||||||
onMessage(`Watching ${watchPath} for changes`);
|
onMessage(`Watching ${watchPath} for changes`)
|
||||||
let options = {
|
let options = {
|
||||||
recursive: watchRecursive,
|
recursive: watchRecursive,
|
||||||
delayMs: parseInt(watchDebounceDelay),
|
delayMs: watchDebounceDelay
|
||||||
};
|
}
|
||||||
if (options.delayMs === 0) {
|
if (options.delayMs === 0) {
|
||||||
fs.watchImmediate(watchPath, onMessage, options)
|
fs.watchImmediate(watchPath, onMessage, options)
|
||||||
.then((fn) => {
|
.then((fn) => {
|
||||||
unwatchFn = fn;
|
unwatchFn = fn
|
||||||
unwatchPath = watchPath;
|
unwatchPath = watchPath
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
} else {
|
} else {
|
||||||
fs.watch(watchPath, onMessage, options)
|
fs.watch(watchPath, onMessage, options)
|
||||||
.then((fn) => {
|
.then((fn) => {
|
||||||
unwatchFn = fn;
|
unwatchFn = fn
|
||||||
unwatchPath = watchPath;
|
unwatchPath = watchPath
|
||||||
})
|
})
|
||||||
.catch(onMessage);
|
.catch(onMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unwatch() {
|
function unwatch() {
|
||||||
if (unwatchFn) {
|
if (unwatchFn) {
|
||||||
onMessage(`Stopped watching ${unwatchPath} for changes`);
|
onMessage(`Stopped watching ${unwatchPath} for changes`)
|
||||||
unwatchFn();
|
unwatchFn()
|
||||||
}
|
}
|
||||||
unwatchFn = undefined;
|
unwatchFn = undefined
|
||||||
unwatchPath = undefined;
|
unwatchPath = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (file) {
|
if (file) {
|
||||||
file.close();
|
file.close()
|
||||||
}
|
}
|
||||||
if (unwatchFn) {
|
if (unwatchFn) {
|
||||||
unwatchFn();
|
unwatchFn()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
{#if isMobile}
|
||||||
|
<div>
|
||||||
|
On mobile, paths outside of App* paths require the use of dialogs
|
||||||
|
regardless of Tauri's scope mechanism.
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
{/if}
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<select class="input" id="dir">
|
<select class="input" bind:value={baseDir}>
|
||||||
<option value="">None</option>
|
<option value={undefined} selected>None</option>
|
||||||
{#each DirOptions as dir}
|
{#each dirOptions as dir}
|
||||||
<option value={dir[1]}>{dir[0]}</option>
|
<option value={fs.BaseDirectory[dir]}>{dir}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<input
|
<input
|
||||||
@@ -199,20 +219,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
<button class="btn" on:click={open}>Open</button>
|
<button class="btn" onclick={open}>Open</button>
|
||||||
<button class="btn" on:click={read}>Read</button>
|
<button class="btn" onclick={read}>Read</button>
|
||||||
<button class="btn" on:click={mkdir}>Mkdir</button>
|
<button class="btn" onclick={mkdir}>Mkdir</button>
|
||||||
<button class="btn" on:click={remove}>Remove</button>
|
<button class="btn" onclick={remove}>Remove</button>
|
||||||
<div class="flex flex-row">
|
<div class="flex flex-row">
|
||||||
<button class="btn" on:click={rename}>Rename</button>
|
<button class="btn" onclick={rename}>Rename</button>
|
||||||
<input class="input" bind:value={renameTo} placeholder="To" />
|
<input class="input" bind:value={renameTo} placeholder="To" />
|
||||||
</div>
|
</div>
|
||||||
<button class="btn" type="button" on:click={setSrc}>Use as img src</button>
|
<button class="btn" type="button" onclick={setSrc}>Use as img src</button>
|
||||||
</div>
|
</div>
|
||||||
{#if file}
|
{#if file}
|
||||||
<div>
|
<div>
|
||||||
<button class="btn" on:click={truncate}>Truncate</button>
|
<button class="btn" onclick={write}>Write</button>
|
||||||
<button class="btn" on:click={stat}>Stat</button>
|
<button class="btn" onclick={truncate}>Truncate</button>
|
||||||
|
<button class="btn" onclick={stat}>Stat</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -241,8 +262,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
<button class="btn" on:click={watch}>Watch</button>
|
<button class="btn" onclick={watch}>Watch</button>
|
||||||
<button class="btn" on:click={unwatch}>Unwatch</button>
|
<button class="btn" onclick={unwatch}>Unwatch</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { sendNotification } from '@tauri-apps/plugin-notification'
|
||||||
export let onMessage
|
export let onMessage
|
||||||
|
|
||||||
|
let sound = ''
|
||||||
|
|
||||||
// send the notification directly
|
// send the notification directly
|
||||||
// the backend is responsible for checking the permission
|
// the backend is responsible for checking the permission
|
||||||
function _sendNotification() {
|
function _sendNotification() {
|
||||||
new Notification('Notification title', {
|
sendNotification({
|
||||||
body: 'This is the notification body'
|
title: 'Notification title',
|
||||||
|
body: 'This is the notification body',
|
||||||
|
sound: sound || null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// alternatively, check the permission ourselves
|
// alternatively, check the permission ourselves
|
||||||
function sendNotification() {
|
function triggerNotification() {
|
||||||
if (Notification.permission === 'default') {
|
if (Notification.permission === 'default') {
|
||||||
Notification.requestPermission()
|
Notification.requestPermission()
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
@@ -29,6 +34,11 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button class="btn" id="notification" on:click={sendNotification}>
|
<input
|
||||||
|
class="input grow"
|
||||||
|
placeholder="Notification sound..."
|
||||||
|
bind:value={sound}
|
||||||
|
/>
|
||||||
|
<button class="btn" id="notification" on:click={triggerNotification}>
|
||||||
Send test notification
|
Send test notification
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,71 +1,85 @@
|
|||||||
<script>
|
<script>
|
||||||
import { LazyStore } from "@tauri-apps/plugin-store";
|
import { appDataDir, resolve } from '@tauri-apps/api/path'
|
||||||
import { onMount } from "svelte";
|
import { LazyStore } from '@tauri-apps/plugin-store'
|
||||||
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
export let onMessage;
|
let { onMessage } = $props()
|
||||||
|
|
||||||
let key;
|
let key = $state()
|
||||||
let value;
|
let value = $state()
|
||||||
|
|
||||||
let store = new LazyStore("cache.json");
|
const storeName = 'cache.json'
|
||||||
let cache = {};
|
|
||||||
|
let store = new LazyStore(storeName)
|
||||||
|
let path = $state('')
|
||||||
|
let cache = $state({})
|
||||||
|
|
||||||
async function refreshEntries() {
|
async function refreshEntries() {
|
||||||
try {
|
try {
|
||||||
const values = await store.entries();
|
const values = await store.entries()
|
||||||
cache = {};
|
cache = {}
|
||||||
for (const [key, value] of values) {
|
for (const [key, value] of values) {
|
||||||
cache[key] = value;
|
cache[key] = value
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
onMessage(error);
|
onMessage(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await refreshEntries();
|
path = await resolve(await appDataDir(), storeName)
|
||||||
});
|
await refreshEntries()
|
||||||
|
})
|
||||||
|
|
||||||
async function write(key, value) {
|
async function write(key, value) {
|
||||||
try {
|
try {
|
||||||
if (value) {
|
if (value) {
|
||||||
await store.set(key, value);
|
await store.set(key, value)
|
||||||
} else {
|
} else {
|
||||||
await store.delete(key);
|
await store.delete(key)
|
||||||
}
|
}
|
||||||
const v = await store.get(key);
|
const v = await store.get(key)
|
||||||
if (v === undefined) {
|
if (v === undefined) {
|
||||||
delete cache[key];
|
delete cache[key]
|
||||||
cache = cache;
|
cache = cache
|
||||||
} else {
|
} else {
|
||||||
cache[key] = v;
|
cache[key] = v
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
onMessage(error);
|
onMessage(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reset() {
|
async function reset() {
|
||||||
try {
|
try {
|
||||||
await store.reset();
|
await store.reset()
|
||||||
} catch (error) {
|
} 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() {
|
async function close() {
|
||||||
try {
|
try {
|
||||||
await store.close();
|
await store.close()
|
||||||
onMessage("Store is now closed, any new operations will error out");
|
onMessage('Store is now closed, any new operations will error out')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
onMessage(error);
|
onMessage(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function reopen() {
|
function reopen() {
|
||||||
store = new LazyStore("cache.json");
|
store = new LazyStore(storeName)
|
||||||
onMessage("We made a new `LazyStore` instance, operations will now work");
|
onMessage('We made a new `LazyStore` instance, operations will now work')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -82,14 +96,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button class="btn" on:click={() => write(key, value)}>Write</button>
|
<button class="btn" onclick={() => write(key, value)}>Write</button>
|
||||||
<button class="btn" on:click={() => reset()}>Reset</button>
|
<button class="btn" onclick={() => reset()}>Reset</button>
|
||||||
<button class="btn" on:click={() => close()}>Close</button>
|
<button class="btn" onclick={() => reload()}>Reload</button>
|
||||||
<button class="btn" on:click={() => reopen()}>Re-open</button>
|
<button class="btn" onclick={() => close()}>Close</button>
|
||||||
|
<button class="btn" onclick={() => reopen()}>Re-open</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div>Store at <code>{path}</code> on disk</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
<h2>Store Values</h2>
|
||||||
{#each Object.entries(cache) as [k, v]}
|
{#each Object.entries(cache) as [k, v]}
|
||||||
<div>{k} = {v}</div>
|
<div>{k} = {v}</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { download, upload } from '@tauri-apps/plugin-upload'
|
import { download, upload, HttpMethod } from '@tauri-apps/plugin-upload'
|
||||||
import { open } from '@tauri-apps/plugin-dialog'
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
import { JsonView } from '@zerodevx/svelte-json-view'
|
import { JsonView } from '@zerodevx/svelte-json-view'
|
||||||
import { appDataDir } from '@tauri-apps/api/path'
|
import { appDataDir } from '@tauri-apps/api/path'
|
||||||
@@ -16,6 +16,22 @@
|
|||||||
|
|
||||||
let uploadUrl = 'https://httpbin.org/post'
|
let uploadUrl = 'https://httpbin.org/post'
|
||||||
let uploadFilePath = ''
|
let uploadFilePath = ''
|
||||||
|
let uploadMethod = HttpMethod.Post
|
||||||
|
|
||||||
|
// Update URL when method changes
|
||||||
|
$: {
|
||||||
|
switch (uploadMethod) {
|
||||||
|
case HttpMethod.Post:
|
||||||
|
uploadUrl = 'https://httpbin.org/post'
|
||||||
|
break
|
||||||
|
case HttpMethod.Put:
|
||||||
|
uploadUrl = 'https://httpbin.org/put'
|
||||||
|
break
|
||||||
|
case HttpMethod.Patch:
|
||||||
|
uploadUrl = 'https://httpbin.org/patch'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
let uploadProgress = null
|
let uploadProgress = null
|
||||||
let uploadResult = null
|
let uploadResult = null
|
||||||
let isUploading = false
|
let isUploading = false
|
||||||
@@ -197,7 +213,8 @@
|
|||||||
},
|
},
|
||||||
new Map([
|
new Map([
|
||||||
['User-Agent', 'Tauri Upload Plugin Demo']
|
['User-Agent', 'Tauri Upload Plugin Demo']
|
||||||
])
|
]),
|
||||||
|
uploadMethod
|
||||||
)
|
)
|
||||||
|
|
||||||
uploadResult = {
|
uploadResult = {
|
||||||
@@ -340,12 +357,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="upload-method" class="block text-sm font-medium text-gray-700 mb-1">HTTP Method:</label>
|
||||||
|
<select
|
||||||
|
id="upload-method"
|
||||||
|
bind:value={uploadMethod}
|
||||||
|
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}
|
||||||
|
>
|
||||||
|
<option value={HttpMethod.Post}>POST</option>
|
||||||
|
<option value={HttpMethod.Put}>PUT</option>
|
||||||
|
<option value={HttpMethod.Patch}>PATCH</option>
|
||||||
|
</select>
|
||||||
|
<p class="text-xs text-gray-500 mt-1">Choose the HTTP method for the upload request</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-blue-50 border border-blue-200 p-3 rounded-md">
|
||||||
|
<div class="text-sm text-blue-800">
|
||||||
|
<strong>Upload Configuration:</strong>
|
||||||
|
<div class="font-mono text-xs mt-1">
|
||||||
|
Method: {uploadMethod} | URL: {uploadUrl || 'Not set'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
on:click={startUpload}
|
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"
|
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}
|
disabled={isUploading || !uploadUrl || !uploadFilePath}
|
||||||
>
|
>
|
||||||
{isUploading ? 'Uploading...' : 'Upload File'}
|
{isUploading ? `Uploading (${uploadMethod})...` : `Upload File (${uploadMethod})`}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{#if uploadProgress}
|
{#if uploadProgress}
|
||||||
|
|||||||
+12
-10
@@ -11,29 +11,31 @@
|
|||||||
"example:api:dev": "pnpm run --filter \"api\" tauri dev"
|
"example:api:dev": "pnpm run --filter \"api\" tauri dev"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "9.31.0",
|
"@eslint/js": "9.39.2",
|
||||||
"@rollup/plugin-node-resolve": "16.0.1",
|
"@rollup/plugin-node-resolve": "16.0.3",
|
||||||
"@rollup/plugin-terser": "0.4.4",
|
"@rollup/plugin-terser": "0.4.4",
|
||||||
"@rollup/plugin-typescript": "12.1.4",
|
"@rollup/plugin-typescript": "12.3.0",
|
||||||
"covector": "^0.12.4",
|
"covector": "^0.12.4",
|
||||||
"eslint": "9.31.0",
|
"eslint": "9.39.2",
|
||||||
"eslint-config-prettier": "10.1.8",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"eslint-plugin-security": "3.0.1",
|
"eslint-plugin-security": "3.0.1",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.8.1",
|
||||||
"rollup": "4.45.1",
|
"rollup": "4.57.1",
|
||||||
"tslib": "2.8.1",
|
"tslib": "2.8.1",
|
||||||
"typescript": "5.8.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "8.37.0"
|
"typescript-eslint": "8.54.0"
|
||||||
},
|
},
|
||||||
|
"minimumReleaseAge": 4320,
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"esbuild@<0.25.0": ">=0.25.0"
|
"esbuild@<0.25.0": ">=0.25.0",
|
||||||
|
"lodash@>=4.0.0 <=4.17.22": ">=4.17.23"
|
||||||
},
|
},
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"esbuild"
|
"esbuild"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"pnpm": "^10.0.0"
|
"pnpm": "^10.16.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.5.1]
|
||||||
|
|
||||||
|
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
|
||||||
|
|
||||||
## \[2.5.0]
|
## \[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
|
- [`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]
|
[package]
|
||||||
name = "tauri-plugin-autostart"
|
name = "tauri-plugin-autostart"
|
||||||
version = "2.5.0"
|
version = "2.5.1"
|
||||||
description = "Automatically launch your application at startup."
|
description = "Automatically launch your application at startup."
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
@@ -9,10 +9,6 @@ rust-version = { workspace = true }
|
|||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
links = "tauri-plugin-autostart"
|
links = "tauri-plugin-autostart"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
rustc-args = ["--cfg", "docsrs"]
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[package.metadata.platforms.support]
|
[package.metadata.platforms.support]
|
||||||
windows = { level = "full", notes = "" }
|
windows = { level = "full", notes = "" }
|
||||||
linux = { level = "full", notes = "" }
|
linux = { level = "full", notes = "" }
|
||||||
|
|||||||
@@ -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:
|
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
|
```sh
|
||||||
pnpm add @tauri-apps/plugin-autostart
|
pnpm add @tauri-apps/plugin-autostart
|
||||||
# or
|
# or
|
||||||
npm add @tauri-apps/plugin-autostart
|
npm add @tauri-apps/plugin-autostart
|
||||||
# or
|
# or
|
||||||
yarn add @tauri-apps/plugin-autostart
|
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
|
## Usage
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tauri-apps/plugin-autostart",
|
"name": "@tauri-apps/plugin-autostart",
|
||||||
"version": "2.5.0",
|
"version": "2.5.1",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Tauri Programme within The Commons Conservancy"
|
"Tauri Programme within The Commons Conservancy"
|
||||||
@@ -24,6 +24,6 @@
|
|||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.6.0"
|
"@tauri-apps/api": "^2.10.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,25 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.4.4]
|
||||||
|
|
||||||
|
- [`82fbb0c7`](https://github.com/tauri-apps/plugins-workspace/commit/82fbb0c790288eca72af9ade13828ded7700ff90) ([#3221](https://github.com/tauri-apps/plugins-workspace/pull/3221)) On iOS, fixed an application crash happening when the scanner was started when user denied permission before.
|
||||||
|
- [`b60dd887`](https://github.com/tauri-apps/plugins-workspace/commit/b60dd88702a2be2af942a3d104d728970a4c42d6) ([#3223](https://github.com/tauri-apps/plugins-workspace/pull/3223)) On iOS, start the scanning session on a separate thread to fix performance issues.
|
||||||
|
- [`c27af912`](https://github.com/tauri-apps/plugins-workspace/commit/c27af9128d6cc7a2424ec49e98005e68b2d0eb1a) ([#3222](https://github.com/tauri-apps/plugins-workspace/pull/3222)) On iOS, fixed an application crash happening when the scanner was started on the iOS Simulator (no camera available).
|
||||||
|
|
||||||
|
## \[2.4.3]
|
||||||
|
|
||||||
|
- [`d8bfe61f`](https://github.com/tauri-apps/plugins-workspace/commit/d8bfe61f20f235314bad93a9c50d8b7f3eade734) ([#3121](https://github.com/tauri-apps/plugins-workspace/pull/3121) by [@NKIPSC](https://github.com/tauri-apps/plugins-workspace/../../NKIPSC)) Remove unnecessary checks on Android when requesting camera permission.
|
||||||
|
- [`631d0e25`](https://github.com/tauri-apps/plugins-workspace/commit/631d0e256a37946b6a9102ca35511abfbebb92c5) ([#2440](https://github.com/tauri-apps/plugins-workspace/pull/2440) by [@kingsword09](https://github.com/tauri-apps/plugins-workspace/../../kingsword09)) Fix the `cameraView` is not removed after scanning in iOS.
|
||||||
|
|
||||||
|
## \[2.4.2]
|
||||||
|
|
||||||
|
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
|
||||||
|
|
||||||
|
## \[2.4.1]
|
||||||
|
|
||||||
|
- [`654bf489`](https://github.com/tauri-apps/plugins-workspace/commit/654bf4891a35769f7e82971641d3ad99974b2dfe) ([#3038](https://github.com/tauri-apps/plugins-workspace/pull/3038) by [@daniel-mader](https://github.com/tauri-apps/plugins-workspace/../../daniel-mader)) Update `androidx.camera` from `1.1.0` to `1.5.1` to support 16 KB memory page sizes.
|
||||||
|
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
|
||||||
|
|
||||||
## \[2.4.0]
|
## \[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+
|
- [`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+
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-barcode-scanner"
|
name = "tauri-plugin-barcode-scanner"
|
||||||
version = "2.4.0"
|
version = "2.4.4"
|
||||||
description = "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS"
|
description = "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS"
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
@@ -10,8 +10,6 @@ repository = { workspace = true }
|
|||||||
links = "tauri-plugin-barcode-scanner"
|
links = "tauri-plugin-barcode-scanner"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustc-args = ["--cfg", "docsrs"]
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
targets = ["x86_64-linux-android"]
|
targets = ["x86_64-linux-android"]
|
||||||
|
|
||||||
[package.metadata.platforms.support]
|
[package.metadata.platforms.support]
|
||||||
|
|||||||
@@ -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:
|
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
|
```sh
|
||||||
pnpm add @tauri-apps/plugin-barcode-scanner
|
pnpm add @tauri-apps/plugin-barcode-scanner
|
||||||
# or
|
# or
|
||||||
npm add @tauri-apps/plugin-barcode-scanner
|
npm add @tauri-apps/plugin-barcode-scanner
|
||||||
# or
|
# or
|
||||||
yarn add @tauri-apps/plugin-barcode-scanner
|
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
|
## Usage
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.tauri.barcodescanner"
|
namespace = "app.tauri.barcodescanner"
|
||||||
compileSdk = 34
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
@@ -33,16 +33,14 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
val camerax_version = "1.5.1"
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.9.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.0")
|
implementation("androidx.appcompat:appcompat:1.6.0")
|
||||||
implementation("com.google.android.material:material:1.7.0")
|
implementation("com.google.android.material:material:1.7.0")
|
||||||
implementation("androidx.camera:camera-core:1.1.0")
|
implementation("androidx.camera:camera-core:${camerax_version}")
|
||||||
implementation("androidx.camera:camera-view:1.1.0")
|
implementation("androidx.camera:camera-camera2:${camerax_version}")
|
||||||
implementation("androidx.camera:camera-lifecycle:1.1.0")
|
implementation("androidx.camera:camera-lifecycle:${camerax_version}")
|
||||||
implementation("androidx.camera:camera-camera2:1.1.0")
|
implementation("androidx.camera:camera-view:${camerax_version}")
|
||||||
implementation("androidx.camera:camera-lifecycle:1.1.0")
|
|
||||||
implementation("androidx.camera:camera-view:1.1.0")
|
|
||||||
implementation("com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0")
|
implementation("com.google.android.gms:play-services-mlkit-barcode-scanning:18.1.0")
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ import java.util.concurrent.ExecutionException
|
|||||||
|
|
||||||
private const val PERMISSION_ALIAS_CAMERA = "camera"
|
private const val PERMISSION_ALIAS_CAMERA = "camera"
|
||||||
private const val PERMISSION_NAME = Manifest.permission.CAMERA
|
private const val PERMISSION_NAME = Manifest.permission.CAMERA
|
||||||
private const val PREFS_PERMISSION_FIRST_TIME_ASKING = "PREFS_PERMISSION_FIRST_TIME_ASKING"
|
|
||||||
|
|
||||||
@InvokeArg
|
@InvokeArg
|
||||||
class ScanOptions {
|
class ScanOptions {
|
||||||
@@ -354,17 +353,6 @@ class BarcodeScannerPlugin(private val activity: Activity) : Plugin(activity),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun markFirstPermissionRequest() {
|
|
||||||
val sharedPreference: SharedPreferences =
|
|
||||||
activity.getSharedPreferences(PREFS_PERMISSION_FIRST_TIME_ASKING, MODE_PRIVATE)
|
|
||||||
sharedPreference.edit().putBoolean(PERMISSION_NAME, false).apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun firstPermissionRequest(): Boolean {
|
|
||||||
return activity.getSharedPreferences(PREFS_PERMISSION_FIRST_TIME_ASKING, MODE_PRIVATE)
|
|
||||||
.getBoolean(PERMISSION_NAME, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ObsoleteSdkInt")
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
@PermissionCallback
|
@PermissionCallback
|
||||||
fun cameraPermissionCallback(invoke: Invoke) {
|
fun cameraPermissionCallback(invoke: Invoke) {
|
||||||
@@ -380,9 +368,7 @@ class BarcodeScannerPlugin(private val activity: Activity) : Plugin(activity),
|
|||||||
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
|
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
|
||||||
} else {
|
} else {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
if (!activity.shouldShowRequestPermissionRationale(PERMISSION_NAME)) {
|
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED)
|
||||||
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
|
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
|
||||||
}
|
}
|
||||||
@@ -401,20 +387,12 @@ class BarcodeScannerPlugin(private val activity: Activity) : Plugin(activity),
|
|||||||
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
|
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
|
||||||
} else {
|
} else {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
if (firstPermissionRequest() || activity.shouldShowRequestPermissionRationale(
|
requestPermissionForAlias(
|
||||||
PERMISSION_NAME
|
PERMISSION_ALIAS_CAMERA,
|
||||||
)
|
invoke,
|
||||||
) {
|
"cameraPermissionCallback"
|
||||||
markFirstPermissionRequest()
|
)
|
||||||
requestPermissionForAlias(
|
return
|
||||||
PERMISSION_ALIAS_CAMERA,
|
|
||||||
invoke,
|
|
||||||
"cameraPermissionCallback"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.DENIED)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
|
requestPermissionResponse.put(PERMISSION_ALIAS_CAMERA, PermissionState.GRANTED)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate {
|
|||||||
if self.captureSession != nil {
|
if self.captureSession != nil {
|
||||||
self.captureSession!.stopRunning()
|
self.captureSession!.stopRunning()
|
||||||
self.cameraView.removePreviewLayer()
|
self.cameraView.removePreviewLayer()
|
||||||
|
self.cameraView.removeFromSuperview()
|
||||||
self.captureVideoPreviewLayer = nil
|
self.captureVideoPreviewLayer = nil
|
||||||
self.metaOutput = nil
|
self.metaOutput = nil
|
||||||
self.captureSession = nil
|
self.captureSession = nil
|
||||||
@@ -260,27 +261,34 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func runScanner(_ invoke: Invoke, args: ScanOptions) {
|
private func runScanner(_ invoke: Invoke, args: ScanOptions) {
|
||||||
|
if getPermissionState() != "granted" {
|
||||||
|
invoke.reject("Camera permission denied or not yet requested")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
scanFormats = [AVMetadataObject.ObjectType]()
|
scanFormats = [AVMetadataObject.ObjectType]()
|
||||||
|
|
||||||
(args.formats ?? []).forEach { format in
|
(args.formats ?? []).forEach { format in
|
||||||
if let formatValue = format.value {
|
if let formatValue = format.value {
|
||||||
scanFormats.append(formatValue)
|
scanFormats.append(formatValue)
|
||||||
} else {
|
} else {
|
||||||
invoke.reject("Unsupported barcode format on this iOS version: \(format)")
|
invoke.reject("Unsupported barcode format on this iOS version: \(format)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if scanFormats.isEmpty {
|
if scanFormats.isEmpty {
|
||||||
for supportedFormat in SupportedFormat.allCases {
|
for supportedFormat in SupportedFormat.allCases {
|
||||||
if let formatValue = supportedFormat.value {
|
if let formatValue = supportedFormat.value {
|
||||||
scanFormats.append(formatValue)
|
scanFormats.append(formatValue)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.metaOutput!.metadataObjectTypes = self.scanFormats
|
self.metaOutput!.metadataObjectTypes = self.scanFormats
|
||||||
self.captureSession!.startRunning()
|
DispatchQueue.main.async {
|
||||||
|
self.captureSession!.startRunning()
|
||||||
|
}
|
||||||
|
|
||||||
self.isScanning = true
|
self.isScanning = true
|
||||||
}
|
}
|
||||||
@@ -297,6 +305,13 @@ class BarcodeScannerPlugin: Plugin, AVCaptureMetadataOutputObjectsDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if camera is available on this platform (iOS simulator doesn't have cameras)
|
||||||
|
let availableVideoDevices = discoverCaptureDevices()
|
||||||
|
if availableVideoDevices.isEmpty {
|
||||||
|
invoke.reject("No camera available on this device (e.g., iOS Simulator)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var iOS14min: Bool = false
|
var iOS14min: Bool = false
|
||||||
if #available(iOS 14.0, *) { iOS14min = true }
|
if #available(iOS 14.0, *) { iOS14min = true }
|
||||||
if !iOS14min && self.getPermissionState() != "granted" {
|
if !iOS14min && self.getPermissionState() != "granted" {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tauri-apps/plugin-barcode-scanner",
|
"name": "@tauri-apps/plugin-barcode-scanner",
|
||||||
"version": "2.4.0",
|
"version": "2.4.4",
|
||||||
"description": "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS",
|
"description": "Scan QR codes, EAN-13 and other kinds of barcodes on Android and iOS",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
@@ -25,6 +25,6 @@
|
|||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.6.0"
|
"@tauri-apps/api": "^2.10.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.3.2]
|
||||||
|
|
||||||
|
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
|
||||||
|
|
||||||
|
## \[2.3.1]
|
||||||
|
|
||||||
|
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
|
||||||
|
|
||||||
## \[2.3.0]
|
## \[2.3.0]
|
||||||
|
|
||||||
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
|
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-biometric"
|
name = "tauri-plugin-biometric"
|
||||||
version = "2.3.0"
|
version = "2.3.2"
|
||||||
description = "Prompt the user for biometric authentication on Android and iOS."
|
description = "Prompt the user for biometric authentication on Android and iOS."
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
@@ -9,8 +9,6 @@ repository = { workspace = true }
|
|||||||
links = "tauri-plugin-biometric"
|
links = "tauri-plugin-biometric"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustc-args = ["--cfg", "docsrs"]
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
targets = ["x86_64-linux-android"]
|
targets = ["x86_64-linux-android"]
|
||||||
|
|
||||||
[package.metadata.platforms.support]
|
[package.metadata.platforms.support]
|
||||||
|
|||||||
@@ -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:
|
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! -->
|
<!-- Add the branch for installations using git! -->
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -43,13 +41,6 @@ pnpm add @tauri-apps/plugin-biometric
|
|||||||
npm add @tauri-apps/plugin-biometric
|
npm add @tauri-apps/plugin-biometric
|
||||||
# or
|
# or
|
||||||
yarn add @tauri-apps/plugin-biometric
|
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
|
## Usage
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.tauri.biometric"
|
namespace = "app.tauri.biometric"
|
||||||
compileSdk = 34
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tauri-apps/plugin-biometric",
|
"name": "@tauri-apps/plugin-biometric",
|
||||||
"version": "2.3.0",
|
"version": "2.3.2",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Tauri Programme within The Commons Conservancy"
|
"Tauri Programme within The Commons Conservancy"
|
||||||
@@ -25,6 +25,6 @@
|
|||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.6.0"
|
"@tauri-apps/api": "^2.10.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.4.1]
|
||||||
|
|
||||||
|
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
|
||||||
|
|
||||||
## \[2.4.0]
|
## \[2.4.0]
|
||||||
|
|
||||||
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
|
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-cli"
|
name = "tauri-plugin-cli"
|
||||||
version = "2.4.0"
|
version = "2.4.1"
|
||||||
description = "Parse arguments from your Tauri application's command line interface."
|
description = "Parse arguments from your Tauri application's command line interface."
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
@@ -9,10 +9,6 @@ rust-version = { workspace = true }
|
|||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
links = "tauri-plugin-cli"
|
links = "tauri-plugin-cli"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
rustc-args = ["--cfg", "docsrs"]
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[package.metadata.platforms.support]
|
[package.metadata.platforms.support]
|
||||||
windows = { level = "full", notes = "" }
|
windows = { level = "full", notes = "" }
|
||||||
linux = { level = "full", notes = "" }
|
linux = { level = "full", notes = "" }
|
||||||
|
|||||||
@@ -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:
|
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
|
```sh
|
||||||
pnpm add @tauri-apps/plugin-cli
|
pnpm add @tauri-apps/plugin-cli
|
||||||
# or
|
# or
|
||||||
npm add @tauri-apps/plugin-cli
|
npm add @tauri-apps/plugin-cli
|
||||||
# or
|
# or
|
||||||
yarn add @tauri-apps/plugin-cli
|
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
|
## Usage
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tauri-apps/plugin-cli",
|
"name": "@tauri-apps/plugin-cli",
|
||||||
"version": "2.4.0",
|
"version": "2.4.1",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Tauri Programme within The Commons Conservancy"
|
"Tauri Programme within The Commons Conservancy"
|
||||||
@@ -24,6 +24,6 @@
|
|||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.6.0"
|
"@tauri-apps/api": "^2.10.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.3.2]
|
||||||
|
|
||||||
|
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
|
||||||
|
|
||||||
|
## \[2.3.1]
|
||||||
|
|
||||||
|
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
|
||||||
|
|
||||||
## \[2.3.0]
|
## \[2.3.0]
|
||||||
|
|
||||||
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
|
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-clipboard-manager"
|
name = "tauri-plugin-clipboard-manager"
|
||||||
version = "2.3.0"
|
version = "2.3.2"
|
||||||
description = "Read and write to the system clipboard."
|
description = "Read and write to the system clipboard."
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
@@ -10,8 +10,7 @@ repository = { workspace = true }
|
|||||||
links = "tauri-plugin-clipboard-manager"
|
links = "tauri-plugin-clipboard-manager"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustc-args = ["--cfg", "docsrs"]
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"]
|
targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"]
|
||||||
|
|
||||||
[package.metadata.platforms.support]
|
[package.metadata.platforms.support]
|
||||||
|
|||||||
@@ -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:
|
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
|
```sh
|
||||||
pnpm add @tauri-apps/plugin-clipboard-manager
|
pnpm add @tauri-apps/plugin-clipboard-manager
|
||||||
# or
|
# or
|
||||||
npm add @tauri-apps/plugin-clipboard-manager
|
npm add @tauri-apps/plugin-clipboard-manager
|
||||||
# or
|
# or
|
||||||
yarn add @tauri-apps/plugin-clipboard-manager
|
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
|
## Usage
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.tauri.clipboard"
|
namespace = "app.tauri.clipboard"
|
||||||
compileSdk = 34
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tauri-apps/plugin-clipboard-manager",
|
"name": "@tauri-apps/plugin-clipboard-manager",
|
||||||
"version": "2.3.0",
|
"version": "2.3.2",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Tauri Programme within The Commons Conservancy"
|
"Tauri Programme within The Commons Conservancy"
|
||||||
@@ -24,6 +24,6 @@
|
|||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.6.0"
|
"@tauri-apps/api": "^2.10.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,31 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.4.6]
|
||||||
|
|
||||||
|
- [`28048039`](https://github.com/tauri-apps/plugins-workspace/commit/28048039496e84b46847c008416d341f1349e30e) ([#3143](https://github.com/tauri-apps/plugins-workspace/pull/3143) by [@Tunglies](https://github.com/tauri-apps/plugins-workspace/../../Tunglies)) Fix clippy warnings. No user facing changes.
|
||||||
|
|
||||||
|
## \[2.4.5]
|
||||||
|
|
||||||
|
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
|
||||||
|
|
||||||
|
## \[2.4.4]
|
||||||
|
|
||||||
|
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
|
||||||
|
- [`e25a9339`](https://github.com/tauri-apps/plugins-workspace/commit/e25a9339f46268f70dc9afd0c5ab3decbf79b330) ([#3019](https://github.com/tauri-apps/plugins-workspace/pull/3019) by [@kevinschoonover](https://github.com/tauri-apps/plugins-workspace/../../kevinschoonover)) Fix Exec= field in desktop handler if executable path changes
|
||||||
|
|
||||||
|
## \[2.4.3]
|
||||||
|
|
||||||
|
- [`2522b71f`](https://github.com/tauri-apps/plugins-workspace/commit/2522b71f6bcae65c03b24415eb9295c9e7c84ffc) ([#2970](https://github.com/tauri-apps/plugins-workspace/pull/2970) by [@WSH032](https://github.com/tauri-apps/plugins-workspace/../../WSH032)) Revert the breaking change introduced by [#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928).
|
||||||
|
|
||||||
|
## \[2.4.2]
|
||||||
|
|
||||||
|
- [`21d721a0`](https://github.com/tauri-apps/plugins-workspace/commit/21d721a0c2731fc201872f5b99ea8bbdc61b0b60) ([#2928](https://github.com/tauri-apps/plugins-workspace/pull/2928) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Linux, improved error messages when OS commands fail.
|
||||||
|
|
||||||
|
## \[2.4.1]
|
||||||
|
|
||||||
|
- [`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]
|
## \[2.4.0]
|
||||||
|
|
||||||
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
|
- [`f209b2f2`](https://github.com/tauri-apps/plugins-workspace/commit/f209b2f23cb29133c97ad5961fb46ef794dbe063) ([#2804](https://github.com/tauri-apps/plugins-workspace/pull/2804) by [@renovate](https://github.com/tauri-apps/plugins-workspace/../../renovate)) Updated tauri to 2.6
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-deep-link"
|
name = "tauri-plugin-deep-link"
|
||||||
version = "2.4.0"
|
version = "2.4.6"
|
||||||
description = "Set your Tauri application as the default handler for an URL"
|
description = "Set your Tauri application as the default handler for an URL"
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
@@ -10,16 +10,14 @@ repository = { workspace = true }
|
|||||||
links = "tauri-plugin-deep-link"
|
links = "tauri-plugin-deep-link"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustc-args = ["--cfg", "docsrs"]
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
targets = ["x86_64-linux-android"]
|
targets = ["x86_64-linux-android"]
|
||||||
|
|
||||||
[package.metadata.platforms.support]
|
[package.metadata.platforms.support]
|
||||||
windows = { level = "full", notes = "" }
|
windows = { level = "full", notes = "" }
|
||||||
linux = { level = "full", notes = "" }
|
linux = { level = "full", notes = "" }
|
||||||
macos = { 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 = "Runtime deep link registration 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 = "Runtime deep link registration is not supported" }
|
ios = { level = "partial", notes = "Deep links must be registered in config. Dynamic registration at runtime is not supported." }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
@@ -27,6 +25,9 @@ serde_json = { workspace = true }
|
|||||||
tauri-utils = { workspace = true }
|
tauri-utils = { workspace = true }
|
||||||
tauri-plugin = { workspace = true, features = ["build"] }
|
tauri-plugin = { workspace = true, features = ["build"] }
|
||||||
|
|
||||||
|
[target."cfg(target_os = \"macos\")".build-dependencies]
|
||||||
|
plist = "1"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|||||||
@@ -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:
|
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
|
```sh
|
||||||
pnpm add @tauri-apps/plugin-deep-link
|
pnpm add @tauri-apps/plugin-deep-link
|
||||||
# or
|
# or
|
||||||
npm add @tauri-apps/plugin-deep-link
|
npm add @tauri-apps/plugin-deep-link
|
||||||
# or
|
# or
|
||||||
yarn add @tauri-apps/plugin-deep-link
|
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
|
## Setting up
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.tauri.deep_link"
|
namespace = "app.tauri.deep_link"
|
||||||
compileSdk = 34
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
|
|||||||
+113
-26
@@ -10,50 +10,64 @@ const COMMANDS: &[&str] = &["get_current", "register", "unregister", "is_registe
|
|||||||
|
|
||||||
// TODO: Consider using activity-alias in case users may have multiple activities in their app.
|
// TODO: Consider using activity-alias in case users may have multiple activities in their app.
|
||||||
fn intent_filter(domain: &AssociatedDomain) -> String {
|
fn intent_filter(domain: &AssociatedDomain) -> String {
|
||||||
|
let host = domain
|
||||||
|
.host
|
||||||
|
.as_ref()
|
||||||
|
.map(|h| format!(r#"<data android:host="{h}" />"#))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let auto_verify = if domain.is_app_link() {
|
||||||
|
r#"android:autoVerify="true" "#.to_string()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
r#"<intent-filter android:autoVerify="true">
|
r#"<intent-filter {auto_verify}>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
{}
|
{schemes}
|
||||||
<data android:host="{}" />
|
{host}
|
||||||
{}
|
{domains}
|
||||||
{}
|
{path_patterns}
|
||||||
{}
|
{path_prefixes}
|
||||||
{}
|
{path_suffixes}
|
||||||
</intent-filter>"#,
|
</intent-filter>"#,
|
||||||
domain
|
schemes = domain
|
||||||
.scheme
|
.scheme
|
||||||
.iter()
|
.iter()
|
||||||
.map(|scheme| format!(r#"<data android:scheme="{scheme}" />"#))
|
.map(|scheme| format!(r#"<data android:scheme="{scheme}" />"#))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n "),
|
.join("\n "),
|
||||||
domain.host,
|
host = host,
|
||||||
domain
|
domains = domain
|
||||||
.path
|
.path
|
||||||
.iter()
|
.iter()
|
||||||
.map(|path| format!(r#"<data android:path="{path}" />"#))
|
.map(|path| format!(r#"<data android:path="{path}" />"#))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n "),
|
.join("\n "),
|
||||||
domain
|
path_patterns = domain
|
||||||
.path_pattern
|
.path_pattern
|
||||||
.iter()
|
.iter()
|
||||||
.map(|pattern| format!(r#"<data android:pathPattern="{pattern}" />"#))
|
.map(|pattern| format!(r#"<data android:pathPattern="{pattern}" />"#))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n "),
|
.join("\n "),
|
||||||
domain
|
path_prefixes = domain
|
||||||
.path_prefix
|
.path_prefix
|
||||||
.iter()
|
.iter()
|
||||||
.map(|prefix| format!(r#"<data android:pathPrefix="{prefix}" />"#))
|
.map(|prefix| format!(r#"<data android:pathPrefix="{prefix}" />"#))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n "),
|
.join("\n "),
|
||||||
domain
|
path_suffixes = domain
|
||||||
.path_suffix
|
.path_suffix
|
||||||
.iter()
|
.iter()
|
||||||
.map(|suffix| format!(r#"<data android:pathSuffix="{suffix}" />"#))
|
.map(|suffix| format!(r#"<data android:pathSuffix="{suffix}" />"#))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n "),
|
.join("\n "),
|
||||||
)
|
)
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -68,6 +82,16 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(config) = tauri_plugin::plugin_config::<Config>("deep-link") {
|
if let Some(config) = tauri_plugin::plugin_config::<Config>("deep-link") {
|
||||||
|
let errors: Vec<String> = config
|
||||||
|
.mobile
|
||||||
|
.iter()
|
||||||
|
.filter_map(|d| d.validate().err())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
panic!("Deep link config validation failed:\n{}", errors.join("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
tauri_plugin::mobile::update_android_manifest(
|
tauri_plugin::mobile::update_android_manifest(
|
||||||
"DEEP LINK PLUGIN",
|
"DEEP LINK PLUGIN",
|
||||||
"activity",
|
"activity",
|
||||||
@@ -80,20 +104,83 @@ fn main() {
|
|||||||
)
|
)
|
||||||
.expect("failed to rewrite AndroidManifest.xml");
|
.expect("failed to rewrite AndroidManifest.xml");
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||||
{
|
{
|
||||||
tauri_plugin::mobile::update_entitlements(|entitlements| {
|
// we need to ensure that the entitlements are only
|
||||||
entitlements.insert(
|
// generated for explicit app links and not
|
||||||
"com.apple.developer.associated-domains".into(),
|
// other deep links because then they
|
||||||
config
|
// are just going to complain and not be built or signed
|
||||||
.mobile
|
let has_app_links = config.mobile.iter().any(|d| d.is_app_link());
|
||||||
.into_iter()
|
|
||||||
.map(|d| format!("applinks:{}", d.host).into())
|
if !has_app_links {
|
||||||
.collect::<Vec<_>>()
|
tauri_plugin::mobile::update_entitlements(|entitlements| {
|
||||||
.into(),
|
entitlements.remove("com.apple.developer.associated-domains");
|
||||||
);
|
})
|
||||||
})
|
.expect("failed to update entitlements");
|
||||||
.expect("failed to update entitlements");
|
} else {
|
||||||
|
tauri_plugin::mobile::update_entitlements(|entitlements| {
|
||||||
|
entitlements.insert(
|
||||||
|
"com.apple.developer.associated-domains".into(),
|
||||||
|
config
|
||||||
|
.mobile
|
||||||
|
.iter()
|
||||||
|
.filter(|d| d.is_app_link())
|
||||||
|
.filter_map(|d| d.host.as_ref())
|
||||||
|
.map(|host| format!("applinks:{}", host).into())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.expect("failed to update entitlements");
|
||||||
|
}
|
||||||
|
|
||||||
|
let deep_link_domains = config
|
||||||
|
.mobile
|
||||||
|
.iter()
|
||||||
|
.filter(|domain| domain.is_app_link())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if deep_link_domains.is_empty() {
|
||||||
|
tauri_plugin::mobile::update_info_plist(|info_plist| {
|
||||||
|
info_plist.remove("CFBundleURLTypes");
|
||||||
|
})
|
||||||
|
.expect("failed to update Info.plist");
|
||||||
|
} else {
|
||||||
|
tauri_plugin::mobile::update_info_plist(|info_plist| {
|
||||||
|
info_plist.insert(
|
||||||
|
"CFBundleURLTypes".into(),
|
||||||
|
deep_link_domains
|
||||||
|
.iter()
|
||||||
|
.map(|domain| {
|
||||||
|
let schemes = domain
|
||||||
|
.scheme
|
||||||
|
.iter()
|
||||||
|
.filter(|scheme| {
|
||||||
|
scheme.as_str() != "https" && scheme.as_str() != "http"
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut dict = plist::Dictionary::new();
|
||||||
|
dict.insert(
|
||||||
|
"CFBundleURLSchemes".into(),
|
||||||
|
schemes
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.to_string().into())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
dict.insert(
|
||||||
|
"CFBundleURLName".into(),
|
||||||
|
domain.scheme[0].clone().into(),
|
||||||
|
);
|
||||||
|
plist::Value::Dictionary(dict)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.expect("failed to update Info.plist");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,41 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.2.9]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `deep-link-js@2.4.6`
|
||||||
|
|
||||||
|
## \[2.2.8]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `deep-link-js@2.4.5`
|
||||||
|
|
||||||
|
## \[2.2.7]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `deep-link-js@2.4.4`
|
||||||
|
|
||||||
|
## \[2.2.6]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `deep-link-js@2.4.3`
|
||||||
|
|
||||||
|
## \[2.2.5]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `deep-link-js@2.4.2`
|
||||||
|
|
||||||
|
## \[2.2.4]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `deep-link-js@2.4.1`
|
||||||
|
|
||||||
## \[2.2.3]
|
## \[2.2.3]
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "deep-link-example",
|
"name": "deep-link-example",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.2.3",
|
"version": "2.2.9",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -10,12 +10,12 @@
|
|||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "2.6.0",
|
"@tauri-apps/api": "^2.10.1",
|
||||||
"@tauri-apps/plugin-deep-link": "2.4.0"
|
"@tauri-apps/plugin-deep-link": "2.4.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "2.6.2",
|
"@tauri-apps/cli": "2.10.0",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"vite": "^7.0.4"
|
"vite": "^7.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ val tauriProperties = Properties().apply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdk = 34
|
compileSdk = 36
|
||||||
namespace = "com.tauri.deep_link_example"
|
namespace = "com.tauri.deep_link_example"
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
||||||
applicationId = "com.tauri.deep_link_example"
|
applicationId = "com.tauri.deep_link_example"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 34
|
targetSdk = 36
|
||||||
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
|
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
|
||||||
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
|
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
|
||||||
}
|
}
|
||||||
@@ -58,9 +58,10 @@ rust {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.webkit:webkit:1.6.1")
|
implementation("androidx.webkit:webkit:1.14.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation("androidx.appcompat:appcompat:1.7.1")
|
||||||
implementation("com.google.android.material:material:1.8.0")
|
implementation("androidx.activity:activity-ktx:1.10.1")
|
||||||
|
implementation("com.google.android.material:material:1.12.0")
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.4")
|
androidTestImplementation("androidx.test.ext:junit:1.1.4")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
|
||||||
|
|||||||
+21
-4
@@ -23,23 +23,40 @@
|
|||||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
|
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
|
||||||
<intent-filter android:autoVerify="true">
|
<intent-filter android:autoVerify="true" >
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="http" />
|
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
|
<data android:scheme="http" />
|
||||||
<data android:host="fabianlars.de" />
|
<data android:host="fabianlars.de" />
|
||||||
|
|
||||||
|
|
||||||
<data android:pathPrefix="/intent" />
|
<data android:pathPrefix="/intent" />
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter android:autoVerify="true">
|
<intent-filter android:autoVerify="true" >
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="http" />
|
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
|
<data android:scheme="http" />
|
||||||
<data android:host="tauri.app" />
|
<data android:host="tauri.app" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter >
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="taurideeplink" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
|
<!-- DEEP LINK PLUGIN. AUTO-GENERATED. DO NOT REMOVE. -->
|
||||||
</activity>
|
</activity>
|
||||||
|
|||||||
+9
-5
@@ -1,7 +1,11 @@
|
|||||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package com.tauri.deep_link_example
|
package com.tauri.deep_link_example
|
||||||
|
|
||||||
class MainActivity : TauriActivity()
|
import android.os.Bundle
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
|
||||||
|
class MainActivity : TauriActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:8.5.1")
|
classpath("com.android.tools.build:gradle:8.11.0")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(gradleApi())
|
compileOnly(gradleApi())
|
||||||
implementation("com.android.tools.build:gradle:8.5.1")
|
implementation("com.android.tools.build:gradle:8.11.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
#Tue May 10 19:22:52 CST 2022
|
#Tue May 10 19:22:52 CST 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
+77
-77
@@ -1,116 +1,116 @@
|
|||||||
{
|
{
|
||||||
"images": [
|
"images" : [
|
||||||
{
|
{
|
||||||
"size": "20x20",
|
"size" : "20x20",
|
||||||
"idiom": "iphone",
|
"idiom" : "iphone",
|
||||||
"filename": "AppIcon-20x20@2x.png",
|
"filename" : "AppIcon-20x20@2x.png",
|
||||||
"scale": "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "20x20",
|
"size" : "20x20",
|
||||||
"idiom": "iphone",
|
"idiom" : "iphone",
|
||||||
"filename": "AppIcon-20x20@3x.png",
|
"filename" : "AppIcon-20x20@3x.png",
|
||||||
"scale": "3x"
|
"scale" : "3x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "29x29",
|
"size" : "29x29",
|
||||||
"idiom": "iphone",
|
"idiom" : "iphone",
|
||||||
"filename": "AppIcon-29x29@2x-1.png",
|
"filename" : "AppIcon-29x29@2x-1.png",
|
||||||
"scale": "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "29x29",
|
"size" : "29x29",
|
||||||
"idiom": "iphone",
|
"idiom" : "iphone",
|
||||||
"filename": "AppIcon-29x29@3x.png",
|
"filename" : "AppIcon-29x29@3x.png",
|
||||||
"scale": "3x"
|
"scale" : "3x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "40x40",
|
"size" : "40x40",
|
||||||
"idiom": "iphone",
|
"idiom" : "iphone",
|
||||||
"filename": "AppIcon-40x40@2x.png",
|
"filename" : "AppIcon-40x40@2x.png",
|
||||||
"scale": "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "40x40",
|
"size" : "40x40",
|
||||||
"idiom": "iphone",
|
"idiom" : "iphone",
|
||||||
"filename": "AppIcon-40x40@3x.png",
|
"filename" : "AppIcon-40x40@3x.png",
|
||||||
"scale": "3x"
|
"scale" : "3x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "60x60",
|
"size" : "60x60",
|
||||||
"idiom": "iphone",
|
"idiom" : "iphone",
|
||||||
"filename": "AppIcon-60x60@2x.png",
|
"filename" : "AppIcon-60x60@2x.png",
|
||||||
"scale": "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "60x60",
|
"size" : "60x60",
|
||||||
"idiom": "iphone",
|
"idiom" : "iphone",
|
||||||
"filename": "AppIcon-60x60@3x.png",
|
"filename" : "AppIcon-60x60@3x.png",
|
||||||
"scale": "3x"
|
"scale" : "3x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "20x20",
|
"size" : "20x20",
|
||||||
"idiom": "ipad",
|
"idiom" : "ipad",
|
||||||
"filename": "AppIcon-20x20@1x.png",
|
"filename" : "AppIcon-20x20@1x.png",
|
||||||
"scale": "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "20x20",
|
"size" : "20x20",
|
||||||
"idiom": "ipad",
|
"idiom" : "ipad",
|
||||||
"filename": "AppIcon-20x20@2x-1.png",
|
"filename" : "AppIcon-20x20@2x-1.png",
|
||||||
"scale": "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "29x29",
|
"size" : "29x29",
|
||||||
"idiom": "ipad",
|
"idiom" : "ipad",
|
||||||
"filename": "AppIcon-29x29@1x.png",
|
"filename" : "AppIcon-29x29@1x.png",
|
||||||
"scale": "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "29x29",
|
"size" : "29x29",
|
||||||
"idiom": "ipad",
|
"idiom" : "ipad",
|
||||||
"filename": "AppIcon-29x29@2x.png",
|
"filename" : "AppIcon-29x29@2x.png",
|
||||||
"scale": "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "40x40",
|
"size" : "40x40",
|
||||||
"idiom": "ipad",
|
"idiom" : "ipad",
|
||||||
"filename": "AppIcon-40x40@1x.png",
|
"filename" : "AppIcon-40x40@1x.png",
|
||||||
"scale": "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "40x40",
|
"size" : "40x40",
|
||||||
"idiom": "ipad",
|
"idiom" : "ipad",
|
||||||
"filename": "AppIcon-40x40@2x-1.png",
|
"filename" : "AppIcon-40x40@2x-1.png",
|
||||||
"scale": "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "76x76",
|
"size" : "76x76",
|
||||||
"idiom": "ipad",
|
"idiom" : "ipad",
|
||||||
"filename": "AppIcon-76x76@1x.png",
|
"filename" : "AppIcon-76x76@1x.png",
|
||||||
"scale": "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "76x76",
|
"size" : "76x76",
|
||||||
"idiom": "ipad",
|
"idiom" : "ipad",
|
||||||
"filename": "AppIcon-76x76@2x.png",
|
"filename" : "AppIcon-76x76@2x.png",
|
||||||
"scale": "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "83.5x83.5",
|
"size" : "83.5x83.5",
|
||||||
"idiom": "ipad",
|
"idiom" : "ipad",
|
||||||
"filename": "AppIcon-83.5x83.5@2x.png",
|
"filename" : "AppIcon-83.5x83.5@2x.png",
|
||||||
"scale": "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size": "1024x1024",
|
"size" : "1024x1024",
|
||||||
"idiom": "ios-marketing",
|
"idiom" : "ios-marketing",
|
||||||
"filename": "AppIcon-512@2x.png",
|
"filename" : "AppIcon-512@2x.png",
|
||||||
"scale": "1x"
|
"scale" : "1x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info": {
|
"info" : {
|
||||||
"version": 1,
|
"version" : 1,
|
||||||
"author": "xcode"
|
"author" : "xcode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"info": {
|
"info" : {
|
||||||
"version": 1,
|
"version" : 1,
|
||||||
"author": "xcode"
|
"author" : "xcode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17150" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17150" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="Y6W-OH-hqX">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17122"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17122"/>
|
||||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
|||||||
+12
-34
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 56;
|
objectVersion = 63;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -167,6 +167,8 @@
|
|||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
name = "deep-link-example_iOS";
|
name = "deep-link-example_iOS";
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
productName = "deep-link-example_iOS";
|
productName = "deep-link-example_iOS";
|
||||||
productReference = 1CAAFA750FD735A285DC1238 /* deep-link-example.app */;
|
productReference = 1CAAFA750FD735A285DC1238 /* deep-link-example.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
@@ -189,6 +191,7 @@
|
|||||||
en,
|
en,
|
||||||
);
|
);
|
||||||
mainGroup = 1DC58B1705AA3ECC6B887FE7;
|
mainGroup = 1DC58B1705AA3ECC6B887FE7;
|
||||||
|
minimizedProjectReferenceProxies = 1;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
@@ -227,7 +230,6 @@
|
|||||||
outputPaths = (
|
outputPaths = (
|
||||||
"$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a",
|
"$(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a",
|
||||||
"$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a",
|
"$(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a",
|
||||||
"$(SRCROOT)/Externals/arm64-sim/${CONFIGURATION}/libapp.a",
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
@@ -314,18 +316,13 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ARCHS = (
|
ARCHS = arm64;
|
||||||
arm64,
|
|
||||||
"arm64-sim",
|
|
||||||
);
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
DEVELOPMENT_TEAM = "Q93MBH6S2F";
|
||||||
DEVELOPMENT_TEAM = Q93MBH6S2F;
|
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
"EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
|
"EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
|
||||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"\".\"",
|
"\".\"",
|
||||||
@@ -335,13 +332,6 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
"LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = (
|
|
||||||
"$(inherited)",
|
|
||||||
"$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)",
|
|
||||||
"$(SDKROOT)/usr/lib/swift",
|
|
||||||
"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)",
|
|
||||||
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
|
|
||||||
);
|
|
||||||
"LIBRARY_SEARCH_PATHS[arch=arm64]" = (
|
"LIBRARY_SEARCH_PATHS[arch=arm64]" = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)",
|
"$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)",
|
||||||
@@ -360,7 +350,7 @@
|
|||||||
PRODUCT_NAME = "deep-link-example";
|
PRODUCT_NAME = "deep-link-example";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
VALID_ARCHS = "arm64 arm64-sim";
|
VALID_ARCHS = arm64;
|
||||||
};
|
};
|
||||||
name = debug;
|
name = debug;
|
||||||
};
|
};
|
||||||
@@ -424,18 +414,13 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
ARCHS = (
|
ARCHS = arm64;
|
||||||
arm64,
|
|
||||||
"arm64-sim",
|
|
||||||
);
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "deep-link-example_iOS/deep-link-example_iOS.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
DEVELOPMENT_TEAM = "Q93MBH6S2F";
|
||||||
DEVELOPMENT_TEAM = Q93MBH6S2F;
|
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
"EXCLUDED_ARCHS[sdk=iphoneos*]" = "arm64-sim x86_64";
|
"EXCLUDED_ARCHS[sdk=iphoneos*]" = x86_64;
|
||||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
|
||||||
FRAMEWORK_SEARCH_PATHS = (
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"\".\"",
|
"\".\"",
|
||||||
@@ -445,13 +430,6 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
"LIBRARY_SEARCH_PATHS[arch=arm64-sim]" = (
|
|
||||||
"$(inherited)",
|
|
||||||
"$(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION)",
|
|
||||||
"$(SDKROOT)/usr/lib/swift",
|
|
||||||
"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)",
|
|
||||||
"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)",
|
|
||||||
);
|
|
||||||
"LIBRARY_SEARCH_PATHS[arch=arm64]" = (
|
"LIBRARY_SEARCH_PATHS[arch=arm64]" = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)",
|
"$(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION)",
|
||||||
@@ -470,7 +448,7 @@
|
|||||||
PRODUCT_NAME = "deep-link-example";
|
PRODUCT_NAME = "deep-link-example";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
VALID_ARCHS = "arm64 arm64-sim";
|
VALID_ARCHS = arm64;
|
||||||
};
|
};
|
||||||
name = release;
|
name = release;
|
||||||
};
|
};
|
||||||
|
|||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>IDEDidComputeMac32BitWarning</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
+12
-1
@@ -15,7 +15,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.0.0</string>
|
<string>0.1.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>0.1.0</string>
|
<string>0.1.0</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
@@ -40,5 +40,16 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>taurideeplink</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>taurideeplink</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
# Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
# SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
name: deep-link-example
|
name: deep-link-example
|
||||||
options:
|
options:
|
||||||
bundleIdPrefix: com.tauri.deep-link-example
|
bundleIdPrefix: com.tauri.deep-link-example
|
||||||
@@ -16,7 +12,6 @@ settingGroups:
|
|||||||
base:
|
base:
|
||||||
PRODUCT_NAME: deep-link-example
|
PRODUCT_NAME: deep-link-example
|
||||||
PRODUCT_BUNDLE_IDENTIFIER: com.tauri.deep-link-example
|
PRODUCT_BUNDLE_IDENTIFIER: com.tauri.deep-link-example
|
||||||
DEVELOPMENT_TEAM: Q93MBH6S2F
|
|
||||||
targetTemplates:
|
targetTemplates:
|
||||||
app:
|
app:
|
||||||
type: application
|
type: application
|
||||||
@@ -56,8 +51,8 @@ targets:
|
|||||||
- UIInterfaceOrientationPortraitUpsideDown
|
- UIInterfaceOrientationPortraitUpsideDown
|
||||||
- UIInterfaceOrientationLandscapeLeft
|
- UIInterfaceOrientationLandscapeLeft
|
||||||
- UIInterfaceOrientationLandscapeRight
|
- UIInterfaceOrientationLandscapeRight
|
||||||
CFBundleShortVersionString: 0.0.0
|
CFBundleShortVersionString: 0.1.0
|
||||||
CFBundleVersion: 0.0.0
|
CFBundleVersion: '0.1.0'
|
||||||
entitlements:
|
entitlements:
|
||||||
path: deep-link-example_iOS/deep-link-example_iOS.entitlements
|
path: deep-link-example_iOS/deep-link-example_iOS.entitlements
|
||||||
scheme:
|
scheme:
|
||||||
@@ -67,14 +62,12 @@ targets:
|
|||||||
settings:
|
settings:
|
||||||
base:
|
base:
|
||||||
ENABLE_BITCODE: false
|
ENABLE_BITCODE: false
|
||||||
ARCHS: [arm64, arm64-sim]
|
ARCHS: [arm64]
|
||||||
VALID_ARCHS: arm64 arm64-sim
|
VALID_ARCHS: arm64
|
||||||
LIBRARY_SEARCH_PATHS[arch=x86_64]: $(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
|
LIBRARY_SEARCH_PATHS[arch=x86_64]: $(inherited) $(PROJECT_DIR)/Externals/x86_64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
|
||||||
LIBRARY_SEARCH_PATHS[arch=arm64]: $(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
|
LIBRARY_SEARCH_PATHS[arch=arm64]: $(inherited) $(PROJECT_DIR)/Externals/arm64/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
|
||||||
LIBRARY_SEARCH_PATHS[arch=arm64-sim]: $(inherited) $(PROJECT_DIR)/Externals/arm64-sim/$(CONFIGURATION) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME) $(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)
|
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: true
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES: true
|
||||||
EXCLUDED_ARCHS[sdk=iphonesimulator*]: arm64
|
EXCLUDED_ARCHS[sdk=iphoneos*]: x86_64
|
||||||
EXCLUDED_ARCHS[sdk=iphoneos*]: arm64-sim x86_64
|
|
||||||
groups: [app]
|
groups: [app]
|
||||||
dependencies:
|
dependencies:
|
||||||
- framework: libapp.a
|
- framework: libapp.a
|
||||||
@@ -93,4 +86,3 @@ targets:
|
|||||||
outputFiles:
|
outputFiles:
|
||||||
- $(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a
|
- $(SRCROOT)/Externals/x86_64/${CONFIGURATION}/libapp.a
|
||||||
- $(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a
|
- $(SRCROOT)/Externals/arm64/${CONFIGURATION}/libapp.a
|
||||||
- $(SRCROOT)/Externals/arm64-sim/${CONFIGURATION}/libapp.a
|
|
||||||
|
|||||||
@@ -35,6 +35,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"host": "tauri.app"
|
"host": "tauri.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scheme": ["taurideeplink"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"desktop": {
|
"desktop": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tauri-apps/plugin-deep-link",
|
"name": "@tauri-apps/plugin-deep-link",
|
||||||
"version": "2.4.0",
|
"version": "2.4.6",
|
||||||
"description": "Set your Tauri application as the default handler for an URL",
|
"description": "Set your Tauri application as the default handler for an URL",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
@@ -25,6 +25,9 @@
|
|||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.6.0"
|
"@tauri-apps/api": "^2.10.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "2.10.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,8 @@ use tauri_utils::config::DeepLinkProtocol;
|
|||||||
pub struct AssociatedDomain {
|
pub struct AssociatedDomain {
|
||||||
#[serde(default = "default_schemes")]
|
#[serde(default = "default_schemes")]
|
||||||
pub scheme: Vec<String>,
|
pub scheme: Vec<String>,
|
||||||
|
#[serde(default, deserialize_with = "deserialize_associated_host")]
|
||||||
#[serde(deserialize_with = "deserialize_associated_host")]
|
pub host: Option<String>, // Optional custom uri schemes dont NEED a host (may have one still), but required for https/http schemes
|
||||||
pub host: String,
|
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub path: Vec<String>,
|
pub path: Vec<String>,
|
||||||
#[serde(default, alias = "path-pattern", rename = "pathPattern")]
|
#[serde(default, alias = "path-pattern", rename = "pathPattern")]
|
||||||
@@ -23,6 +21,41 @@ pub struct AssociatedDomain {
|
|||||||
pub path_prefix: Vec<String>,
|
pub path_prefix: Vec<String>,
|
||||||
#[serde(default, alias = "path-suffix", rename = "pathSuffix")]
|
#[serde(default, alias = "path-suffix", rename = "pathSuffix")]
|
||||||
pub path_suffix: Vec<String>,
|
pub path_suffix: Vec<String>,
|
||||||
|
#[serde(default, alias = "app-link", rename = "appLink")]
|
||||||
|
pub app_link: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssociatedDomain {
|
||||||
|
/// Returns true if the domain uses http or https scheme.
|
||||||
|
pub fn is_web_link(&self) -> bool {
|
||||||
|
self.scheme.iter().any(|s| s == "https" || s == "http")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the domain uses http or https scheme and has proper host configuration.
|
||||||
|
pub fn is_app_link(&self) -> bool {
|
||||||
|
self.app_link
|
||||||
|
.unwrap_or_else(|| self.is_web_link() && self.host.is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate(&self) -> Result<(), String> {
|
||||||
|
// Rule 1: All web links require a host.
|
||||||
|
if self.is_web_link() && self.host.is_none() {
|
||||||
|
return Err("Web link requires a host".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule 2: If it's an App Link, ensure http(s) and host.
|
||||||
|
if self.is_app_link() {
|
||||||
|
if !self.is_web_link() {
|
||||||
|
return Err("AppLink must be a valid web link (https/http + host)".into());
|
||||||
|
}
|
||||||
|
if self.scheme.iter().any(|s| s == "http") && !self.scheme.iter().any(|s| s == "https")
|
||||||
|
{
|
||||||
|
eprintln!("Warning: AppLink uses only 'http' — allowed on Android but not secure for production.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Consider removing this in v3
|
// TODO: Consider removing this in v3
|
||||||
@@ -30,18 +63,19 @@ fn default_schemes() -> Vec<String> {
|
|||||||
vec!["https".to_string(), "http".to_string()]
|
vec!["https".to_string(), "http".to_string()]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_associated_host<'de, D>(deserializer: D) -> Result<String, D::Error>
|
fn deserialize_associated_host<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let host = String::deserialize(deserializer)?;
|
let opt = Option::<String>::deserialize(deserializer)?;
|
||||||
if let Some((scheme, _host)) = host.split_once("://") {
|
if let Some(ref host) = opt {
|
||||||
Err(serde::de::Error::custom(format!(
|
if let Some((scheme, _)) = host.split_once("://") {
|
||||||
"host `{host}` cannot start with a scheme, please remove the `{scheme}://` prefix"
|
return Err(serde::de::Error::custom(format!(
|
||||||
)))
|
"host `{host}` cannot start with a scheme, please remove the `{scheme}://` prefix"
|
||||||
} else {
|
)));
|
||||||
Ok(host)
|
}
|
||||||
}
|
}
|
||||||
|
Ok(opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ pub enum Error {
|
|||||||
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
|
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(v3): change this into an error in v3,
|
||||||
|
// see <https://github.com/tauri-apps/plugins-workspace/pull/2970#issuecomment-3244660138>.
|
||||||
|
#[inline]
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub(crate) fn inspect_command_error<'a>(command: &'a str) -> impl Fn(&std::io::Error) + 'a {
|
||||||
|
move |e| {
|
||||||
|
tracing::error!("Failed to run OS command `{command}`: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Serialize for Error {
|
impl Serialize for Error {
|
||||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -254,6 +254,7 @@ mod imp {
|
|||||||
///
|
///
|
||||||
/// ## Platform-specific:
|
/// ## Platform-specific:
|
||||||
///
|
///
|
||||||
|
/// - **Linux**: Needs the `xdg-mime` and `update-desktop-database` commands available on the system.
|
||||||
/// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`).
|
/// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`).
|
||||||
pub fn register<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<()> {
|
pub fn register<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<()> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -292,6 +293,7 @@ mod imp {
|
|||||||
.unwrap_or_else(|| bin.into_os_string())
|
.unwrap_or_else(|| bin.into_os_string())
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
let qualified_exec = format!("{} %u", exec);
|
||||||
|
|
||||||
let target = self.app.path().data_dir()?.join("applications");
|
let target = self.app.path().data_dir()?.join("applications");
|
||||||
|
|
||||||
@@ -303,12 +305,30 @@ mod imp {
|
|||||||
|
|
||||||
if let Ok(mut desktop_file) = ini::Ini::load_from_file(&target_file) {
|
if let Ok(mut desktop_file) = ini::Ini::load_from_file(&target_file) {
|
||||||
if let Some(section) = desktop_file.section_mut(Some("Desktop Entry")) {
|
if let Some(section) = desktop_file.section_mut(Some("Desktop Entry")) {
|
||||||
let old_mimes = section.remove("MimeType");
|
let old_mimes = section.remove("MimeType").unwrap_or_default();
|
||||||
section.append(
|
let mut change = false;
|
||||||
"MimeType",
|
|
||||||
format!("{mime_type};{}", old_mimes.unwrap_or_default()),
|
// if the mime type is not present, append it to the list
|
||||||
);
|
if !old_mimes.split(';').any(|mime| mime == mime_type) {
|
||||||
desktop_file.write_to_file(&target_file)?;
|
section.append("MimeType", format!("{mime_type};{old_mimes}"));
|
||||||
|
change = true;
|
||||||
|
} else {
|
||||||
|
section.insert("MimeType".to_string(), old_mimes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the exec command doesnt match, update to the new one
|
||||||
|
let old_exec = section.remove("Exec").unwrap_or_default();
|
||||||
|
if old_exec != qualified_exec {
|
||||||
|
section.append("Exec", qualified_exec);
|
||||||
|
change = true;
|
||||||
|
} else {
|
||||||
|
section.insert("Exec".to_string(), old_exec.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// if any property has changed, rewrite the .desktop file
|
||||||
|
if change {
|
||||||
|
desktop_file.write_to_file(&target_file)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut file = File::create(target_file)?;
|
let mut file = File::create(target_file)?;
|
||||||
@@ -321,7 +341,7 @@ mod imp {
|
|||||||
.product_name
|
.product_name
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| file_name.clone()),
|
.unwrap_or_else(|| file_name.clone()),
|
||||||
exec = exec,
|
qualified_exec = qualified_exec,
|
||||||
mime_type = mime_type
|
mime_type = mime_type
|
||||||
)
|
)
|
||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
@@ -330,11 +350,15 @@ mod imp {
|
|||||||
|
|
||||||
Command::new("update-desktop-database")
|
Command::new("update-desktop-database")
|
||||||
.arg(target)
|
.arg(target)
|
||||||
.status()?;
|
.status()
|
||||||
|
.inspect_err(crate::error::inspect_command_error(
|
||||||
|
"update-desktop-database",
|
||||||
|
))?;
|
||||||
|
|
||||||
Command::new("xdg-mime")
|
Command::new("xdg-mime")
|
||||||
.args(["default", &file_name, _protocol.as_ref()])
|
.args(["default", &file_name, mime_type.as_str()])
|
||||||
.status()?;
|
.status()
|
||||||
|
.inspect_err(crate::error::inspect_command_error("xdg-mime"))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -403,6 +427,7 @@ mod imp {
|
|||||||
///
|
///
|
||||||
/// ## Platform-specific:
|
/// ## Platform-specific:
|
||||||
///
|
///
|
||||||
|
/// - **Linux**: Needs the `xdg-mime` command available on the system.
|
||||||
/// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`).
|
/// - **macOS / Android / iOS**: Unsupported, will return [`Error::UnsupportedPlatform`](`crate::Error::UnsupportedPlatform`).
|
||||||
pub fn is_registered<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<bool> {
|
pub fn is_registered<S: AsRef<str>>(&self, _protocol: S) -> crate::Result<bool> {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -437,7 +462,8 @@ mod imp {
|
|||||||
"default",
|
"default",
|
||||||
&format!("x-scheme-handler/{}", _protocol.as_ref()),
|
&format!("x-scheme-handler/{}", _protocol.as_ref()),
|
||||||
])
|
])
|
||||||
.output()?;
|
.output()
|
||||||
|
.inspect_err(crate::error::inspect_command_error("xdg-mime"))?;
|
||||||
|
|
||||||
Ok(String::from_utf8_lossy(&output.stdout).contains(&file_name))
|
Ok(String::from_utf8_lossy(&output.stdout).contains(&file_name))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name={name}
|
Name={name}
|
||||||
Exec={exec} %u
|
Exec={qualified_exec}
|
||||||
Terminal=false
|
Terminal=false
|
||||||
MimeType={mime_type}
|
MimeType={mime_type}
|
||||||
NoDisplay=true
|
NoDisplay=true
|
||||||
|
|||||||
@@ -1,5 +1,51 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.6.0]
|
||||||
|
|
||||||
|
- [`d7a0bb32`](https://github.com/tauri-apps/plugins-workspace/commit/d7a0bb325dad919d6cc132eb3898c33540de77c4) ([#3136](https://github.com/tauri-apps/plugins-workspace/pull/3136) by [@onehumandev](https://github.com/tauri-apps/plugins-workspace/../../onehumandev)) Add `fileAccessMode` option to file picker.
|
||||||
|
|
||||||
|
## \[2.5.0]
|
||||||
|
|
||||||
|
- [`dff6fa98`](https://github.com/tauri-apps/plugins-workspace/commit/dff6fa986a9a05ba98b6ca660fea78ae97251fc2) ([#3034](https://github.com/tauri-apps/plugins-workspace/pull/3034) by [@onehumandev](https://github.com/tauri-apps/plugins-workspace/../../onehumandev)) Add `pickerMode` option to file picker (currently only used on iOS)
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `fs-js@2.4.5`
|
||||||
|
|
||||||
|
### feat
|
||||||
|
|
||||||
|
- [`c23fa03f`](https://github.com/tauri-apps/plugins-workspace/commit/c23fa03f07d5c1c220bcf0bca482364513e3f754) ([#3098](https://github.com/tauri-apps/plugins-workspace/pull/3098) by [@Lepidopteran](https://github.com/tauri-apps/plugins-workspace/../../Lepidopteran)) Add `xdg-portal` as an optional feature for `rfd`
|
||||||
|
|
||||||
|
## \[2.4.2]
|
||||||
|
|
||||||
|
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `fs-js@2.4.4`
|
||||||
|
|
||||||
|
## \[2.4.1]
|
||||||
|
|
||||||
|
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `fs-js@2.4.3`
|
||||||
|
|
||||||
|
## \[2.4.0]
|
||||||
|
|
||||||
|
- [`509eba8d`](https://github.com/tauri-apps/plugins-workspace/commit/509eba8d441c4f6ecf0af77b572cb2afd69a752d) ([#2641](https://github.com/tauri-apps/plugins-workspace/pull/2641) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Add support for showing a message dialog with 3 buttons.
|
||||||
|
|
||||||
|
## \[2.3.3]
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- Upgraded to `fs-js@2.4.2`
|
||||||
|
|
||||||
|
## \[2.3.2]
|
||||||
|
|
||||||
|
- [`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]
|
## \[2.3.1]
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-dialog"
|
name = "tauri-plugin-dialog"
|
||||||
version = "2.3.1"
|
version = "2.6.0"
|
||||||
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
|
description = "Native system dialogs for opening and saving files along with message dialogs on your Tauri application."
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
@@ -9,9 +9,12 @@ rust-version = { workspace = true }
|
|||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
links = "tauri-plugin-dialog"
|
links = "tauri-plugin-dialog"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["gtk3"]
|
||||||
|
xdg-portal = ["rfd/xdg-portal", "rfd/tokio", "rfd/wayland"]
|
||||||
|
gtk3 = ["rfd/gtk3"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
rustc-args = ["--cfg", "docsrs"]
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"]
|
targets = ["x86_64-unknown-linux-gnu", "x86_64-linux-android"]
|
||||||
|
|
||||||
[package.metadata.platforms.support]
|
[package.metadata.platforms.support]
|
||||||
@@ -34,15 +37,14 @@ tauri = { workspace = true }
|
|||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
tauri-plugin-fs = { path = "../fs", version = "2.4.1" }
|
tauri-plugin-fs = { path = "../fs", version = "2.4.5" }
|
||||||
|
|
||||||
[target.'cfg(target_os = "ios")'.dependencies]
|
[target.'cfg(target_os = "ios")'.dependencies]
|
||||||
tauri = { workspace = true, features = ["wry"] }
|
tauri = { workspace = true, features = ["wry"] }
|
||||||
|
|
||||||
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
|
||||||
rfd = { version = "0.15", default-features = false, features = [
|
rfd = { version = "0.16", default-features = false, features = [
|
||||||
"tokio",
|
|
||||||
"gtk3",
|
|
||||||
"common-controls-v6",
|
"common-controls-v6",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
raw-window-handle = "0.6"
|
raw-window-handle = "0.6"
|
||||||
|
|||||||
@@ -31,9 +31,25 @@ tauri-plugin-dialog = "2.0.0"
|
|||||||
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
|
||||||
```
|
```
|
||||||
|
|
||||||
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
### Linux XDG Desktop Portal Support
|
||||||
|
|
||||||
> 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.
|
By default, this plugin uses gtk to show dialogs, however since `v2.5.0` you can switch to using [XDG Desktop Portal](https://flatpak.github.io/xdg-desktop-portal/) by adding the following to your `Cargo.toml` file:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
tauri-plugin-dialog = { version = "2.5.0", default-features = false, features = ["xdg-portal"] }
|
||||||
|
# alternatively with Git:
|
||||||
|
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2", default-features = false, features = ["xdg-portal"] }
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Do note if you use the `xdg-portal` feature, you need to ensure that [`zenity`](https://gitlab.gnome.org/GNOME/zenity) and an [XDG Desktop Portal backend](https://flatpak.github.io/xdg-desktop-portal#using-portals) is installed with your program.
|
||||||
|
|
||||||
|
For more information, see [XDG Desktop Portal documentation](https://flatpak.github.io/xdg-desktop-portal/) and [`rfd` documentation](https://docs.rs/rfd/latest/rfd#xdg-desktop-portal-backend).
|
||||||
|
|
||||||
|
### JavaScript
|
||||||
|
|
||||||
|
You can install the JavaScript Guest bindings using your preferred JavaScript package manager:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pnpm add @tauri-apps/plugin-dialog
|
pnpm add @tauri-apps/plugin-dialog
|
||||||
@@ -41,13 +57,6 @@ pnpm add @tauri-apps/plugin-dialog
|
|||||||
npm add @tauri-apps/plugin-dialog
|
npm add @tauri-apps/plugin-dialog
|
||||||
# or
|
# or
|
||||||
yarn add @tauri-apps/plugin-dialog
|
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
|
## Usage
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.tauri.dialog"
|
namespace = "app.tauri.dialog"
|
||||||
compileSdk = 34
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class Filter {
|
|||||||
class FilePickerOptions {
|
class FilePickerOptions {
|
||||||
lateinit var filters: Array<Filter>
|
lateinit var filters: Array<Filter>
|
||||||
var multiple: Boolean? = null
|
var multiple: Boolean? = null
|
||||||
|
var pickerMode: String? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@InvokeArg
|
@InvokeArg
|
||||||
@@ -38,6 +39,7 @@ class MessageOptions {
|
|||||||
var title: String? = null
|
var title: String? = null
|
||||||
lateinit var message: String
|
lateinit var message: String
|
||||||
var okButtonLabel: String? = null
|
var okButtonLabel: String? = null
|
||||||
|
var noButtonLabel: String? = null
|
||||||
var cancelButtonLabel: String? = null
|
var cancelButtonLabel: String? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,20 +58,27 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
|
|||||||
try {
|
try {
|
||||||
val args = invoke.parseArgs(FilePickerOptions::class.java)
|
val args = invoke.parseArgs(FilePickerOptions::class.java)
|
||||||
val parsedTypes = parseFiltersOption(args.filters)
|
val parsedTypes = parseFiltersOption(args.filters)
|
||||||
|
|
||||||
val intent = if (parsedTypes.isNotEmpty()) {
|
// TODO: ACTION_OPEN_DOCUMENT ??
|
||||||
val intent = Intent(Intent.ACTION_PICK)
|
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||||
setIntentMimeTypes(intent, parsedTypes)
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
intent
|
|
||||||
} else {
|
if (args.pickerMode == "image") {
|
||||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
intent.type = "image/*"
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
} else if (args.pickerMode == "video") {
|
||||||
|
intent.type = "video/*"
|
||||||
|
} else if (args.pickerMode == "media") {
|
||||||
|
intent.type = "*/*"
|
||||||
|
intent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("video/*", "image/*"))
|
||||||
|
} else if (parsedTypes.isNotEmpty()) {
|
||||||
|
intent.type = "*/*"
|
||||||
|
intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes)
|
||||||
|
} else {
|
||||||
intent.type = "*/*"
|
intent.type = "*/*"
|
||||||
intent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, args.multiple ?: false)
|
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, args.multiple ?: false)
|
||||||
|
|
||||||
startActivityForResult(invoke, intent, "filePickerResult")
|
startActivityForResult(invoke, intent, "filePickerResult")
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
val message = ex.message ?: "Failed to pick file"
|
val message = ex.message ?: "Failed to pick file"
|
||||||
@@ -115,7 +124,7 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
|
|||||||
callResult.put("files", JSArray.from(uris.toTypedArray()))
|
callResult.put("files", JSArray.from(uris.toTypedArray()))
|
||||||
return callResult
|
return callResult
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseFiltersOption(filters: Array<Filter>): Array<String> {
|
private fun parseFiltersOption(filters: Array<Filter>): Array<String> {
|
||||||
val mimeTypes = mutableListOf<String>()
|
val mimeTypes = mutableListOf<String>()
|
||||||
for (filter in filters) {
|
for (filter in filters) {
|
||||||
@@ -132,46 +141,17 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
|
|||||||
return mimeTypes.toTypedArray()
|
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
|
@Command
|
||||||
fun showMessageDialog(invoke: Invoke) {
|
fun showMessageDialog(invoke: Invoke) {
|
||||||
val args = invoke.parseArgs(MessageOptions::class.java)
|
val args = invoke.parseArgs(MessageOptions::class.java)
|
||||||
|
|
||||||
if (activity.isFinishing) {
|
if (activity.isFinishing) {
|
||||||
invoke.reject("App is finishing")
|
invoke.reject("App is finishing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val handler = { cancelled: Boolean, value: Boolean ->
|
val handler = { value: String ->
|
||||||
val ret = JSObject()
|
val ret = JSObject()
|
||||||
ret.put("cancelled", cancelled)
|
|
||||||
ret.put("value", value)
|
ret.put("value", value)
|
||||||
invoke.resolve(ret)
|
invoke.resolve(ret)
|
||||||
}
|
}
|
||||||
@@ -179,28 +159,38 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
|
|||||||
Handler(Looper.getMainLooper())
|
Handler(Looper.getMainLooper())
|
||||||
.post {
|
.post {
|
||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
|
|
||||||
if (args.title != null) {
|
if (args.title != null) {
|
||||||
builder.setTitle(args.title)
|
builder.setTitle(args.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val okButtonLabel = args.okButtonLabel ?: "Ok"
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.setMessage(args.message)
|
.setMessage(args.message)
|
||||||
.setPositiveButton(
|
.setPositiveButton(okButtonLabel) { dialog, _ ->
|
||||||
args.okButtonLabel ?: "OK"
|
|
||||||
) { dialog, _ ->
|
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
handler(false, true)
|
handler(okButtonLabel)
|
||||||
}
|
}
|
||||||
.setOnCancelListener { dialog ->
|
.setOnCancelListener { dialog ->
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
handler(true, false)
|
handler(args.cancelButtonLabel ?: "Cancel")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.noButtonLabel != null) {
|
||||||
|
builder.setNeutralButton(args.noButtonLabel) { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
handler(args.noButtonLabel!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (args.cancelButtonLabel != null) {
|
if (args.cancelButtonLabel != null) {
|
||||||
builder.setNegativeButton( args.cancelButtonLabel) { dialog, _ ->
|
builder.setNegativeButton( args.cancelButtonLabel) { dialog, _ ->
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
handler(false, false)
|
handler(args.cancelButtonLabel!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
dialog.show()
|
dialog.show()
|
||||||
}
|
}
|
||||||
@@ -213,10 +203,14 @@ class DialogPlugin(private val activity: Activity): Plugin(activity) {
|
|||||||
val parsedTypes = parseFiltersOption(args.filters)
|
val parsedTypes = parseFiltersOption(args.filters)
|
||||||
|
|
||||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||||
setIntentMimeTypes(intent, parsedTypes)
|
|
||||||
|
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
intent.putExtra(Intent.EXTRA_TITLE, args.fileName ?: "")
|
intent.putExtra(Intent.EXTRA_TITLE, args.fileName ?: "")
|
||||||
|
intent.type = "*/*"
|
||||||
|
|
||||||
|
if (parsedTypes.isNotEmpty()) {
|
||||||
|
intent.putExtra(Intent.EXTRA_MIME_TYPES, parsedTypes)
|
||||||
|
}
|
||||||
|
|
||||||
startActivityForResult(invoke, intent, "saveFileDialogResult")
|
startActivityForResult(invoke, intent, "saveFileDialogResult")
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
val message = ex.message ?: "Failed to pick save file"
|
val message = ex.message ?: "Failed to pick save file"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
if("__TAURI__"in window){var __TAURI_PLUGIN_DIALOG__=function(t){"use strict";async function n(t,n={},e){return window.__TAURI_INTERNALS__.invoke(t,n,e)}return"function"==typeof SuppressedError&&SuppressedError,t.ask=async function(t,e){const i="string"==typeof e?{title:e}:e;return await n("plugin:dialog|ask",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,yesButtonLabel:i?.okLabel?.toString(),noButtonLabel:i?.cancelLabel?.toString()})},t.confirm=async function(t,e){const i="string"==typeof e?{title:e}:e;return await n("plugin:dialog|confirm",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString(),cancelButtonLabel:i?.cancelLabel?.toString()})},t.message=async function(t,e){const i="string"==typeof e?{title:e}:e;await n("plugin:dialog|message",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString()})},t.open=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|open",{options:t})},t.save=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|save",{options:t})},t}({});Object.defineProperty(window.__TAURI__,"dialog",{value:__TAURI_PLUGIN_DIALOG__})}
|
if("__TAURI__"in window){var __TAURI_PLUGIN_DIALOG__=function(t){"use strict";async function n(t,n={},e){return window.__TAURI_INTERNALS__.invoke(t,n,e)}function e(t){if(void 0!==t)return"string"==typeof t?t:"ok"in t&&"cancel"in t?{OkCancelCustom:[t.ok,t.cancel]}:"yes"in t&&"no"in t&&"cancel"in t?{YesNoCancelCustom:[t.yes,t.no,t.cancel]}:"ok"in t?{OkCustom:t.ok}:void 0}return"function"==typeof SuppressedError&&SuppressedError,t.ask=async function(t,e){const o="string"==typeof e?{title:e}:e;return await n("plugin:dialog|ask",{message:t.toString(),title:o?.title?.toString(),kind:o?.kind,yesButtonLabel:o?.okLabel?.toString(),noButtonLabel:o?.cancelLabel?.toString()})},t.confirm=async function(t,e){const o="string"==typeof e?{title:e}:e;return await n("plugin:dialog|confirm",{message:t.toString(),title:o?.title?.toString(),kind:o?.kind,okButtonLabel:o?.okLabel?.toString(),cancelButtonLabel:o?.cancelLabel?.toString()})},t.message=async function(t,o){const i="string"==typeof o?{title:o}:o;return n("plugin:dialog|message",{message:t.toString(),title:i?.title?.toString(),kind:i?.kind,okButtonLabel:i?.okLabel?.toString(),buttons:e(i?.buttons)})},t.open=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|open",{options:t})},t.save=async function(t={}){return"object"==typeof t&&Object.freeze(t),await n("plugin:dialog|save",{options:t})},t}({});Object.defineProperty(window.__TAURI__,"dialog",{value:__TAURI_PLUGIN_DIALOG__})}
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ interface DialogFilter {
|
|||||||
name: string
|
name: string
|
||||||
/**
|
/**
|
||||||
* Extensions to filter, without a `.` prefix.
|
* Extensions to filter, without a `.` prefix.
|
||||||
|
*
|
||||||
|
* **Note:** Mobile platforms have different APIs for filtering that may not support extensions.
|
||||||
|
* iOS: Extensions are supported in the document picker, but not in the media picker.
|
||||||
|
* Android: Extensions are not supported.
|
||||||
|
*
|
||||||
|
* For these platforms, MIME types are the primary way to filter files, as opposed to extensions.
|
||||||
|
* This means the string values here labeled as `extensions` may also be a MIME type.
|
||||||
|
* This property name of `extensions` is being kept for backwards compatibility, but this may be revisited to
|
||||||
|
* specify the difference between extension or MIME type filtering.
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* extensions: ['svg', 'png']
|
* extensions: ['svg', 'png']
|
||||||
@@ -30,7 +40,14 @@ interface DialogFilter {
|
|||||||
interface OpenDialogOptions {
|
interface OpenDialogOptions {
|
||||||
/** The title of the dialog window (desktop only). */
|
/** The title of the dialog window (desktop only). */
|
||||||
title?: string
|
title?: string
|
||||||
/** The filters of the dialog. */
|
/**
|
||||||
|
* The filters of the dialog.
|
||||||
|
* On mobile platforms, if either:
|
||||||
|
* A) the {@linkcode pickerMode} is set to `media`, `image`, or `video`
|
||||||
|
* -- or --
|
||||||
|
* B) the filters include **only** either image or video mime types, the media picker will be displayed.
|
||||||
|
* Otherwise, the document picker will be displayed.
|
||||||
|
*/
|
||||||
filters?: DialogFilter[]
|
filters?: DialogFilter[]
|
||||||
/**
|
/**
|
||||||
* Initial directory or file path.
|
* Initial directory or file path.
|
||||||
@@ -52,6 +69,38 @@ interface OpenDialogOptions {
|
|||||||
recursive?: boolean
|
recursive?: boolean
|
||||||
/** Whether to allow creating directories in the dialog. Enabled by default. **macOS Only** */
|
/** Whether to allow creating directories in the dialog. Enabled by default. **macOS Only** */
|
||||||
canCreateDirectories?: boolean
|
canCreateDirectories?: boolean
|
||||||
|
/**
|
||||||
|
* The preferred mode of the dialog.
|
||||||
|
* This is meant for mobile platforms (iOS and Android) which have distinct file and media pickers.
|
||||||
|
* If not provided, the dialog will automatically choose the best mode based on the MIME types or extensions of the {@linkcode filters}.
|
||||||
|
* On desktop, this option is ignored.
|
||||||
|
*/
|
||||||
|
pickerMode?: PickerMode
|
||||||
|
/**
|
||||||
|
* The file access mode of the dialog.
|
||||||
|
* If not provided, `copy` is used, which matches the behavior of the {@linkcode open} method before the introduction of this option.
|
||||||
|
*
|
||||||
|
* **Usage**
|
||||||
|
* If a file is opened with {@linkcode fileAccessMode: 'copy'}, it will be copied to the app's sandbox.
|
||||||
|
* This means the file can be read, edited, deleted, copied, or any other operation without any issues, since the file
|
||||||
|
* now belongs to the app.
|
||||||
|
* This also means that the caller has responsibility of deleting the file if this file is not meant to be retained
|
||||||
|
* in the app sandbox.
|
||||||
|
*
|
||||||
|
* If a file is opened with {@linkcode fileAccessMode: 'scoped'}, the file will remain in its original location
|
||||||
|
* and security-scoped access will be automatically managed by the system.
|
||||||
|
*
|
||||||
|
* **Note**
|
||||||
|
* This is specifically meant for document pickers on iOS or MacOS, in conjunction with [security scoped resources](https://developer.apple.com/documentation/foundation/nsurl/startaccessingsecurityscopedresource()).
|
||||||
|
*
|
||||||
|
* Why only document pickers, and not image or video pickers?
|
||||||
|
* The image and video pickers on iOS behave differently from the document pickers, and return [NSItemProvider](https://developer.apple.com/documentation/foundation/nsitemprovider) objects instead of file URLs.
|
||||||
|
* These are meant to be ephemeral (only available within the callback of the picker), and are not accessible outside of the callback.
|
||||||
|
* So for image and video pickers, the only way to access the file is to copy it to the app's sandbox, and this is the URL that is returned from this API.
|
||||||
|
* This means there is no provision for using `scoped` mode with image or video pickers.
|
||||||
|
* If an image or video picker is used, `copy` is always used.
|
||||||
|
*/
|
||||||
|
fileAccessMode?: FileAccessMode
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,6 +126,100 @@ interface SaveDialogOptions {
|
|||||||
canCreateDirectories?: boolean
|
canCreateDirectories?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The preferred mode of the dialog.
|
||||||
|
* This is meant for mobile platforms (iOS and Android) which have distinct file and media pickers.
|
||||||
|
* On desktop, this option is ignored.
|
||||||
|
* If not provided, the dialog will automatically choose the best mode based on the MIME types or extensions of the {@linkcode filters}.
|
||||||
|
*
|
||||||
|
* **Note:** This option is only supported on iOS 14 and above. This parameter is ignored on iOS 13 and below.
|
||||||
|
*/
|
||||||
|
export type PickerMode = 'document' | 'media' | 'image' | 'video'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file access mode of the dialog.
|
||||||
|
*
|
||||||
|
* - `copy`: copy/move the picked file to the app sandbox; no scoped access required.
|
||||||
|
* - `scoped`: keep file in place; security-scoped access is automatically managed.
|
||||||
|
*
|
||||||
|
* **Note:** This option is only supported on iOS 14 and above. This parameter is ignored on iOS 13 and below.
|
||||||
|
*/
|
||||||
|
export type FileAccessMode = 'copy' | 'scoped'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default buttons for a message dialog.
|
||||||
|
*
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
export type MessageDialogDefaultButtons =
|
||||||
|
| 'Ok'
|
||||||
|
| 'OkCancel'
|
||||||
|
| 'YesNo'
|
||||||
|
| 'YesNoCancel'
|
||||||
|
|
||||||
|
/** All possible button keys. */
|
||||||
|
type ButtonKey = 'ok' | 'cancel' | 'yes' | 'no'
|
||||||
|
|
||||||
|
/** Ban everything except a set of keys. */
|
||||||
|
type BanExcept<Allowed extends ButtonKey> = Partial<
|
||||||
|
Record<Exclude<ButtonKey, Allowed>, never>
|
||||||
|
>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Yes, No and Cancel buttons of a message dialog.
|
||||||
|
*
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
export type MessageDialogButtonsYesNoCancel = {
|
||||||
|
/** The Yes button. */
|
||||||
|
yes: string
|
||||||
|
/** The No button. */
|
||||||
|
no: string
|
||||||
|
/** The Cancel button. */
|
||||||
|
cancel: string
|
||||||
|
} & BanExcept<'yes' | 'no' | 'cancel'>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Ok and Cancel buttons of a message dialog.
|
||||||
|
*
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
export type MessageDialogButtonsOkCancel = {
|
||||||
|
/** The Ok button. */
|
||||||
|
ok: string
|
||||||
|
/** The Cancel button. */
|
||||||
|
cancel: string
|
||||||
|
} & BanExcept<'ok' | 'cancel'>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Ok button of a message dialog.
|
||||||
|
*
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
export type MessageDialogButtonsOk = {
|
||||||
|
/** The Ok button. */
|
||||||
|
ok: string
|
||||||
|
} & BanExcept<'ok'>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom buttons for a message dialog.
|
||||||
|
*
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
export type MessageDialogCustomButtons =
|
||||||
|
| MessageDialogButtonsYesNoCancel
|
||||||
|
| MessageDialogButtonsOkCancel
|
||||||
|
| MessageDialogButtonsOk
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The buttons of a message dialog.
|
||||||
|
*
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
export type MessageDialogButtons =
|
||||||
|
| MessageDialogDefaultButtons
|
||||||
|
| MessageDialogCustomButtons
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@@ -85,8 +228,58 @@ interface MessageDialogOptions {
|
|||||||
title?: string
|
title?: string
|
||||||
/** The kind of the dialog. Defaults to `info`. */
|
/** The kind of the dialog. Defaults to `info`. */
|
||||||
kind?: 'info' | 'warning' | 'error'
|
kind?: 'info' | 'warning' | 'error'
|
||||||
/** The label of the confirm button. */
|
/**
|
||||||
|
* The label of the Ok button.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@linkcode MessageDialogOptions.buttons} instead.
|
||||||
|
*/
|
||||||
okLabel?: string
|
okLabel?: string
|
||||||
|
/**
|
||||||
|
* The buttons of the dialog.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* // Use system default buttons texts
|
||||||
|
* await message('Hello World!', { buttons: 'Ok' })
|
||||||
|
* await message('Hello World!', { buttons: 'OkCancel' })
|
||||||
|
*
|
||||||
|
* // Or with custom button texts
|
||||||
|
* await message('Hello World!', { buttons: { ok: 'Yes!' } })
|
||||||
|
* await message('Take on the task?', {
|
||||||
|
* buttons: { ok: 'Accept', cancel: 'Cancel' }
|
||||||
|
* })
|
||||||
|
* await message('Show the file content?', {
|
||||||
|
* buttons: { yes: 'Show content', no: 'Show in folder', cancel: 'Cancel' }
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
buttons?: MessageDialogButtons
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal function to convert the buttons to the Rust type.
|
||||||
|
*/
|
||||||
|
function buttonsToRust(buttons: MessageDialogButtons | undefined) {
|
||||||
|
if (buttons === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof buttons === 'string') {
|
||||||
|
return buttons
|
||||||
|
} else if ('ok' in buttons && 'cancel' in buttons) {
|
||||||
|
return { OkCancelCustom: [buttons.ok, buttons.cancel] }
|
||||||
|
} else if ('yes' in buttons && 'no' in buttons && 'cancel' in buttons) {
|
||||||
|
return {
|
||||||
|
YesNoCancelCustom: [buttons.yes, buttons.no, buttons.cancel]
|
||||||
|
}
|
||||||
|
} else if ('ok' in buttons) {
|
||||||
|
return { OkCustom: buttons.ok }
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConfirmDialogOptions {
|
interface ConfirmDialogOptions {
|
||||||
@@ -202,6 +395,16 @@ async function save(options: SaveDialogOptions = {}): Promise<string | null> {
|
|||||||
return await invoke('plugin:dialog|save', { options })
|
return await invoke('plugin:dialog|save', { options })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The result of a message dialog.
|
||||||
|
*
|
||||||
|
* The result is a string if the dialog has custom buttons,
|
||||||
|
* otherwise it is one of the default buttons.
|
||||||
|
*
|
||||||
|
* @since 2.4.0
|
||||||
|
*/
|
||||||
|
export type MessageDialogResult = 'Yes' | 'No' | 'Ok' | 'Cancel' | (string & {})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a message dialog with an `Ok` button.
|
* Shows a message dialog with an `Ok` button.
|
||||||
* @example
|
* @example
|
||||||
@@ -222,13 +425,15 @@ async function save(options: SaveDialogOptions = {}): Promise<string | null> {
|
|||||||
async function message(
|
async function message(
|
||||||
message: string,
|
message: string,
|
||||||
options?: string | MessageDialogOptions
|
options?: string | MessageDialogOptions
|
||||||
): Promise<void> {
|
): Promise<MessageDialogResult> {
|
||||||
const opts = typeof options === 'string' ? { title: options } : options
|
const opts = typeof options === 'string' ? { title: options } : options
|
||||||
await invoke('plugin:dialog|message', {
|
|
||||||
|
return invoke<MessageDialogResult>('plugin:dialog|message', {
|
||||||
message: message.toString(),
|
message: message.toString(),
|
||||||
title: opts?.title?.toString(),
|
title: opts?.title?.toString(),
|
||||||
kind: opts?.kind,
|
kind: opts?.kind,
|
||||||
okButtonLabel: opts?.okLabel?.toString()
|
okButtonLabel: opts?.okLabel?.toString(),
|
||||||
|
buttons: buttonsToRust(opts?.buttons)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import PhotosUI
|
|||||||
import SwiftRs
|
import SwiftRs
|
||||||
import Tauri
|
import Tauri
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import UniformTypeIdentifiers
|
||||||
import WebKit
|
import WebKit
|
||||||
|
|
||||||
enum FilePickerEvent {
|
enum FilePickerEvent {
|
||||||
@@ -20,6 +21,7 @@ struct MessageDialogOptions: Decodable {
|
|||||||
var title: String?
|
var title: String?
|
||||||
let message: String
|
let message: String
|
||||||
var okButtonLabel: String?
|
var okButtonLabel: String?
|
||||||
|
var noButtonLabel: String?
|
||||||
var cancelButtonLabel: String?
|
var cancelButtonLabel: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,6 +33,8 @@ struct FilePickerOptions: Decodable {
|
|||||||
var multiple: Bool?
|
var multiple: Bool?
|
||||||
var filters: [Filter]?
|
var filters: [Filter]?
|
||||||
var defaultPath: String?
|
var defaultPath: String?
|
||||||
|
var pickerMode: PickerMode?
|
||||||
|
var fileAccessMode: FileAccessMode?
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SaveFileDialogOptions: Decodable {
|
struct SaveFileDialogOptions: Decodable {
|
||||||
@@ -38,6 +42,18 @@ struct SaveFileDialogOptions: Decodable {
|
|||||||
var defaultPath: String?
|
var defaultPath: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum FileAccessMode: String, Decodable {
|
||||||
|
case copy
|
||||||
|
case scoped
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PickerMode: String, Decodable {
|
||||||
|
case document
|
||||||
|
case media
|
||||||
|
case image
|
||||||
|
case video
|
||||||
|
}
|
||||||
|
|
||||||
class DialogPlugin: Plugin {
|
class DialogPlugin: Plugin {
|
||||||
|
|
||||||
var filePickerController: FilePickerController!
|
var filePickerController: FilePickerController!
|
||||||
@@ -46,31 +62,12 @@ class DialogPlugin: Plugin {
|
|||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
filePickerController = FilePickerController(self)
|
filePickerController = FilePickerController(self)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public func showFilePicker(_ invoke: Invoke) throws {
|
@objc public func showFilePicker(_ invoke: Invoke) throws {
|
||||||
let args = try invoke.parseArgs(FilePickerOptions.self)
|
let args = try invoke.parseArgs(FilePickerOptions.self)
|
||||||
|
|
||||||
let parsedTypes = parseFiltersOption(args.filters ?? [])
|
|
||||||
|
|
||||||
var isMedia = !parsedTypes.isEmpty
|
|
||||||
var uniqueMimeType: Bool? = nil
|
|
||||||
var mimeKind: String? = nil
|
|
||||||
if !parsedTypes.isEmpty {
|
|
||||||
uniqueMimeType = true
|
|
||||||
for mime in parsedTypes {
|
|
||||||
let kind = mime.components(separatedBy: "/")[0]
|
|
||||||
if kind != "image" && kind != "video" {
|
|
||||||
isMedia = false
|
|
||||||
}
|
|
||||||
if mimeKind == nil {
|
|
||||||
mimeKind = kind
|
|
||||||
} else if mimeKind != kind {
|
|
||||||
uniqueMimeType = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onFilePickerResult = { (event: FilePickerEvent) -> Void in
|
onFilePickerResult = { (event: FilePickerEvent) -> Void in
|
||||||
switch event {
|
switch event {
|
||||||
case .selected(let urls):
|
case .selected(let urls):
|
||||||
@@ -82,49 +79,59 @@ class DialogPlugin: Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if uniqueMimeType == true || isMedia {
|
if #available(iOS 14, *) {
|
||||||
DispatchQueue.main.async {
|
let parsedTypes = parseFiltersOption(args.filters ?? [])
|
||||||
if #available(iOS 14, *) {
|
|
||||||
|
let mimeKinds = Set(
|
||||||
|
parsedTypes.compactMap { $0.preferredMIMEType?.components(separatedBy: "/")[0] })
|
||||||
|
let filtersIncludeImage = mimeKinds.contains("image")
|
||||||
|
let filtersIncludeVideo = mimeKinds.contains("video")
|
||||||
|
let filtersIncludeNonMedia = mimeKinds.contains(where: { $0 != "image" && $0 != "video" })
|
||||||
|
|
||||||
|
// If the picker mode is media, images, or videos, we always want to show the media picker regardless of what's in the filters.
|
||||||
|
// Otherwise, if the filters A) do not include non-media types and B) include either image or video, we want to show the media picker.
|
||||||
|
if args.pickerMode == .media
|
||||||
|
|| args.pickerMode == .image
|
||||||
|
|| args.pickerMode == .video
|
||||||
|
|| (!filtersIncludeNonMedia && (filtersIncludeImage || filtersIncludeVideo))
|
||||||
|
{
|
||||||
|
DispatchQueue.main.async {
|
||||||
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
|
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
|
||||||
configuration.selectionLimit = (args.multiple ?? false) ? 0 : 1
|
configuration.selectionLimit = (args.multiple ?? false) ? 0 : 1
|
||||||
|
|
||||||
if uniqueMimeType == true {
|
// If the filters include image or video, use the appropriate filter.
|
||||||
if mimeKind == "image" {
|
// If both are true, don't define a filter, which means we will display all media.
|
||||||
configuration.filter = .images
|
if args.pickerMode == .image || (filtersIncludeImage && !filtersIncludeVideo) {
|
||||||
} else if mimeKind == "video" {
|
configuration.filter = .images
|
||||||
configuration.filter = .videos
|
} else if args.pickerMode == .video || (filtersIncludeVideo && !filtersIncludeImage) {
|
||||||
}
|
configuration.filter = .videos
|
||||||
}
|
}
|
||||||
|
|
||||||
let picker = PHPickerViewController(configuration: configuration)
|
let picker = PHPickerViewController(configuration: configuration)
|
||||||
picker.delegate = self.filePickerController
|
picker.delegate = self.filePickerController
|
||||||
picker.modalPresentationStyle = .fullScreen
|
picker.modalPresentationStyle = .fullScreen
|
||||||
self.presentViewController(picker)
|
self.presentViewController(picker)
|
||||||
} else {
|
}
|
||||||
let picker = UIImagePickerController()
|
} else {
|
||||||
picker.delegate = self.filePickerController
|
DispatchQueue.main.async {
|
||||||
|
// The UTType.item is the catch-all, allowing for any file type to be selected.
|
||||||
|
let contentTypes = parsedTypes.isEmpty ? [UTType.item] : parsedTypes
|
||||||
|
let picker: UIDocumentPickerViewController = UIDocumentPickerViewController(
|
||||||
|
forOpeningContentTypes: contentTypes,
|
||||||
|
asCopy: args.fileAccessMode == .scoped ? false : true)
|
||||||
|
|
||||||
if uniqueMimeType == true && mimeKind == "image" {
|
if let defaultPath = args.defaultPath {
|
||||||
picker.sourceType = .photoLibrary
|
picker.directoryURL = URL(string: defaultPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
picker.sourceType = .photoLibrary
|
picker.delegate = self.filePickerController
|
||||||
|
picker.allowsMultipleSelection = args.multiple ?? false
|
||||||
picker.modalPresentationStyle = .fullScreen
|
picker.modalPresentationStyle = .fullScreen
|
||||||
self.presentViewController(picker)
|
self.presentViewController(picker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let documentTypes = parsedTypes.isEmpty ? ["public.data"] : parsedTypes
|
showFilePickerLegacy(args: args)
|
||||||
DispatchQueue.main.async {
|
|
||||||
let picker = UIDocumentPickerViewController(documentTypes: documentTypes, in: .import)
|
|
||||||
if let defaultPath = args.defaultPath {
|
|
||||||
picker.directoryURL = URL(string: defaultPath)
|
|
||||||
}
|
|
||||||
picker.delegate = self.filePickerController
|
|
||||||
picker.allowsMultipleSelection = args.multiple ?? false
|
|
||||||
picker.modalPresentationStyle = .fullScreen
|
|
||||||
self.presentViewController(picker)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +179,68 @@ class DialogPlugin: Plugin {
|
|||||||
self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil)
|
self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func parseFiltersOption(_ filters: [Filter]) -> [String] {
|
@available(iOS 14, *)
|
||||||
|
private func parseFiltersOption(_ filters: [Filter]) -> [UTType] {
|
||||||
|
var parsedTypes: [UTType] = []
|
||||||
|
for filter in filters {
|
||||||
|
for ext in filter.extensions ?? [] {
|
||||||
|
// We need to support extensions as well as MIME types.
|
||||||
|
if let utType = UTType(mimeType: ext) {
|
||||||
|
parsedTypes.append(utType)
|
||||||
|
} else if let utType = UTType(filenameExtension: ext) {
|
||||||
|
parsedTypes.append(utType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is only used for iOS < 14, and should be removed if/when the deployment target is raised to 14.
|
||||||
|
private func showFilePickerLegacy(args: FilePickerOptions) {
|
||||||
|
let parsedTypes = parseFiltersOptionLegacy(args.filters ?? [])
|
||||||
|
|
||||||
|
var filtersIncludeImage: Bool = false
|
||||||
|
var filtersIncludeVideo: Bool = false
|
||||||
|
var filtersIncludeNonMedia: Bool = false
|
||||||
|
|
||||||
|
if !parsedTypes.isEmpty {
|
||||||
|
let mimeKinds = Set(parsedTypes.map { $0.components(separatedBy: "/")[0] })
|
||||||
|
filtersIncludeImage = mimeKinds.contains("image")
|
||||||
|
filtersIncludeVideo = mimeKinds.contains("video")
|
||||||
|
filtersIncludeNonMedia = mimeKinds.contains(where: { $0 != "image" && $0 != "video" })
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filtersIncludeNonMedia && (filtersIncludeImage || filtersIncludeVideo) {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
let picker = UIImagePickerController()
|
||||||
|
picker.delegate = self.filePickerController
|
||||||
|
|
||||||
|
if filtersIncludeImage && !filtersIncludeVideo {
|
||||||
|
picker.sourceType = .photoLibrary
|
||||||
|
}
|
||||||
|
|
||||||
|
picker.modalPresentationStyle = .fullScreen
|
||||||
|
self.presentViewController(picker)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let documentTypes = parsedTypes.isEmpty ? ["public.data"] : parsedTypes
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
let picker = UIDocumentPickerViewController(documentTypes: documentTypes, in: .import)
|
||||||
|
if let defaultPath = args.defaultPath {
|
||||||
|
picker.directoryURL = URL(string: defaultPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
picker.delegate = self.filePickerController
|
||||||
|
picker.allowsMultipleSelection = args.multiple ?? false
|
||||||
|
picker.modalPresentationStyle = .fullScreen
|
||||||
|
self.presentViewController(picker)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function is only used for iOS < 14, and should be removed if/when the deployment target is raised to 14.
|
||||||
|
private func parseFiltersOptionLegacy(_ filters: [Filter]) -> [String] {
|
||||||
var parsedTypes: [String] = []
|
var parsedTypes: [String] = []
|
||||||
for filter in filters {
|
for filter in filters {
|
||||||
for ext in filter.extensions ?? [] {
|
for ext in filter.extensions ?? [] {
|
||||||
@@ -185,6 +253,7 @@ class DialogPlugin: Plugin {
|
|||||||
parsedTypes.append(utType)
|
parsedTypes.append(utType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsedTypes
|
return parsedTypes
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,39 +269,42 @@ class DialogPlugin: Plugin {
|
|||||||
let alert = UIAlertController(
|
let alert = UIAlertController(
|
||||||
title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert)
|
title: args.title, message: args.message, preferredStyle: UIAlertController.Style.alert)
|
||||||
|
|
||||||
let cancelButtonLabel = args.cancelButtonLabel ?? ""
|
if let cancelButtonLabel = args.cancelButtonLabel {
|
||||||
if !cancelButtonLabel.isEmpty {
|
|
||||||
alert.addAction(
|
alert.addAction(
|
||||||
UIAlertAction(
|
UIAlertAction(
|
||||||
title: cancelButtonLabel, style: UIAlertAction.Style.default,
|
title: cancelButtonLabel, style: UIAlertAction.Style.default,
|
||||||
handler: { (_) -> Void in
|
handler: { (_) -> Void in
|
||||||
Logger.error("cancel")
|
invoke.resolve(["value": cancelButtonLabel])
|
||||||
|
}
|
||||||
invoke.resolve([
|
)
|
||||||
"value": false,
|
)
|
||||||
"cancelled": false,
|
|
||||||
])
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let okButtonLabel = args.okButtonLabel ?? (cancelButtonLabel.isEmpty ? "OK" : "")
|
if let noButtonLabel = args.noButtonLabel {
|
||||||
if !okButtonLabel.isEmpty {
|
|
||||||
alert.addAction(
|
alert.addAction(
|
||||||
UIAlertAction(
|
UIAlertAction(
|
||||||
title: okButtonLabel, style: UIAlertAction.Style.default,
|
title: noButtonLabel, style: UIAlertAction.Style.default,
|
||||||
handler: { (_) -> Void in
|
handler: { (_) -> Void in
|
||||||
Logger.error("ok")
|
invoke.resolve(["value": noButtonLabel])
|
||||||
|
}
|
||||||
invoke.resolve([
|
)
|
||||||
"value": true,
|
)
|
||||||
"cancelled": false,
|
|
||||||
])
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let okButtonLabel = args.okButtonLabel ?? "Ok"
|
||||||
|
alert.addAction(
|
||||||
|
UIAlertAction(
|
||||||
|
title: okButtonLabel, style: UIAlertAction.Style.default,
|
||||||
|
handler: { (_) -> Void in
|
||||||
|
invoke.resolve(["value": okButtonLabel])
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
manager.viewController?.present(alert, animated: true, completion: nil)
|
manager.viewController?.present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@_cdecl("init_plugin_dialog")
|
@_cdecl("init_plugin_dialog")
|
||||||
|
|||||||
@@ -95,16 +95,33 @@ public class FilePickerController: NSObject {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ## In which cases do we need to save a copy of a file selected by a user to the app sandbox?
|
||||||
|
/// In short, only when the file is **not** selected using UIDocumentPickerDelegate.
|
||||||
|
/// For the rest of the cases, we need to write a copy of the file to the app sandbox.
|
||||||
|
///
|
||||||
|
/// For PHPicker (used for photos and videos), `NSItemProvider.loadFileRepresentation` returns a temporary file URL that is deleted after the completion handler.
|
||||||
|
/// The recommendation is to [Persist](https://developer.apple.com/documentation/foundation/nsitemprovider/2888338-loadfilerepresentation) the file by moving/copying
|
||||||
|
/// it to your app’s directory within the completion handler.
|
||||||
|
///
|
||||||
|
/// If available, `loadInPlaceFileRepresentation` can open a file in place; Photos assets typically do not support true in-place access,
|
||||||
|
/// so fall back to persisting a local file.
|
||||||
|
/// Ref: https://developer.apple.com/documentation/foundation/nsitemprovider/2888335-loadinplacefilerepresentation
|
||||||
|
///
|
||||||
|
/// For UIDocumentPicker, prefer "open in place" and avoid copying when possible.
|
||||||
|
/// Ref: https://developer.apple.com/documentation/uikit/uidocumentpickerviewcontroller
|
||||||
private func saveTemporaryFile(_ sourceUrl: URL) throws -> URL {
|
private func saveTemporaryFile(_ sourceUrl: URL) throws -> URL {
|
||||||
|
|
||||||
var directory = URL(fileURLWithPath: NSTemporaryDirectory())
|
var directory = URL(fileURLWithPath: NSTemporaryDirectory())
|
||||||
if let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
|
if let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
|
||||||
directory = cachesDirectory
|
directory = cachesDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
let targetUrl = directory.appendingPathComponent(sourceUrl.lastPathComponent)
|
let targetUrl = directory.appendingPathComponent(sourceUrl.lastPathComponent)
|
||||||
do {
|
do {
|
||||||
try deleteFile(targetUrl)
|
try deleteFile(targetUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
try FileManager.default.copyItem(at: sourceUrl, to: targetUrl)
|
try FileManager.default.copyItem(at: sourceUrl, to: targetUrl)
|
||||||
return targetUrl
|
return targetUrl
|
||||||
}
|
}
|
||||||
@@ -119,8 +136,7 @@ public class FilePickerController: NSObject {
|
|||||||
extension FilePickerController: UIDocumentPickerDelegate {
|
extension FilePickerController: UIDocumentPickerDelegate {
|
||||||
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||||
do {
|
do {
|
||||||
let temporaryUrls = try urls.map { try saveTemporaryFile($0) }
|
self.plugin.onFilePickerEvent(.selected(urls))
|
||||||
self.plugin.onFilePickerEvent(.selected(temporaryUrls))
|
|
||||||
} catch {
|
} catch {
|
||||||
self.plugin.onFilePickerEvent(.error("Failed to create a temporary copy of the file"))
|
self.plugin.onFilePickerEvent(.error("Failed to create a temporary copy of the file"))
|
||||||
}
|
}
|
||||||
@@ -191,6 +207,8 @@ extension FilePickerController: PHPickerViewControllerDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
|
// We have to make a copy of the file to the app sandbox here, as PHPicker returns an NSItemProvider with either an ephemeral file URL or content that is deleted after the completion handler.
|
||||||
|
// This is a different behavior from UIDocumentPicker, where the file can either be copied to the app sandbox or opened in place, and then accessed with `startAccessingSecurityScopedResource`.
|
||||||
let temporaryUrl = try self.saveTemporaryFile(url)
|
let temporaryUrl = try self.saveTemporaryFile(url)
|
||||||
temporaryUrls.append(temporaryUrl)
|
temporaryUrls.append(temporaryUrl)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -212,6 +230,8 @@ extension FilePickerController: PHPickerViewControllerDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
|
// We have to make a copy of the file to the app sandbox here, as PHPicker returns an NSItemProvider with either an ephemeral file URL or content that is deleted after the completion handler.
|
||||||
|
// This is a different behavior from UIDocumentPicker, where the file can either be copied to the app sandbox or opened in place, and then accessed with `startAccessingSecurityScopedResource`.
|
||||||
let temporaryUrl = try self.saveTemporaryFile(url)
|
let temporaryUrl = try self.saveTemporaryFile(url)
|
||||||
temporaryUrls.append(temporaryUrl)
|
temporaryUrls.append(temporaryUrl)
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tauri-apps/plugin-dialog",
|
"name": "@tauri-apps/plugin-dialog",
|
||||||
"version": "2.3.1",
|
"version": "2.6.0",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
"Tauri Programme within The Commons Conservancy"
|
"Tauri Programme within The Commons Conservancy"
|
||||||
@@ -24,6 +24,6 @@
|
|||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.6.0"
|
"@tauri-apps/api": "^2.10.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ use tauri::{command, Manager, Runtime, State, Window};
|
|||||||
use tauri_plugin_fs::FsExt;
|
use tauri_plugin_fs::FsExt;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Dialog, FileDialogBuilder, FilePath, MessageDialogButtons, MessageDialogKind, Result, CANCEL,
|
Dialog, FileAccessMode, FileDialogBuilder, FilePath, MessageDialogBuilder,
|
||||||
NO, OK, YES,
|
MessageDialogButtons, MessageDialogKind, MessageDialogResult, PickerMode, Result, CANCEL, NO,
|
||||||
|
OK, YES,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -56,6 +57,17 @@ pub struct OpenDialogOptions {
|
|||||||
recursive: bool,
|
recursive: bool,
|
||||||
/// Whether to allow creating directories in the dialog **macOS Only**
|
/// Whether to allow creating directories in the dialog **macOS Only**
|
||||||
can_create_directories: Option<bool>,
|
can_create_directories: Option<bool>,
|
||||||
|
/// The preferred mode of the dialog.
|
||||||
|
/// This is meant for mobile platforms (iOS and Android) which have distinct file and media pickers.
|
||||||
|
/// On desktop, this option is ignored.
|
||||||
|
/// If not provided, the dialog will automatically choose the best mode based on the MIME types of the filters.
|
||||||
|
#[serde(default)]
|
||||||
|
#[cfg_attr(mobile, allow(dead_code))]
|
||||||
|
picker_mode: Option<PickerMode>,
|
||||||
|
/// The file access mode of the dialog.
|
||||||
|
#[serde(default)]
|
||||||
|
#[cfg_attr(mobile, allow(dead_code))]
|
||||||
|
file_access_mode: Option<FileAccessMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The options for the save dialog API.
|
/// The options for the save dialog API.
|
||||||
@@ -127,10 +139,16 @@ pub(crate) async fn open<R: Runtime>(
|
|||||||
if let Some(can) = options.can_create_directories {
|
if let Some(can) = options.can_create_directories {
|
||||||
dialog_builder = dialog_builder.set_can_create_directories(can);
|
dialog_builder = dialog_builder.set_can_create_directories(can);
|
||||||
}
|
}
|
||||||
|
if let Some(picker_mode) = options.picker_mode {
|
||||||
|
dialog_builder = dialog_builder.set_picker_mode(picker_mode);
|
||||||
|
}
|
||||||
for filter in options.filters {
|
for filter in options.filters {
|
||||||
let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect();
|
let extensions: Vec<&str> = filter.extensions.iter().map(|s| &**s).collect();
|
||||||
dialog_builder = dialog_builder.add_filter(filter.name, &extensions);
|
dialog_builder = dialog_builder.add_filter(filter.name, &extensions);
|
||||||
}
|
}
|
||||||
|
if let Some(file_access_mode) = options.file_access_mode {
|
||||||
|
dialog_builder = dialog_builder.set_file_access_mode(file_access_mode);
|
||||||
|
}
|
||||||
|
|
||||||
let res = if options.directory {
|
let res = if options.directory {
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
@@ -248,7 +266,7 @@ fn message_dialog<R: Runtime>(
|
|||||||
message: String,
|
message: String,
|
||||||
kind: Option<MessageDialogKind>,
|
kind: Option<MessageDialogKind>,
|
||||||
buttons: MessageDialogButtons,
|
buttons: MessageDialogButtons,
|
||||||
) -> bool {
|
) -> MessageDialogBuilder<R> {
|
||||||
let mut builder = dialog.message(message);
|
let mut builder = dialog.message(message);
|
||||||
|
|
||||||
builder = builder.buttons(buttons);
|
builder = builder.buttons(buttons);
|
||||||
@@ -266,7 +284,7 @@ fn message_dialog<R: Runtime>(
|
|||||||
builder = builder.kind(kind);
|
builder = builder.kind(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.blocking_show()
|
builder
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
@@ -277,19 +295,15 @@ pub(crate) async fn message<R: Runtime>(
|
|||||||
message: String,
|
message: String,
|
||||||
kind: Option<MessageDialogKind>,
|
kind: Option<MessageDialogKind>,
|
||||||
ok_button_label: Option<String>,
|
ok_button_label: Option<String>,
|
||||||
) -> Result<bool> {
|
buttons: Option<MessageDialogButtons>,
|
||||||
Ok(message_dialog(
|
) -> Result<MessageDialogResult> {
|
||||||
window,
|
let buttons = buttons.unwrap_or(if let Some(ok_button_label) = ok_button_label {
|
||||||
dialog,
|
MessageDialogButtons::OkCustom(ok_button_label)
|
||||||
title,
|
} else {
|
||||||
message,
|
MessageDialogButtons::Ok
|
||||||
kind,
|
});
|
||||||
if let Some(ok_button_label) = ok_button_label {
|
|
||||||
MessageDialogButtons::OkCustom(ok_button_label)
|
Ok(message_dialog(window, dialog, title, message, kind, buttons).blocking_show_with_result())
|
||||||
} else {
|
|
||||||
MessageDialogButtons::Ok
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
@@ -302,7 +316,7 @@ pub(crate) async fn ask<R: Runtime>(
|
|||||||
yes_button_label: Option<String>,
|
yes_button_label: Option<String>,
|
||||||
no_button_label: Option<String>,
|
no_button_label: Option<String>,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
Ok(message_dialog(
|
let dialog = message_dialog(
|
||||||
window,
|
window,
|
||||||
dialog,
|
dialog,
|
||||||
title,
|
title,
|
||||||
@@ -318,7 +332,9 @@ pub(crate) async fn ask<R: Runtime>(
|
|||||||
} else {
|
} else {
|
||||||
MessageDialogButtons::YesNo
|
MessageDialogButtons::YesNo
|
||||||
},
|
},
|
||||||
))
|
);
|
||||||
|
|
||||||
|
Ok(dialog.blocking_show())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
@@ -331,7 +347,7 @@ pub(crate) async fn confirm<R: Runtime>(
|
|||||||
ok_button_label: Option<String>,
|
ok_button_label: Option<String>,
|
||||||
cancel_button_label: Option<String>,
|
cancel_button_label: Option<String>,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
Ok(message_dialog(
|
let dialog = message_dialog(
|
||||||
window,
|
window,
|
||||||
dialog,
|
dialog,
|
||||||
title,
|
title,
|
||||||
@@ -347,5 +363,7 @@ pub(crate) async fn confirm<R: Runtime>(
|
|||||||
} else {
|
} else {
|
||||||
MessageDialogButtons::OkCancel
|
MessageDialogButtons::OkCancel
|
||||||
},
|
},
|
||||||
))
|
);
|
||||||
|
|
||||||
|
Ok(dialog.blocking_show())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use rfd::{AsyncFileDialog, AsyncMessageDialog};
|
|||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use tauri::{plugin::PluginApi, AppHandle, Runtime};
|
use tauri::{plugin::PluginApi, AppHandle, Runtime};
|
||||||
|
|
||||||
use crate::{models::*, FileDialogBuilder, FilePath, MessageDialogBuilder, OK};
|
use crate::{models::*, FileDialogBuilder, FilePath, MessageDialogBuilder};
|
||||||
|
|
||||||
pub fn init<R: Runtime, C: DeserializeOwned>(
|
pub fn init<R: Runtime, C: DeserializeOwned>(
|
||||||
app: &AppHandle<R>,
|
app: &AppHandle<R>,
|
||||||
@@ -115,6 +115,10 @@ impl From<MessageDialogButtons> for rfd::MessageButtons {
|
|||||||
MessageDialogButtons::YesNo => Self::YesNo,
|
MessageDialogButtons::YesNo => Self::YesNo,
|
||||||
MessageDialogButtons::OkCustom(ok) => Self::OkCustom(ok),
|
MessageDialogButtons::OkCustom(ok) => Self::OkCustom(ok),
|
||||||
MessageDialogButtons::OkCancelCustom(ok, cancel) => Self::OkCancelCustom(ok, cancel),
|
MessageDialogButtons::OkCancelCustom(ok, cancel) => Self::OkCancelCustom(ok, cancel),
|
||||||
|
MessageDialogButtons::YesNoCancel => Self::YesNoCancel,
|
||||||
|
MessageDialogButtons::YesNoCancelCustom(yes, no, cancel) => {
|
||||||
|
Self::YesNoCancelCustom(yes, no, cancel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,28 +212,46 @@ pub fn save_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Shows a message dialog
|
/// Shows a message dialog
|
||||||
pub fn show_message_dialog<R: Runtime, F: FnOnce(bool) + Send + 'static>(
|
pub fn show_message_dialog<R: Runtime, F: FnOnce(MessageDialogResult) + Send + 'static>(
|
||||||
dialog: MessageDialogBuilder<R>,
|
dialog: MessageDialogBuilder<R>,
|
||||||
f: F,
|
callback: F,
|
||||||
) {
|
) {
|
||||||
use rfd::MessageDialogResult;
|
let f = move |res: rfd::MessageDialogResult| callback(res.into());
|
||||||
|
|
||||||
let ok_label = match &dialog.buttons {
|
|
||||||
MessageDialogButtons::OkCustom(ok) => Some(ok.clone()),
|
|
||||||
MessageDialogButtons::OkCancelCustom(ok, _) => Some(ok.clone()),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let f = move |res| {
|
|
||||||
f(match res {
|
|
||||||
MessageDialogResult::Ok | MessageDialogResult::Yes => true,
|
|
||||||
MessageDialogResult::Custom(s) => ok_label.map_or(s == OK, |ok_label| ok_label == s),
|
|
||||||
_ => false,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
let handle = dialog.dialog.app_handle().to_owned();
|
let handle = dialog.dialog.app_handle().to_owned();
|
||||||
let _ = handle.run_on_main_thread(move || {
|
let _ = handle.run_on_main_thread(move || {
|
||||||
|
let buttons = dialog.buttons.clone();
|
||||||
let dialog = AsyncMessageDialog::from(dialog).show();
|
let dialog = AsyncMessageDialog::from(dialog).show();
|
||||||
std::thread::spawn(move || f(tauri::async_runtime::block_on(dialog)));
|
std::thread::spawn(move || {
|
||||||
|
let result = tauri::async_runtime::block_on(dialog);
|
||||||
|
// on Linux rfd does not return rfd::MessageDialogResult::Custom, so we must map manually
|
||||||
|
let result = match (result, buttons) {
|
||||||
|
(rfd::MessageDialogResult::Ok, MessageDialogButtons::OkCustom(s)) => {
|
||||||
|
rfd::MessageDialogResult::Custom(s)
|
||||||
|
}
|
||||||
|
(
|
||||||
|
rfd::MessageDialogResult::Ok,
|
||||||
|
MessageDialogButtons::OkCancelCustom(ok, _cancel),
|
||||||
|
) => rfd::MessageDialogResult::Custom(ok),
|
||||||
|
(
|
||||||
|
rfd::MessageDialogResult::Cancel,
|
||||||
|
MessageDialogButtons::OkCancelCustom(_ok, cancel),
|
||||||
|
) => rfd::MessageDialogResult::Custom(cancel),
|
||||||
|
(
|
||||||
|
rfd::MessageDialogResult::Yes,
|
||||||
|
MessageDialogButtons::YesNoCancelCustom(yes, _no, _cancel),
|
||||||
|
) => rfd::MessageDialogResult::Custom(yes),
|
||||||
|
(
|
||||||
|
rfd::MessageDialogResult::No,
|
||||||
|
MessageDialogButtons::YesNoCancelCustom(_yes, no, _cancel),
|
||||||
|
) => rfd::MessageDialogResult::Custom(no),
|
||||||
|
(
|
||||||
|
rfd::MessageDialogResult::Cancel,
|
||||||
|
MessageDialogButtons::YesNoCancelCustom(_yes, _no, cancel),
|
||||||
|
) => rfd::MessageDialogResult::Custom(cancel),
|
||||||
|
(result, _) => result,
|
||||||
|
};
|
||||||
|
f(result);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+127
-14
@@ -9,7 +9,7 @@
|
|||||||
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
|
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use tauri::{
|
use tauri::{
|
||||||
plugin::{Builder, TauriPlugin},
|
plugin::{Builder, TauriPlugin},
|
||||||
Manager, Runtime,
|
Manager, Runtime,
|
||||||
@@ -44,6 +44,22 @@ pub use desktop::Dialog;
|
|||||||
#[cfg(mobile)]
|
#[cfg(mobile)]
|
||||||
pub use mobile::Dialog;
|
pub use mobile::Dialog;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum PickerMode {
|
||||||
|
Document,
|
||||||
|
Media,
|
||||||
|
Image,
|
||||||
|
Video,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum FileAccessMode {
|
||||||
|
Copy,
|
||||||
|
Scoped,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) const OK: &str = "Ok";
|
pub(crate) const OK: &str = "Ok";
|
||||||
pub(crate) const CANCEL: &str = "Cancel";
|
pub(crate) const CANCEL: &str = "Cancel";
|
||||||
pub(crate) const YES: &str = "Yes";
|
pub(crate) const YES: &str = "Yes";
|
||||||
@@ -182,7 +198,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|||||||
commands::save,
|
commands::save,
|
||||||
commands::message,
|
commands::message,
|
||||||
commands::ask,
|
commands::ask,
|
||||||
commands::confirm
|
commands::confirm,
|
||||||
])
|
])
|
||||||
.setup(|app, api| {
|
.setup(|app, api| {
|
||||||
#[cfg(mobile)]
|
#[cfg(mobile)]
|
||||||
@@ -216,6 +232,7 @@ pub(crate) struct MessageDialogPayload<'a> {
|
|||||||
message: &'a String,
|
message: &'a String,
|
||||||
kind: &'a MessageDialogKind,
|
kind: &'a MessageDialogKind,
|
||||||
ok_button_label: Option<&'a str>,
|
ok_button_label: Option<&'a str>,
|
||||||
|
no_button_label: Option<&'a str>,
|
||||||
cancel_button_label: Option<&'a str>,
|
cancel_button_label: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,13 +255,17 @@ impl<R: Runtime> MessageDialogBuilder<R> {
|
|||||||
|
|
||||||
#[cfg(mobile)]
|
#[cfg(mobile)]
|
||||||
pub(crate) fn payload(&self) -> MessageDialogPayload<'_> {
|
pub(crate) fn payload(&self) -> MessageDialogPayload<'_> {
|
||||||
let (ok_button_label, cancel_button_label) = match &self.buttons {
|
let (ok_button_label, no_button_label, cancel_button_label) = match &self.buttons {
|
||||||
MessageDialogButtons::Ok => (Some(OK), None),
|
MessageDialogButtons::Ok => (Some(OK), None, None),
|
||||||
MessageDialogButtons::OkCancel => (Some(OK), Some(CANCEL)),
|
MessageDialogButtons::OkCancel => (Some(OK), None, Some(CANCEL)),
|
||||||
MessageDialogButtons::YesNo => (Some(YES), Some(NO)),
|
MessageDialogButtons::YesNo => (Some(YES), Some(NO), None),
|
||||||
MessageDialogButtons::OkCustom(ok) => (Some(ok.as_str()), Some(CANCEL)),
|
MessageDialogButtons::YesNoCancel => (Some(YES), Some(NO), Some(CANCEL)),
|
||||||
|
MessageDialogButtons::OkCustom(ok) => (Some(ok.as_str()), None, None),
|
||||||
MessageDialogButtons::OkCancelCustom(ok, cancel) => {
|
MessageDialogButtons::OkCancelCustom(ok, cancel) => {
|
||||||
(Some(ok.as_str()), Some(cancel.as_str()))
|
(Some(ok.as_str()), None, Some(cancel.as_str()))
|
||||||
|
}
|
||||||
|
MessageDialogButtons::YesNoCancelCustom(yes, no, cancel) => {
|
||||||
|
(Some(yes.as_str()), Some(no.as_str()), Some(cancel.as_str()))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
MessageDialogPayload {
|
MessageDialogPayload {
|
||||||
@@ -252,6 +273,7 @@ impl<R: Runtime> MessageDialogBuilder<R> {
|
|||||||
message: &self.message,
|
message: &self.message,
|
||||||
kind: &self.kind,
|
kind: &self.kind,
|
||||||
ok_button_label,
|
ok_button_label,
|
||||||
|
no_button_label,
|
||||||
cancel_button_label,
|
cancel_button_label,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,16 +317,55 @@ impl<R: Runtime> MessageDialogBuilder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Shows a message dialog
|
/// Shows a message dialog
|
||||||
|
///
|
||||||
|
/// Returns `true` if the user pressed the OK/Yes button,
|
||||||
pub fn show<F: FnOnce(bool) + Send + 'static>(self, f: F) {
|
pub fn show<F: FnOnce(bool) + Send + 'static>(self, f: F) {
|
||||||
|
let ok_label = match &self.buttons {
|
||||||
|
MessageDialogButtons::OkCustom(ok) => Some(ok.clone()),
|
||||||
|
MessageDialogButtons::OkCancelCustom(ok, _) => Some(ok.clone()),
|
||||||
|
MessageDialogButtons::YesNoCancelCustom(yes, _, _) => Some(yes.clone()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
show_message_dialog(self, move |res| {
|
||||||
|
let sucess = match res {
|
||||||
|
MessageDialogResult::Ok | MessageDialogResult::Yes => true,
|
||||||
|
MessageDialogResult::Custom(s) => {
|
||||||
|
ok_label.map_or(s == OK, |ok_label| ok_label == s)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
f(sucess)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows a message dialog and returns the button that was pressed.
|
||||||
|
///
|
||||||
|
/// Returns a [`MessageDialogResult`] enum that indicates which button was pressed.
|
||||||
|
pub fn show_with_result<F: FnOnce(MessageDialogResult) + Send + 'static>(self, f: F) {
|
||||||
show_message_dialog(self, f)
|
show_message_dialog(self, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows a message dialog.
|
/// Shows a message dialog.
|
||||||
|
///
|
||||||
|
/// Returns `true` if the user pressed the OK/Yes button,
|
||||||
|
///
|
||||||
/// This is a blocking operation,
|
/// This is a blocking operation,
|
||||||
/// and should *NOT* be used when running on the main thread context.
|
/// and should *NOT* be used when running on the main thread context.
|
||||||
pub fn blocking_show(self) -> bool {
|
pub fn blocking_show(self) -> bool {
|
||||||
blocking_fn!(self, show)
|
blocking_fn!(self, show)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shows a message dialog and returns the button that was pressed.
|
||||||
|
///
|
||||||
|
/// Returns a [`MessageDialogResult`] enum that indicates which button was pressed.
|
||||||
|
///
|
||||||
|
/// This is a blocking operation,
|
||||||
|
/// and should *NOT* be used when running on the main thread context.
|
||||||
|
pub fn blocking_show_with_result(self) -> MessageDialogResult {
|
||||||
|
blocking_fn!(self, show_with_result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) struct Filter {
|
pub(crate) struct Filter {
|
||||||
@@ -324,6 +385,8 @@ pub struct FileDialogBuilder<R: Runtime> {
|
|||||||
pub(crate) file_name: Option<String>,
|
pub(crate) file_name: Option<String>,
|
||||||
pub(crate) title: Option<String>,
|
pub(crate) title: Option<String>,
|
||||||
pub(crate) can_create_directories: Option<bool>,
|
pub(crate) can_create_directories: Option<bool>,
|
||||||
|
pub(crate) picker_mode: Option<PickerMode>,
|
||||||
|
pub(crate) file_access_mode: Option<FileAccessMode>,
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
pub(crate) parent: Option<crate::desktop::WindowHandle>,
|
pub(crate) parent: Option<crate::desktop::WindowHandle>,
|
||||||
}
|
}
|
||||||
@@ -335,6 +398,8 @@ pub(crate) struct FileDialogPayload<'a> {
|
|||||||
file_name: &'a Option<String>,
|
file_name: &'a Option<String>,
|
||||||
filters: &'a Vec<Filter>,
|
filters: &'a Vec<Filter>,
|
||||||
multiple: bool,
|
multiple: bool,
|
||||||
|
picker_mode: &'a Option<PickerMode>,
|
||||||
|
file_access_mode: &'a Option<FileAccessMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// raw window handle :(
|
// raw window handle :(
|
||||||
@@ -350,6 +415,8 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
|||||||
file_name: None,
|
file_name: None,
|
||||||
title: None,
|
title: None,
|
||||||
can_create_directories: None,
|
can_create_directories: None,
|
||||||
|
picker_mode: None,
|
||||||
|
file_access_mode: None,
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
parent: None,
|
parent: None,
|
||||||
}
|
}
|
||||||
@@ -361,6 +428,8 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
|||||||
file_name: &self.file_name,
|
file_name: &self.file_name,
|
||||||
filters: &self.filters,
|
filters: &self.filters,
|
||||||
multiple,
|
multiple,
|
||||||
|
picker_mode: &self.picker_mode,
|
||||||
|
file_access_mode: &self.file_access_mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,11 +490,29 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the picker mode of the dialog.
|
||||||
|
/// This is meant for mobile platforms (iOS and Android) which have distinct file and media pickers.
|
||||||
|
/// On desktop, this option is ignored.
|
||||||
|
/// If not provided, the dialog will automatically choose the best mode based on the MIME types of the filters.
|
||||||
|
pub fn set_picker_mode(mut self, mode: PickerMode) -> Self {
|
||||||
|
self.picker_mode.replace(mode);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the file access mode of the dialog.
|
||||||
|
/// This is only used on iOS.
|
||||||
|
/// On desktop and Android, this option is ignored.
|
||||||
|
pub fn set_file_access_mode(mut self, mode: FileAccessMode) -> Self {
|
||||||
|
self.file_access_mode.replace(mode);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Shows the dialog to select a single file.
|
/// Shows the dialog to select a single file.
|
||||||
|
///
|
||||||
/// This is not a blocking operation,
|
/// This is not a blocking operation,
|
||||||
/// and should be used when running on the main thread to avoid deadlocks with the event loop.
|
/// and should be used when running on the main thread to avoid deadlocks with the event loop.
|
||||||
///
|
///
|
||||||
/// For usage in other contexts such as commands, prefer [`Self::pick_file`].
|
/// See [`Self::blocking_pick_file`] for a blocking version for use in other contexts.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@@ -445,9 +532,12 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Shows the dialog to select multiple files.
|
/// Shows the dialog to select multiple files.
|
||||||
|
///
|
||||||
/// This is not a blocking operation,
|
/// This is not a blocking operation,
|
||||||
/// and should be used when running on the main thread to avoid deadlocks with the event loop.
|
/// and should be used when running on the main thread to avoid deadlocks with the event loop.
|
||||||
///
|
///
|
||||||
|
/// See [`Self::blocking_pick_files`] for a blocking version for use in other contexts.
|
||||||
|
///
|
||||||
/// # Reading the files
|
/// # Reading the files
|
||||||
///
|
///
|
||||||
/// The file paths cannot be read directly on Android as they are behind a content URI.
|
/// The file paths cannot be read directly on Android as they are behind a content URI.
|
||||||
@@ -490,9 +580,12 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Shows the dialog to select a single folder.
|
/// Shows the dialog to select a single folder.
|
||||||
|
///
|
||||||
/// This is not a blocking operation,
|
/// This is not a blocking operation,
|
||||||
/// and should be used when running on the main thread to avoid deadlocks with the event loop.
|
/// and should be used when running on the main thread to avoid deadlocks with the event loop.
|
||||||
///
|
///
|
||||||
|
/// See [`Self::blocking_pick_folder`] for a blocking version for use in other contexts.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@@ -512,9 +605,12 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Shows the dialog to select multiple folders.
|
/// Shows the dialog to select multiple folders.
|
||||||
|
///
|
||||||
/// This is not a blocking operation,
|
/// This is not a blocking operation,
|
||||||
/// and should be used when running on the main thread to avoid deadlocks with the event loop.
|
/// and should be used when running on the main thread to avoid deadlocks with the event loop.
|
||||||
///
|
///
|
||||||
|
/// See [`Self::blocking_pick_folders`] for a blocking version for use in other contexts.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@@ -538,6 +634,8 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
|||||||
/// This is not a blocking operation,
|
/// This is not a blocking operation,
|
||||||
/// and should be used when running on the main thread to avoid deadlocks with the event loop.
|
/// and should be used when running on the main thread to avoid deadlocks with the event loop.
|
||||||
///
|
///
|
||||||
|
/// See [`Self::blocking_save_file`] for a blocking version for use in other contexts.
|
||||||
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@@ -559,8 +657,11 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
|||||||
/// Blocking APIs.
|
/// Blocking APIs.
|
||||||
impl<R: Runtime> FileDialogBuilder<R> {
|
impl<R: Runtime> FileDialogBuilder<R> {
|
||||||
/// Shows the dialog to select a single file.
|
/// Shows the dialog to select a single file.
|
||||||
|
///
|
||||||
/// This is a blocking operation,
|
/// This is a blocking operation,
|
||||||
/// and should *NOT* be used when running on the main thread context.
|
/// and should *NOT* be used when running on the main thread.
|
||||||
|
///
|
||||||
|
/// See [`Self::pick_file`] for a non-blocking version for use in main-thread contexts.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@@ -578,8 +679,11 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Shows the dialog to select multiple files.
|
/// Shows the dialog to select multiple files.
|
||||||
|
///
|
||||||
/// This is a blocking operation,
|
/// This is a blocking operation,
|
||||||
/// and should *NOT* be used when running on the main thread context.
|
/// and should *NOT* be used when running on the main thread.
|
||||||
|
///
|
||||||
|
/// See [`Self::pick_files`] for a non-blocking version for use in main-thread contexts.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@@ -597,8 +701,11 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Shows the dialog to select a single folder.
|
/// Shows the dialog to select a single folder.
|
||||||
|
///
|
||||||
/// This is a blocking operation,
|
/// This is a blocking operation,
|
||||||
/// and should *NOT* be used when running on the main thread context.
|
/// and should *NOT* be used when running on the main thread.
|
||||||
|
///
|
||||||
|
/// See [`Self::pick_folder`] for a non-blocking version for use in main-thread contexts.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@@ -617,8 +724,11 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Shows the dialog to select multiple folders.
|
/// Shows the dialog to select multiple folders.
|
||||||
|
///
|
||||||
/// This is a blocking operation,
|
/// This is a blocking operation,
|
||||||
/// and should *NOT* be used when running on the main thread context.
|
/// and should *NOT* be used when running on the main thread.
|
||||||
|
///
|
||||||
|
/// See [`Self::pick_folders`] for a non-blocking version for use in main-thread contexts.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@@ -637,8 +747,11 @@ impl<R: Runtime> FileDialogBuilder<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Shows the dialog to save a file.
|
/// Shows the dialog to save a file.
|
||||||
|
///
|
||||||
/// This is a blocking operation,
|
/// This is a blocking operation,
|
||||||
/// and should *NOT* be used when running on the main thread context.
|
/// and should *NOT* be used when running on the main thread.
|
||||||
|
///
|
||||||
|
/// See [`Self::save_file`] for a non-blocking version for use in main-thread contexts.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use tauri::{
|
|||||||
AppHandle, Runtime,
|
AppHandle, Runtime,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{FileDialogBuilder, FilePath, MessageDialogBuilder};
|
use crate::{FileDialogBuilder, FilePath, MessageDialogBuilder, MessageDialogResult};
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
#[cfg(target_os = "android")]
|
||||||
const PLUGIN_IDENTIFIER: &str = "app.tauri.dialog";
|
const PLUGIN_IDENTIFIER: &str = "app.tauri.dialog";
|
||||||
@@ -107,13 +107,11 @@ pub fn save_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct ShowMessageDialogResponse {
|
struct ShowMessageDialogResponse {
|
||||||
#[allow(dead_code)]
|
value: String,
|
||||||
cancelled: bool,
|
|
||||||
value: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows a message dialog
|
/// Shows a message dialog
|
||||||
pub fn show_message_dialog<R: Runtime, F: FnOnce(bool) + Send + 'static>(
|
pub fn show_message_dialog<R: Runtime, F: FnOnce(MessageDialogResult) + Send + 'static>(
|
||||||
dialog: MessageDialogBuilder<R>,
|
dialog: MessageDialogBuilder<R>,
|
||||||
f: F,
|
f: F,
|
||||||
) {
|
) {
|
||||||
@@ -122,6 +120,8 @@ pub fn show_message_dialog<R: Runtime, F: FnOnce(bool) + Send + 'static>(
|
|||||||
.dialog
|
.dialog
|
||||||
.0
|
.0
|
||||||
.run_mobile_plugin::<ShowMessageDialogResponse>("showMessageDialog", dialog.payload());
|
.run_mobile_plugin::<ShowMessageDialogResponse>("showMessageDialog", dialog.payload());
|
||||||
f(res.map(|r| r.value).unwrap_or_default())
|
|
||||||
|
let res = res.map(|res| res.value.into());
|
||||||
|
f(res.unwrap_or_default())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|||||||
|
|
||||||
/// Types of message, ask and confirm dialogs.
|
/// Types of message, ask and confirm dialogs.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
|
||||||
pub enum MessageDialogKind {
|
pub enum MessageDialogKind {
|
||||||
/// Information dialog.
|
/// Information dialog.
|
||||||
|
#[default]
|
||||||
Info,
|
Info,
|
||||||
/// Warning dialog.
|
/// Warning dialog.
|
||||||
Warning,
|
Warning,
|
||||||
@@ -16,12 +17,6 @@ pub enum MessageDialogKind {
|
|||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MessageDialogKind {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for MessageDialogKind {
|
impl<'de> Deserialize<'de> for MessageDialogKind {
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
where
|
where
|
||||||
@@ -52,7 +47,7 @@ impl Serialize for MessageDialogKind {
|
|||||||
|
|
||||||
/// Set of button that will be displayed on the dialog
|
/// Set of button that will be displayed on the dialog
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||||
pub enum MessageDialogButtons {
|
pub enum MessageDialogButtons {
|
||||||
#[default]
|
#[default]
|
||||||
/// A single `Ok` button with OS default dialog text
|
/// A single `Ok` button with OS default dialog text
|
||||||
@@ -61,8 +56,49 @@ pub enum MessageDialogButtons {
|
|||||||
OkCancel,
|
OkCancel,
|
||||||
/// 2 buttons `Yes` and `No` with OS default dialog texts
|
/// 2 buttons `Yes` and `No` with OS default dialog texts
|
||||||
YesNo,
|
YesNo,
|
||||||
|
/// 3 buttons `Yes`, `No` and `Cancel` with OS default dialog texts
|
||||||
|
YesNoCancel,
|
||||||
/// A single `Ok` button with custom text
|
/// A single `Ok` button with custom text
|
||||||
OkCustom(String),
|
OkCustom(String),
|
||||||
/// 2 buttons `Ok` and `Cancel` with custom texts
|
/// 2 buttons `Ok` and `Cancel` with custom texts
|
||||||
OkCancelCustom(String, String),
|
OkCancelCustom(String, String),
|
||||||
|
/// 3 buttons `Yes`, `No` and `Cancel` with custom texts
|
||||||
|
YesNoCancelCustom(String, String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of a message dialog
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
|
pub enum MessageDialogResult {
|
||||||
|
Yes,
|
||||||
|
No,
|
||||||
|
Ok,
|
||||||
|
#[default]
|
||||||
|
Cancel,
|
||||||
|
#[serde(untagged)]
|
||||||
|
Custom(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(desktop)]
|
||||||
|
impl From<rfd::MessageDialogResult> for MessageDialogResult {
|
||||||
|
fn from(result: rfd::MessageDialogResult) -> Self {
|
||||||
|
match result {
|
||||||
|
rfd::MessageDialogResult::Yes => Self::Yes,
|
||||||
|
rfd::MessageDialogResult::No => Self::No,
|
||||||
|
rfd::MessageDialogResult::Ok => Self::Ok,
|
||||||
|
rfd::MessageDialogResult::Cancel => Self::Cancel,
|
||||||
|
rfd::MessageDialogResult::Custom(s) => Self::Custom(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for MessageDialogResult {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
match value.as_str() {
|
||||||
|
"Yes" => Self::Yes,
|
||||||
|
"No" => Self::No,
|
||||||
|
"Ok" => Self::Ok,
|
||||||
|
"Cancel" => Self::Cancel,
|
||||||
|
_ => Self::Custom(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,25 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## \[2.4.5]
|
||||||
|
|
||||||
|
- [`521cd8b3`](https://github.com/tauri-apps/plugins-workspace/commit/521cd8b372c862d96b1637775710e4d7cf2443e2) ([#3155](https://github.com/tauri-apps/plugins-workspace/pull/3155) by [@EliasStar](https://github.com/tauri-apps/plugins-workspace/../../EliasStar)) Fix off by one error in the implementation of readTextFileLines causing all lines to end with an (additional) null byte.
|
||||||
|
Issue: [#3154](https://github.com/tauri-apps/plugins-workspace/issues/3154)
|
||||||
|
PR: [#3155](https://github.com/tauri-apps/plugins-workspace/pull/3155)
|
||||||
|
|
||||||
|
## \[2.4.4]
|
||||||
|
|
||||||
|
- [`93426f85`](https://github.com/tauri-apps/plugins-workspace/commit/93426f85120f49beb9f40222bff45185a32d54a9) Fixed an issue that caused docs.rs builds to fail. No user facing changes.
|
||||||
|
|
||||||
|
## \[2.4.3]
|
||||||
|
|
||||||
|
- [`6c9b61fb`](https://github.com/tauri-apps/plugins-workspace/commit/6c9b61fb658145d13893626112fc489f7458aa17) ([#3039](https://github.com/tauri-apps/plugins-workspace/pull/3039) by [@FabianLars](https://github.com/tauri-apps/plugins-workspace/../../FabianLars)) On Android, updated compileSdk to 36.
|
||||||
|
- [`6b5b1053`](https://github.com/tauri-apps/plugins-workspace/commit/6b5b1053ba8aeb789dd5cb5fb05b7e98f3b8de0b) ([#1939](https://github.com/tauri-apps/plugins-workspace/pull/1939) by [@amrbashir](https://github.com/tauri-apps/plugins-workspace/../../amrbashir)) Enhance error messages.
|
||||||
|
|
||||||
|
## \[2.4.2]
|
||||||
|
|
||||||
|
- [`4eb36b0f`](https://github.com/tauri-apps/plugins-workspace/commit/4eb36b0ff57acb0bb1b911c583efa3bf2f56aa32) ([#2907](https://github.com/tauri-apps/plugins-workspace/pull/2907) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fixed calling `writeFile` with `data: ReadableStream` throws `Invalid argument`
|
||||||
|
- [`515182a1`](https://github.com/tauri-apps/plugins-workspace/commit/515182a179d4439079b2b7f6927555ba5ab0b035) ([#2915](https://github.com/tauri-apps/plugins-workspace/pull/2915) by [@samhinshaw](https://github.com/tauri-apps/plugins-workspace/../../samhinshaw)) `readFile` now returns a more specific type `Promise<Uint8Array<ArrayBuffer>>` instead of the default `Promise<Uint8Array<ArrayBufferLike>`
|
||||||
|
|
||||||
## \[2.4.1]
|
## \[2.4.1]
|
||||||
|
|
||||||
- [`44a1f659`](https://github.com/tauri-apps/plugins-workspace/commit/44a1f659125a341191420e650608b0b6ff316a0e) ([#2846](https://github.com/tauri-apps/plugins-workspace/pull/2846) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fix `writeFile` doesn't create a new file by default when the data is a `ReadableStream`
|
- [`44a1f659`](https://github.com/tauri-apps/plugins-workspace/commit/44a1f659125a341191420e650608b0b6ff316a0e) ([#2846](https://github.com/tauri-apps/plugins-workspace/pull/2846) by [@Legend-Master](https://github.com/tauri-apps/plugins-workspace/../../Legend-Master)) Fix `writeFile` doesn't create a new file by default when the data is a `ReadableStream`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tauri-plugin-fs"
|
name = "tauri-plugin-fs"
|
||||||
version = "2.4.1"
|
version = "2.4.5"
|
||||||
description = "Access the file system."
|
description = "Access the file system."
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
@@ -9,10 +9,6 @@ rust-version = { workspace = true }
|
|||||||
repository = { workspace = true }
|
repository = { workspace = true }
|
||||||
links = "tauri-plugin-fs"
|
links = "tauri-plugin-fs"
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
rustc-args = ["--cfg", "docsrs"]
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[package.metadata.platforms.support]
|
[package.metadata.platforms.support]
|
||||||
windows = { level = "full", notes = "Apps installed via MSI or NSIS in `perMachine` and `both` mode require admin permissions for write access in `$RESOURCES` folder" }
|
windows = { level = "full", notes = "Apps installed via MSI or NSIS in `perMachine` and `both` mode require admin permissions for write access in `$RESOURCES` folder" }
|
||||||
linux = { level = "full", notes = "No write access to `$RESOURCES` folder" }
|
linux = { level = "full", notes = "No write access to `$RESOURCES` folder" }
|
||||||
@@ -24,7 +20,7 @@ ios = { level = "partial", notes = "Access is restricted to Application folder b
|
|||||||
tauri-plugin = { workspace = true, features = ["build"] }
|
tauri-plugin = { workspace = true, features = ["build"] }
|
||||||
schemars = { workspace = true }
|
schemars = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
toml = "0.8"
|
toml = "0.9"
|
||||||
tauri-utils = { workspace = true, features = ["build"] }
|
tauri-utils = { workspace = true, features = ["build"] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@@ -41,7 +37,7 @@ notify = { version = "8", optional = true, features = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serialization-compat-6",
|
"serialization-compat-6",
|
||||||
] }
|
] }
|
||||||
notify-debouncer-full = { version = "0.5", optional = true }
|
notify-debouncer-full = { version = "0.6", optional = true }
|
||||||
dunce = { workspace = true }
|
dunce = { workspace = true }
|
||||||
percent-encoding = "2"
|
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:
|
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
|
```sh
|
||||||
pnpm add @tauri-apps/plugin-fs
|
pnpm add @tauri-apps/plugin-fs
|
||||||
# or
|
# or
|
||||||
npm add @tauri-apps/plugin-fs
|
npm add @tauri-apps/plugin-fs
|
||||||
# or
|
# or
|
||||||
yarn add @tauri-apps/plugin-fs
|
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
|
## Usage
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.plugin.fs"
|
namespace = "com.plugin.fs"
|
||||||
compileSdk = 34
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
consumerProguardFiles("consumer-rules.pro")
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user