PresentSingleFileOpenDialogAction.swift 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import Foundation
  2. /// Presents an 'Open file' dialog fit for selecting a single file.
  3. @available(tvOS, unavailable, message: "tvOS does not provide file system access")
  4. public struct PresentSingleFileOpenDialogAction: Sendable {
  5. let backend: any BaseAppBackend
  6. let window: MainActorBox<Any?>
  7. /// Presents an 'Open file' dialog fit for selecting a single file.
  8. ///
  9. /// - Important: GtkBackend, Gtk3Backend, and WinUIBackend will only
  10. /// enable _either_ files or directories for selection, but won't
  11. /// enable both types in a single dialog.
  12. ///
  13. /// - Parameters:
  14. /// - title: The dialog's title. Defaults to "Open".
  15. /// - message: The dialog's message. Defaults to an empty string.
  16. /// - defaultButtonLabel: The label for the dialog's default button.
  17. /// Defaults to "Open".
  18. /// - initialDirectory: The directory to start the dialog in. Defaults
  19. /// to `nil`, which lets the backend choose (usually it'll be the
  20. /// app's current working directory and/or the directory where the
  21. /// previous dialog was dismissed).
  22. /// - showHiddenFiles: Whether to show hidden files. Defaults to `false`.
  23. /// - allowSelectingFiles: Whether to allow selecting files (as opposed
  24. /// to directories) in the dialog. Defaults to `true`.
  25. /// - allowSelectingDirectories: Whether to allow selecting directories
  26. /// in the dialog. Defaults to `true`.
  27. /// - Returns: The URL of the user's chosen file, or `nil` if the user
  28. /// cancelled the dialog.
  29. public func callAsFunction(
  30. title: String = "Open",
  31. message: String = "",
  32. defaultButtonLabel: String = "Open",
  33. initialDirectory: URL? = nil,
  34. showHiddenFiles: Bool = false,
  35. allowSelectingFiles: Bool = true,
  36. allowSelectingDirectories: Bool = false
  37. ) async -> URL? {
  38. guard let backend = backend as? any BackendFeatures.FileOpenDialogs else {
  39. logger.warnOnce(Logger.Message(stringLiteral: "\(type(of: backend)) does not support file open dialogs"))
  40. return nil
  41. }
  42. func chooseFile<Backend: BackendFeatures.FileOpenDialogs>(backend: Backend) async -> URL? {
  43. await withCheckedContinuation { continuation in
  44. backend.runInMainThread {
  45. let window = self.window.value.map { $0 as! Backend.Window }
  46. backend.showOpenDialog(
  47. fileDialogOptions: FileDialogOptions(
  48. title: title,
  49. defaultButtonLabel: defaultButtonLabel,
  50. allowedContentTypes: [],
  51. showHiddenFiles: showHiddenFiles,
  52. allowOtherContentTypes: true,
  53. initialDirectory: initialDirectory
  54. ),
  55. openDialogOptions: OpenDialogOptions(
  56. allowSelectingFiles: allowSelectingFiles,
  57. allowSelectingDirectories: allowSelectingDirectories,
  58. allowMultipleSelections: false
  59. ),
  60. window: window
  61. ) { result in
  62. switch result {
  63. case .success(let url):
  64. continuation.resume(returning: url[0])
  65. case .cancelled:
  66. continuation.resume(returning: nil)
  67. }
  68. }
  69. }
  70. }
  71. }
  72. return await chooseFile(backend: backend)
  73. }
  74. }