PresentFileSaveDialogAction.swift 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. import Foundation
  2. /// Presents a 'Save file' dialog fit for selecting a save destination.
  3. public struct PresentFileSaveDialogAction: Sendable {
  4. let backend: any BaseAppBackend
  5. let window: MainActorBox<Any?>
  6. /// Presents a 'Save file' dialog fit for selecting a save destination.
  7. ///
  8. /// - Parameters:
  9. /// - title: The dialog's title. Defaults to "Save".
  10. /// - message: The dialog's message. Defaults to an empty string.
  11. /// - defaultButtonLabel: The label for the dialog's default button.
  12. /// Defaults to "Save".
  13. /// - initialDirectory: The directory to start the dialog in. Defaults
  14. /// to `nil`, which lets the backend choose (usually it'll be the
  15. /// app's current working directory and/or the directory where the
  16. /// previous dialog was dismissed).
  17. /// - showHiddenFiles: Whether to show hidden files. Defaults to `false`.
  18. /// - nameFieldLabel: The placeholder label for the file name field.
  19. /// Defaults to `nil`, which uses the backend-specific default.
  20. /// - defaultFileName: The default file name. Defaults to `nil`, which
  21. /// uses the backend-specific default.
  22. /// - Returns: The URL of the user's chosen save destination, or `nil` if
  23. /// the user cancelled the dialog.
  24. public func callAsFunction(
  25. title: String = "Save",
  26. message: String = "",
  27. defaultButtonLabel: String = "Save",
  28. initialDirectory: URL? = nil,
  29. showHiddenFiles: Bool = false,
  30. nameFieldLabel: String? = nil,
  31. defaultFileName: String? = nil
  32. ) async -> URL? {
  33. guard let backend = backend as? any BackendFeatures.FileSaveDialogs else {
  34. logger.warnOnce(Logger.Message(stringLiteral: "\(type(of: backend)) does not support file save dialogs"))
  35. return nil
  36. }
  37. func chooseFile<Backend: BackendFeatures.FileSaveDialogs>(backend: Backend) async -> URL? {
  38. return await withCheckedContinuation { continuation in
  39. backend.runInMainThread {
  40. let window = self.window.value.map { $0 as! Backend.Window }
  41. backend.showSaveDialog(
  42. fileDialogOptions: FileDialogOptions(
  43. title: title,
  44. defaultButtonLabel: defaultButtonLabel,
  45. allowedContentTypes: [],
  46. showHiddenFiles: showHiddenFiles,
  47. allowOtherContentTypes: true,
  48. initialDirectory: initialDirectory
  49. ),
  50. saveDialogOptions: SaveDialogOptions(
  51. nameFieldLabel: nameFieldLabel,
  52. defaultFileName: defaultFileName
  53. ),
  54. window: window
  55. ) { result in
  56. switch result {
  57. case .success(let url):
  58. continuation.resume(returning: url)
  59. case .cancelled:
  60. continuation.resume(returning: nil)
  61. }
  62. }
  63. }
  64. }
  65. }
  66. return await chooseFile(backend: backend)
  67. }
  68. }