From d64e068d0bee190126f3dc7641bb179a663cd7e4 Mon Sep 17 00:00:00 2001 From: Abhishek Shroff Date: Fri, 6 Sep 2024 22:11:13 +0530 Subject: [PATCH] [client] Cut/paste within app --- client/lib/app_shortcuts.dart | 11 ++- .../lib/ui/folder/folder_contents_view.dart | 68 ++++++++++++++++--- client/lib/ui/folder/folder_view.dart | 3 +- client/lib/util/upload_utils.dart | 4 +- 4 files changed, 65 insertions(+), 21 deletions(-) diff --git a/client/lib/app_shortcuts.dart b/client/lib/app_shortcuts.dart index 64fb938a..bf856867 100644 --- a/client/lib/app_shortcuts.dart +++ b/client/lib/app_shortcuts.dart @@ -26,12 +26,9 @@ class SelectAllIntent extends Intent { const SelectAllIntent(); } -class CutToClipboardIntent extends Intent { - const CutToClipboardIntent(); -} - class CopyToClipboardIntent extends Intent { - const CopyToClipboardIntent(); + final bool cut; + const CopyToClipboardIntent(this.cut); } class PasteFromClipboardIntent extends Intent { @@ -82,8 +79,8 @@ Map getAppShortcuts() { SingleActivator(LogicalKeyboardKey.keyN, control: true): NewFolderIntent(), SingleActivator(LogicalKeyboardKey.keyU, control: true): UploadFilesIntent(), SingleActivator(LogicalKeyboardKey.keyU, control: true, shift: true): UploadFolderIntent(), - SingleActivator(LogicalKeyboardKey.keyC, control: true): CopyToClipboardIntent(), - SingleActivator(LogicalKeyboardKey.keyX, control: true): CutToClipboardIntent(), + SingleActivator(LogicalKeyboardKey.keyC, control: true): CopyToClipboardIntent(false), + SingleActivator(LogicalKeyboardKey.keyX, control: true): CopyToClipboardIntent(true), SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteFromClipboardIntent(), }; } diff --git a/client/lib/ui/folder/folder_contents_view.dart b/client/lib/ui/folder/folder_contents_view.dart index 6639b533..075faee9 100644 --- a/client/lib/ui/folder/folder_contents_view.dart +++ b/client/lib/ui/folder/folder_contents_view.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:phylum/app_shortcuts.dart'; import 'package:phylum/libphylum/actions/action_resource_delete.dart'; +import 'package:phylum/libphylum/actions/action_resource_move.dart'; import 'package:phylum/libphylum/db/db.dart'; import 'package:phylum/libphylum/phylum_account.dart'; import 'package:phylum/ui/folder/folder_navigator_stack.dart'; @@ -16,9 +17,10 @@ import 'package:provider/provider.dart'; import 'package:super_clipboard/super_clipboard.dart'; class FolderContentsView extends StatefulWidget { + final String folderId; final List resources; - const FolderContentsView({super.key, required this.resources}); + FolderContentsView({required this.folderId, required this.resources}) : super(key: ValueKey(folderId)); @override State createState() => _FolderContentsViewState(); @@ -114,26 +116,70 @@ class _FolderContentsViewState extends State { deleteSelected(); return null; }), + CopyToClipboardIntent: CallbackAction(onInvoke: (i) async { + final account = context.read(); + final selectedIds = context.read().selected; + if (selectedIds.isEmpty) return; + final selected = resources.where((r) => selectedIds.contains(r.id)).toList(growable: false); + final items = selected.map((r) { + final uri = account.api.createUriBuilder('/open'); + uri.queryParameters['id'] = r.id; + if (i.cut) { + uri.queryParameters['cut'] = 'y'; + } + final uriData = Formats.uri(NamedUri(uri.build(), name: r.name)); + return DataWriterItem(suggestedName: r.name)..add(uriData); + }); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('${items.length} items ${i.cut ? 'cut' : 'copied'} to clipboard'))); + await SystemClipboard.instance?.write(items); + return null; + }), PasteFromClipboardIntent: CallbackAction(onInvoke: (i) async { + final account = context.read(); + final openUri = account.api.createUri('/open'); final clipboard = SystemClipboard.instance; if (clipboard == null) { return; } final reader = await clipboard.read(); - final files = reader.items.where((item) => item.canProvide(Formats.fileUri)); final paths = []; - if (files.isNotEmpty) { - for (final item in reader.items) { - final c = Completer(); - item.getValue(Formats.fileUri, (value) => c.complete(value?.toFilePath()), onError: (value) => c.complete(null)); - final path = await c.future; - if (path != null) { - paths.add(path); + final cutResources = []; + final copyResources = []; + for (final item in reader.items) { + final c = Completer(); + item.getValue(Formats.fileUri, (value) => c.complete(value?.toFilePath()), onError: (value) => c.complete(null)); + final filePath = await c.future; + if (filePath != null) { + paths.add(filePath); + } else { + final c = Completer(); + item.getValue(Formats.uri, (value) { + c.complete(value?.uri); + }, onError: (value) => c.complete(null)); + final uri = await c.future; + if (uri != null) { + if (uri.authority == openUri.authority && uri.path == openUri.path && uri.queryParameters.containsKey('id')) { + final resourceId = uri.queryParameters['id']!; + final cut = uri.queryParameters.containsKey('cut'); + final resource = await account.datastore.db.managers.resources.filter((f) => f.id.equals(resourceId)).getSingleOrNull(); + if (resource != null) { + (cut ? cutResources : copyResources).add(resource); + } + } } } - if (!context.mounted) return; - uploadRecursive(context, context.read().folderId, paths); } + if (!context.mounted) return; + uploadRecursive(context, widget.folderId, paths); + final parent = await account.datastore.db.managers.resources.filter((f) => f.id.equals(widget.folderId)).getSingleOrNull(); + if (parent != null) { + for (final r in cutResources) { + account.addAction(ResourceMoveAction(r: r, parent: parent)); + } + } + + await SystemClipboard.instance?.write([]); + return null; }), }, diff --git a/client/lib/ui/folder/folder_view.dart b/client/lib/ui/folder/folder_view.dart index 3fa54e74..200be260 100644 --- a/client/lib/ui/folder/folder_view.dart +++ b/client/lib/ui/folder/folder_view.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_state_notifier/flutter_state_notifier.dart'; import 'package:phylum/libphylum/phylum_account.dart'; import 'package:phylum/libphylum/requests/resource_detail_request.dart'; @@ -50,7 +49,7 @@ class _FolderViewState extends State { if (!snapshot.hasData) { return Container(); } - return FolderContentsView(resources: snapshot.data!); + return FolderContentsView(folderId: widget.id, resources: snapshot.data!); }), ), ), diff --git a/client/lib/util/upload_utils.dart b/client/lib/util/upload_utils.dart index c35eac9d..3314bb58 100644 --- a/client/lib/util/upload_utils.dart +++ b/client/lib/util/upload_utils.dart @@ -42,8 +42,10 @@ Future uploadRecursive(BuildContext context, String folderId, Iterable