From 2dd28540f123321ccc597df7c42bfb1d892dcd48 Mon Sep 17 00:00:00 2001
From: ameer2468 <33054370+ameer2468@users.noreply.github.com>
Date: Sun, 15 Oct 2023 13:47:03 +0300
Subject: [PATCH] [ENG-1288] Copy, cut, and paste shortcuts (#1591)
* copy paste shortcut
* add cut shortcut
---
.../app/$libraryId/Explorer/View/index.tsx | 9 ++-
interface/hooks/index.ts | 1 +
interface/hooks/useKeyCopyCutPaste.ts | 79 +++++++++++++++++++
3 files changed, 85 insertions(+), 4 deletions(-)
create mode 100644 interface/hooks/useKeyCopyCutPaste.ts
diff --git a/interface/app/$libraryId/Explorer/View/index.tsx b/interface/app/$libraryId/Explorer/View/index.tsx
index 7b404fb9a..40eb3bd2a 100644
--- a/interface/app/$libraryId/Explorer/View/index.tsx
+++ b/interface/app/$libraryId/Explorer/View/index.tsx
@@ -12,9 +12,9 @@ import {
import { createPortal } from 'react-dom';
import { useKey, useKeys } from 'rooks';
import { getItemObject, useLibraryContext, type Object } from '@sd/client';
-import { dialogManager, ModifierKeys, toast } from '@sd/ui';
+import { dialogManager, ModifierKeys } from '@sd/ui';
import { Loader } from '~/components';
-import { useKeyMatcher, useOperatingSystem } from '~/hooks';
+import { useKeyCopyPaste, useKeyMatcher, useOperatingSystem } from '~/hooks';
import { isNonEmpty } from '~/util';
import { usePlatform } from '~/util/Platform';
@@ -63,7 +63,6 @@ export default memo(
const quickPreview = useQuickPreviewContext();
const quickPreviewStore = useQuickPreviewStore();
const os = useOperatingSystem();
-
const { doubleClick } = useViewItemDoubleClick();
const { layoutMode } = explorer.useSettingsSnapshot();
@@ -109,6 +108,8 @@ export default memo(
doubleClick();
});
+ useKeyCopyPaste();
+
return (
<>
+
{props.icon
? isValidElement(props.icon)
? props.icon
diff --git a/interface/hooks/index.ts b/interface/hooks/index.ts
index 2afd384e5..92e9dfd3e 100644
--- a/interface/hooks/index.ts
+++ b/interface/hooks/index.ts
@@ -21,3 +21,4 @@ export * from './useZodRouteParams';
export * from './useZodSearchParams';
export * from './useIsTextTruncated';
export * from './useKeyMatcher';
+export * from './useKeyCopyCutPaste';
diff --git a/interface/hooks/useKeyCopyCutPaste.ts b/interface/hooks/useKeyCopyCutPaste.ts
new file mode 100644
index 000000000..40b9c0c2a
--- /dev/null
+++ b/interface/hooks/useKeyCopyCutPaste.ts
@@ -0,0 +1,79 @@
+import { getExplorerStore, useExplorerStore } from "~/app/$libraryId/Explorer/store";
+import { useExplorerSearchParams } from "~/app/$libraryId/Explorer/util";
+import { useExplorerContext } from "~/app/$libraryId/Explorer/Context";
+import { useKeyMatcher } from "./useKeyMatcher";
+import { useKeys } from "rooks";
+import {useItemsAsFilePaths, useLibraryMutation} from '@sd/client';
+import {toast} from '@sd/ui';
+
+
+export const useKeyCopyPaste = () => {
+ const {cutCopyState} = useExplorerStore();
+ const [{ path }] = useExplorerSearchParams();
+
+ const metaCtrlKey = useKeyMatcher('Meta').key;
+ const copyFiles = useLibraryMutation('files.copyFiles');
+ const cutFiles = useLibraryMutation('files.cutFiles');
+ const explorer = useExplorerContext();
+ const selectedFilePaths = useItemsAsFilePaths(Array.from(explorer.selectedItems));
+
+ useKeys([metaCtrlKey, 'KeyC'], (e) => {
+ e.stopPropagation();
+; if (explorer.parent?.type === 'Location') {
+ getExplorerStore().cutCopyState = {
+ sourceParentPath: path ?? '/',
+ sourceLocationId: explorer.parent.location.id,
+ sourcePathIds: selectedFilePaths.map((p) => p.id),
+ type: 'Copy'
+ };
+ }
+ });
+
+ useKeys([metaCtrlKey, 'KeyX'], (e) => {
+ e.stopPropagation();
+ if (explorer.parent?.type === 'Location') {
+ getExplorerStore().cutCopyState = {
+ sourceParentPath: path ?? '/',
+ sourceLocationId: explorer.parent.location.id,
+ sourcePathIds: selectedFilePaths.map((p) => p.id),
+ type: 'Cut'
+ };
+ }
+ });
+
+ useKeys([metaCtrlKey, 'KeyV'], async (e) => {
+ e.stopPropagation();
+ const parent = explorer.parent;
+ if (parent?.type === 'Location' && cutCopyState.type !== 'Idle' && path) {
+ const { type, sourcePathIds, sourceParentPath, sourceLocationId } = cutCopyState;
+
+ const sameLocation =
+ sourceLocationId === parent.location.id && sourceParentPath === path;
+
+ try {
+ if (type == 'Copy') {
+ await copyFiles.mutateAsync({
+ source_location_id: sourceLocationId,
+ sources_file_path_ids: [...sourcePathIds],
+ target_location_id: parent.location.id,
+ target_location_relative_directory_path: path
+ });
+ } else if (sameLocation) {
+ toast.error('File already exists in this location');
+ } else {
+ await cutFiles.mutateAsync({
+ source_location_id: sourceLocationId,
+ sources_file_path_ids: [...sourcePathIds],
+ target_location_id: parent.location.id,
+ target_location_relative_directory_path: path
+ });
+ }
+ } catch (error) {
+ toast.error({
+ title: `Failed to ${type.toLowerCase()} file`,
+ body: `Error: ${error}.`
+ });
+ }
+ }
+ });
+}