@@ -178,7 +178,13 @@ struct BrowserView: View {
178178 Log . warn ( " failed to access security scoped URL from file importer: \( url) " )
179179 }
180180 }
181- try ? self . dropFiles ( fu)
181+ do {
182+ try self . dropFiles ( fu)
183+ }
184+ catch {
185+ Log . warn ( " failed to drop file: \( error) " )
186+ self . error = error
187+ }
182188 for url in fu {
183189 url. stopAccessingSecurityScopedResource ( )
184190 }
@@ -321,6 +327,25 @@ struct BrowserView: View {
321327 }
322328 }
323329
330+ @ViewBuilder private func addMenu( ) -> some View {
331+ Menu {
332+ Button ( " Select files... " , systemImage: " plus " ) {
333+ showAddFilePicker = true
334+ }
335+
336+ #if os(iOS)
337+ Button ( " Paste files... " , systemImage: " document.on.clipboard " ) {
338+ Task {
339+ await self . dropItemProviders ( UIPasteboard . general. itemProviders)
340+ }
341+ } . disabled ( UIPasteboard . general. itemProviders. isEmpty)
342+ #endif
343+ } label: {
344+ Label ( " Add... " , systemImage: " plus " )
345+ } . disabled (
346+ !folderExists || !self . folder. isRegularFolder || self . folder. folderType ( ) == SushitrainFolderTypeReceiveOnly)
347+ }
348+
324349 @ViewBuilder private func folderMenu( ) -> some View {
325350 Menu {
326351 #if os(iOS)
@@ -339,17 +364,11 @@ struct BrowserView: View {
339364 NavigationLink ( destination: FileView ( file: entry, showPath: false , siblings: nil ) ) {
340365 Label ( " Subdirectory properties... " , systemImage: " folder.badge.gearshape " )
341366 }
342-
343- Divider ( )
344367 }
345368 }
346369
347370 if folderExists {
348- if self . folder. isRegularFolder && self . folder. folderType ( ) != SushitrainFolderTypeReceiveOnly {
349- Button ( " Add files... " , systemImage: " plus " ) {
350- showAddFilePicker = true
351- }
352- }
371+ self . addMenu ( )
353372
354373 #if os(iOS)
355374 Button ( openInFilesAppLabel, systemImage: " arrow.up.forward.app " ) {
@@ -419,6 +438,43 @@ struct BrowserView: View {
419438 try self . dropFiles ( urls)
420439 }
421440
441+ private func dropItemProviders( _ items: [ NSItemProvider ] ) async {
442+ var urls : [ URL ] = [ ]
443+
444+ for item in items {
445+ do {
446+ let tempURL : URL ? = try await withCheckedThrowingContinuation { cont in
447+ // TODO: add file coordination
448+ let _ = item. loadFileRepresentation ( for: UTType . data, openInPlace: true ) { tempURL, wasOpenedInPlace, err in
449+ if let err = err {
450+ cont. resume ( throwing: err)
451+ return
452+ }
453+ else {
454+ cont. resume ( returning: tempURL)
455+ }
456+ }
457+ }
458+
459+ if let tempURL = tempURL {
460+ urls. append ( tempURL)
461+ }
462+ }
463+ catch {
464+ Log . warn ( " failed to load file representation for item: \( error) " )
465+ }
466+ }
467+
468+ // Process the files
469+ do {
470+ try self . dropFiles ( urls)
471+ }
472+ catch {
473+ Log . warn ( " failed to drop files: \( error) " )
474+ self . error = error
475+ }
476+ }
477+
422478 private func dropFiles( _ urls: [ URL ] ) throws {
423479 // Find out the native location of our folder
424480 var error : NSError ? = nil
@@ -444,16 +500,23 @@ struct BrowserView: View {
444500 var pathsToSelect : [ String ] = [ ]
445501
446502 if FileManager . default. fileExists ( atPath: localNativeURL. path) {
503+ var retainedError : Error ? = nil
447504 for url in urls {
448- // Copy source to folder
449- let targetURL = localNativeURL. appendingPathComponent (
450- url. lastPathComponent, isDirectory: false )
451- try FileManager . default. copyItem ( at: url, to: targetURL)
452-
453- // Select the dropped file
454- if folder. isSelective ( ) {
455- let localURL = ( self . prefix. withoutEndingSlash + " / " + url. lastPathComponent) . withoutStartingSlash
456- pathsToSelect. append ( localURL)
505+ do {
506+ // Copy source to folder
507+ let targetURL = localNativeURL. appendingPathComponent (
508+ url. lastPathComponent, isDirectory: false )
509+ try FileManager . default. copyItem ( at: url, to: targetURL)
510+
511+ // Select the dropped file
512+ if folder. isSelective ( ) {
513+ let localURL = ( self . prefix. withoutEndingSlash + " / " + url. lastPathComponent) . withoutStartingSlash
514+ pathsToSelect. append ( localURL)
515+ }
516+ }
517+ catch {
518+ Log . warn ( " failed to copy a dropped file: \( error) " )
519+ retainedError = error
457520 }
458521 }
459522
@@ -462,6 +525,10 @@ struct BrowserView: View {
462525 }
463526
464527 try self . folder. rescanSubdirectory ( self . prefix)
528+
529+ if let re = retainedError {
530+ throw re
531+ }
465532 }
466533 }
467534
0 commit comments