Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/dialog/api-iife.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion plugins/dialog/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

const COMMANDS: &[&str] = &["open", "save", "message", "ask", "confirm"];
const COMMANDS: &[&str] = &[
"open",
"save",
"destroy_path",
"message",
"ask",
"confirm",
];

fn main() {
let result = tauri_plugin::Builder::new(COMMANDS)
Expand Down
65 changes: 54 additions & 11 deletions plugins/dialog/guest-js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,31 @@

import { invoke } from '@tauri-apps/api/core'

class Path {
public path: string
constructor(path: string) {
this.path = path
}

destroy() {
return invoke('plugin:dialog|destroy_path', { path: this.path })
}

toPath() {
return this.path
}

toString() {
return this.toPath()
}

toJSON() {
return {
path: this.path
}
}
}

/**
* Extension filters for the file dialog.
*
Expand Down Expand Up @@ -224,13 +249,7 @@ interface ConfirmDialogOptions {
cancelLabel?: string
}

type OpenDialogReturn<T extends OpenDialogOptions> = T['directory'] extends true
? T['multiple'] extends true
? string[] | null
: string | null
: T['multiple'] extends true
? string[] | null
: string | null
type OpenDialogReturn<T extends OpenDialogOptions> = T['multiple'] extends true ? Path[] | null : Path | null

/**
* Open a file/directory selection dialog.
Expand Down Expand Up @@ -280,18 +299,32 @@ type OpenDialogReturn<T extends OpenDialogOptions> = T['directory'] extends true
* }
* ```
*
* ## Platform-specific
*
* - **iOS**: Returns a copy of the file to bypass [security scoped resource](https://developer.apple.com/documentation/foundation/nsurl/1417051-startaccessingsecurityscopedreso?language=objc).
*
* @returns A promise resolving to the selected path(s)
*
* @since 2.0.0
*/
async function open<T extends OpenDialogOptions>(
options: T = {} as T
): Promise<OpenDialogReturn<T>> {
): Promise<Path | Path[] | null> {
if (typeof options === 'object') {
Object.freeze(options)
}

return await invoke('plugin:dialog|open', { options })
const path = await invoke<string[] | string | null>('plugin:dialog|open', { options })

if (Array.isArray(path)) {
return path.map((p) => new Path(p))
}

if (!path) {
return null
}

return new Path(path)
}

/**
Expand All @@ -314,16 +347,26 @@ async function open<T extends OpenDialogOptions>(
* });
* ```
*
* #### Platform-specific
*
* - **iOS**: Returns a copy of the file to bypass [security scoped resource](https://developer.apple.com/documentation/foundation/nsurl/1417051-startaccessingsecurityscopedreso?language=objc).
*
* @returns A promise resolving to the selected path.
*
* @since 2.0.0
*/
async function save(options: SaveDialogOptions = {}): Promise<string | null> {
async function save(options: SaveDialogOptions = {}): Promise<Path | null> {
if (typeof options === 'object') {
Object.freeze(options)
}

return await invoke('plugin:dialog|save', { options })
const path = await invoke<string | null>('plugin:dialog|save', { options })

if (!path) {
return null
}

return new Path(path)
}

/**
Expand Down
13 changes: 13 additions & 0 deletions plugins/dialog/ios/Sources/DialogPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ struct SaveFileDialogOptions: Decodable {
var defaultPath: String?
}

struct StopAccessingPathOptions: Decodable {
var path: URL
}

class DialogPlugin: Plugin {

var filePickerController: FilePickerController!
Expand Down Expand Up @@ -75,6 +79,7 @@ class DialogPlugin: Plugin {
onFilePickerResult = { (event: FilePickerEvent) -> Void in
switch event {
case .selected(let urls):
urls.forEach { $0.startAccessingSecurityScopedResource() }
invoke.resolve(["files": urls])
case .cancelled:
invoke.resolve(["files": nil])
Expand Down Expand Up @@ -150,6 +155,8 @@ class DialogPlugin: Plugin {
onFilePickerResult = { (event: FilePickerEvent) -> Void in
switch event {
case .selected(let urls):
Logger.info("picked file to save: \(urls.first!)")
urls.first!.startAccessingSecurityScopedResource()
invoke.resolve(["file": urls.first!])
case .cancelled:
invoke.resolve(["file": nil])
Expand All @@ -169,6 +176,12 @@ class DialogPlugin: Plugin {
}
}

@objc public func stopAccessingPath(_ invoke: Invoke) throws {
let args = try invoke.parseArgs(StopAccessingPathOptions.self)
args.path.stopAccessingSecurityScopedResource()
invoke.resolve()
}

private func presentViewController(_ viewControllerToPresent: UIViewController) {
self.manager.viewController?.present(viewControllerToPresent, animated: true, completion: nil)
}
Expand Down
53 changes: 7 additions & 46 deletions plugins/dialog/ios/Sources/FilePickerController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,35 +95,11 @@ public class FilePickerController: NSObject {
return nil
}
}

private func saveTemporaryFile(_ sourceUrl: URL) throws -> URL {
var directory = URL(fileURLWithPath: NSTemporaryDirectory())
if let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first {
directory = cachesDirectory
}
let targetUrl = directory.appendingPathComponent(sourceUrl.lastPathComponent)
do {
try deleteFile(targetUrl)
}
try FileManager.default.copyItem(at: sourceUrl, to: targetUrl)
return targetUrl
}

private func deleteFile(_ url: URL) throws {
if FileManager.default.fileExists(atPath: url.path) {
try FileManager.default.removeItem(atPath: url.path)
}
}
}

extension FilePickerController: UIDocumentPickerDelegate {
public func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
do {
let temporaryUrls = try urls.map { try saveTemporaryFile($0) }
self.plugin.onFilePickerEvent(.selected(temporaryUrls))
} catch {
self.plugin.onFilePickerEvent(.error("Failed to create a temporary copy of the file"))
}
self.plugin.onFilePickerEvent(.selected(urls))
}

public func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
Expand All @@ -148,12 +124,7 @@ extension FilePickerController: UIImagePickerControllerDelegate, UINavigationCon
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
dismissViewController(picker) {
if let url = info[.mediaURL] as? URL {
do {
let temporaryUrl = try self.saveTemporaryFile(url)
self.plugin.onFilePickerEvent(.selected([temporaryUrl]))
} catch {
self.plugin.onFilePickerEvent(.error("Failed to create a temporary copy of the file"))
}
self.plugin.onFilePickerEvent(.selected([url]))
} else {
self.plugin.onFilePickerEvent(.cancelled)
}
Expand All @@ -169,7 +140,7 @@ extension FilePickerController: PHPickerViewControllerDelegate {
self.plugin.onFilePickerEvent(.cancelled)
return
}
var temporaryUrls: [URL] = []
var urls: [URL] = []
var errorMessage: String?
let dispatchGroup = DispatchGroup()
for result in results {
Expand All @@ -190,12 +161,7 @@ extension FilePickerController: PHPickerViewControllerDelegate {
errorMessage = "Unknown error"
return
}
do {
let temporaryUrl = try self.saveTemporaryFile(url)
temporaryUrls.append(temporaryUrl)
} catch {
errorMessage = "Failed to create a temporary copy of the file"
}
urls.append(url)
})
} else if result.itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
dispatchGroup.enter()
Expand All @@ -211,12 +177,7 @@ extension FilePickerController: PHPickerViewControllerDelegate {
errorMessage = "Unknown error"
return
}
do {
let temporaryUrl = try self.saveTemporaryFile(url)
temporaryUrls.append(temporaryUrl)
} catch {
errorMessage = "Failed to create a temporary copy of the file"
}
urls.append(url)
})
} else {
errorMessage = "Unsupported file type identifier"
Expand All @@ -227,7 +188,7 @@ extension FilePickerController: PHPickerViewControllerDelegate {
self.plugin.onFilePickerEvent(.error(errorMessage))
return
}
self.plugin.onFilePickerEvent(.selected(temporaryUrls))
self.plugin.onFilePickerEvent(.selected(urls))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Automatically generated - DO NOT EDIT!

"$schema" = "../../schemas/schema.json"

[[permission]]
identifier = "allow-destroy-path"
description = "Enables the destroy_path command without any pre-configured scope."
commands.allow = ["destroy_path"]

[[permission]]
identifier = "deny-destroy-path"
description = "Denies the destroy_path command without any pre-configured scope."
commands.deny = ["destroy_path"]
27 changes: 27 additions & 0 deletions plugins/dialog/permissions/autogenerated/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ All dialog types are enabled.
- `allow-message`
- `allow-save`
- `allow-open`
- `allow-destroy-path`

## Permission Table

Expand Down Expand Up @@ -79,6 +80,32 @@ Denies the confirm command without any pre-configured scope.
<tr>
<td>

`dialog:allow-destroy-path`

</td>
<td>

Enables the destroy_path command without any pre-configured scope.

</td>
</tr>

<tr>
<td>

`dialog:deny-destroy-path`

</td>
<td>

Denies the destroy_path command without any pre-configured scope.

</td>
</tr>

<tr>
<td>

`dialog:allow-message`

</td>
Expand Down
1 change: 1 addition & 0 deletions plugins/dialog/permissions/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ permissions = [
"allow-message",
"allow-save",
"allow-open",
"allow-destroy-path"
]
16 changes: 14 additions & 2 deletions plugins/dialog/permissions/schemas/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,18 @@
"const": "deny-confirm",
"markdownDescription": "Denies the confirm command without any pre-configured scope."
},
{
"description": "Enables the destroy_path command without any pre-configured scope.",
"type": "string",
"const": "allow-destroy-path",
"markdownDescription": "Enables the destroy_path command without any pre-configured scope."
},
{
"description": "Denies the destroy_path command without any pre-configured scope.",
"type": "string",
"const": "deny-destroy-path",
"markdownDescription": "Denies the destroy_path command without any pre-configured scope."
},
{
"description": "Enables the message command without any pre-configured scope.",
"type": "string",
Expand Down Expand Up @@ -355,10 +367,10 @@
"markdownDescription": "Denies the save command without any pre-configured scope."
},
{
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`",
"description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`\n- `allow-destroy-path`",
"type": "string",
"const": "default",
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`"
"markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`\n- `allow-destroy-path`"
}
]
}
Expand Down
5 changes: 5 additions & 0 deletions plugins/dialog/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ pub(crate) async fn save<R: Runtime>(
Ok(path.map(|p| p.simplified()))
}

#[command]
pub fn destroy_path(_path: String) -> bool {
true
}

fn message_dialog<R: Runtime>(
#[allow(unused_variables)] window: Window<R>,
dialog: State<'_, Dialog<R>>,
Expand Down
4 changes: 4 additions & 0 deletions plugins/dialog/src/desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ pub fn save_file<R: Runtime, F: FnOnce(Option<FilePath>) + Send + 'static>(
});
}

pub fn destroy_path<R: Runtime>(_dialog: FileDialogBuilder<R>, _path: String) -> bool {
true
}

/// Shows a message dialog
pub fn show_message_dialog<R: Runtime, F: FnOnce(MessageDialogResult) + Send + 'static>(
dialog: MessageDialogBuilder<R>,
Expand Down
Loading
Loading