[client] Handle paste file on web

This commit is contained in:
Abhishek Shroff
2025-05-02 00:35:03 +05:30
parent de9103b832
commit aa2de79880
4 changed files with 79 additions and 50 deletions

View File

@@ -156,7 +156,7 @@ Map<ShortcutActivator, Intent> get appShortcuts => const {
SingleActivator(LogicalKeyboardKey.keyN, control: true): NewFolderIntent(),
SingleActivator(LogicalKeyboardKey.keyU, control: true): UploadFilesIntent(),
SingleActivator(LogicalKeyboardKey.keyU, control: true, shift: true): UploadFolderIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteFromClipboardIntent(),
if (!kIsWeb) SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteFromClipboardIntent(),
// Top-level Navigation
SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): NavUpIntent(),

View File

@@ -9,11 +9,13 @@ import 'package:phylum/ui/destination_picker/destination_picker.dart';
import 'package:phylum/ui/explorer/explorer_actions.dart';
import 'package:phylum/ui/explorer/explorer_gesture_handler.dart';
import 'package:phylum/ui/explorer/path_view.dart';
import 'package:phylum/ui/explorer/resource_drop_and_drop.dart';
import 'package:phylum/util/dialogs.dart';
import 'package:phylum/util/upload_utils.dart';
import 'package:path/path.dart' as p;
import 'package:provider/provider.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:super_clipboard/super_clipboard.dart';
import 'explorer_controller.dart';
import 'folder_empty_view.dart';
@@ -47,14 +49,32 @@ class _ExplorerViewState extends State<ExplorerView> {
handleShareIntent(value).then((value) => ReceiveSharingIntent.instance.reset());
});
}
ClipboardEvents.instance?.registerPasteEventListener(_handlePaste);
}
@override
void dispose() {
_intentSub?.cancel();
ClipboardEvents.instance?.unregisterPasteEventListener(_handlePaste);
super.dispose();
}
void _handlePaste(ClipboardReadEvent event) async {
final folderId = context.read<ExplorerState>().folderId;
if (folderId == null) {
showAlertDialog(context, title: 'Cannot paste here');
return;
}
final reader = await event.getClipboardReader();
final items = reader.items;
for (final item in items) {
if (!mounted) return;
final err = await processDataReader(context, folderId, item);
if (!mounted || err == null) return;
showAlertDialog(context, title: 'Error uploading file', message: err, barrierDismissible: true);
}
}
Future<void> handleShareIntent(List<SharedMediaFile> files) async {
if (files.isEmpty) return;
final paths = files.map((f) => f.path).toList(growable: false);

View File

@@ -15,6 +15,7 @@ import 'package:phylum/util/dialogs.dart';
import 'package:phylum/util/logging.dart';
import 'package:phylum/util/upload_utils.dart';
import 'package:provider/provider.dart';
import 'package:super_clipboard/super_clipboard.dart';
import 'package:super_drag_and_drop/super_drag_and_drop.dart';
@pragma('vm:platform-const')
@@ -75,6 +76,56 @@ class _ResourceDragTargetState extends State<ResourceDragTarget> {
}
}
Future<String?> processDataReader(BuildContext context, String folderId, DataReader reader) async {
final uploadError = Completer<String?>();
reader.getFile(null, (file) async {
try {
final data = await file.readAll();
if (context.mounted) {
await uploadDirect(context, folderId, [
XFile.fromData(
data,
path: file.fileName,
name: file.fileName,
length: data.length,
)
]);
}
uploadError.complete(null);
} catch (e) {
logger.d('Error reading file data', error: e);
uploadError.complete(e.toString());
}
}, onError: (err) {
logger.e('Error calling getFile', error: err);
});
final err = await uploadError.future;
if (err == null) {
return null;
}
if (kIsWeb) {
return err;
}
// Try reading paths from filesystem
final c = Completer<String?>();
reader.getValue(
Formats.fileUri,
(value) {
final path = value?.toFilePath(windows: Platform.isWindows);
if (path == null) {
c.complete('Unable to get file path');
} else {
if (context.mounted) {
uploadRecursive(context, folderId, [path]).then((value) => c.complete(null));
}
}
},
onError: (value) => c.complete(value.toString()),
);
return c.future;
}
class ExternalDropRegion extends StatefulWidget {
final DropWidgetBuilder buildItem;
@@ -111,58 +162,14 @@ class _ExternalDropRegionState extends State<ExternalDropRegion> {
onPerformDrop: (event) async {
final folderId = context.read<ExplorerState>().folderId;
if (folderId == null) return;
final paths = <String>[];
for (final item in event.session.items) {
final reader = item.dataReader;
if (reader == null) continue;
final uploadError = Completer<String?>();
reader.getFile(null, (file) async {
try {
final data = await file.readAll();
if (context.mounted) {
await uploadDirect(context, folderId, [
XFile.fromData(
data,
path: file.fileName,
name: file.fileName,
length: data.length,
)
]);
}
uploadError.complete(null);
// ignore: empty_catches
} catch (e) {
logger.d('[onPerformDrop] Error reading file data', error: e);
uploadError.complete(e.toString());
}
}, onError: (err) {
logger.e('[onPerformDrop] error calling getFile', error: err);
});
final err = await uploadError.future;
if (err == null) {
continue;
}
if (kIsWeb) {
if (context.mounted) {
showAlertDialog(context, title: 'Error uploading file', message: err, barrierDismissible: true);
}
continue;
}
// Try reading paths from filesystem
final c = Completer<String?>();
reader.getValue(
Formats.fileUri,
(value) => c.complete(value?.toFilePath(windows: Platform.isWindows)),
onError: (value) => c.complete(null),
);
final path = await c.future;
if (path != null) {
paths.add(path);
}
}
if (context.mounted) {
uploadRecursive(context, folderId, paths);
if (!context.mounted) return;
final err = await processDataReader(context, folderId, reader);
if (!context.mounted) return;
showAlertDialog(context, title: 'Error uploading file', message: err, barrierDismissible: true);
}
setState(() => dropTargetActive = false);
},

View File

@@ -125,7 +125,9 @@ Future<void> uploadRecursive(BuildContext context, String folderId, Iterable<Str
);
if (id == null) {
if (!context.mounted) return;
final confirm = await showAlertDialog(context, title: 'Cancel operation?') ?? false;
final confirm =
await showAlertDialog(context, title: 'Cancel operation?', positiveText: 'No', negativeText: 'Yes') ??
false;
if (!confirm) return;
}
}