copy files

This commit is contained in:
2025-07-30 23:35:30 +02:00
parent 956c0c6f65
commit ea09b9c470
21 changed files with 552 additions and 46 deletions

View File

@@ -14,6 +14,7 @@ import {
import type { DirectoryEntry } from '#shared/types';
const warrenStore = useWarrenStore();
const copyStore = useCopyStore();
const renameDialog = useRenameDirectoryDialog();
const { entry, disabled } = defineProps<{
@@ -22,6 +23,14 @@ const { entry, disabled } = defineProps<{
}>();
const deleting = ref(false);
const isCopied = computed(
() =>
warrenStore.current != null &&
copyStore.file != null &&
warrenStore.current.warrenId === copyStore.file.warrenId &&
warrenStore.current.path === copyStore.file.path &&
entry.name === copyStore.file.name
);
async function submitDelete(force: boolean = false) {
if (warrenStore.current == null) {
@@ -89,6 +98,18 @@ function onDragStart(e: DragEvent) {
}
const onDrop = onDirectoryEntryDrop(entry);
function onCopy() {
if (warrenStore.current == null) {
return;
}
copyStore.copyFile(
warrenStore.current.warrenId,
warrenStore.current.path,
entry.name
);
}
</script>
<template>
@@ -96,7 +117,10 @@ const onDrop = onDirectoryEntryDrop(entry);
<ContextMenuTrigger>
<button
:disabled="warrenStore.loading || disabled"
class="bg-accent/30 border-border flex w-52 translate-0 flex-row gap-4 overflow-hidden rounded-md border-1 px-4 py-2 select-none"
:class="[
'bg-accent/30 border-border flex w-52 translate-0 flex-row gap-4 overflow-hidden rounded-md border-1 px-4 py-2 select-none',
isCopied && 'border-primary/50 border',
]"
draggable="true"
@dragstart="onDragStart"
@drop="onDrop"
@@ -124,6 +148,10 @@ const onDrop = onDirectoryEntryDrop(entry);
<Icon name="lucide:pencil" />
Rename
</ContextMenuItem>
<ContextMenuItem @select="onCopy">
<Icon name="lucide:copy" />
Copy
</ContextMenuItem>
<ContextMenuSeparator />

View File

@@ -6,11 +6,35 @@ import {
ContextMenuItem,
} from '@/components/ui/context-menu';
const dialog = useCreateDirectoryDialog();
const warrenStore = useWarrenStore();
const copyStore = useCopyStore();
const createDirectoryDialog = useCreateDirectoryDialog();
const pasting = ref<boolean>(false);
const validPaste = computed(
() =>
!pasting.value && copyStore.file != null && warrenStore.current != null
);
const props = defineProps<{
class?: string;
}>();
async function onPaste() {
if (!validPaste.value) {
return;
}
pasting.value = true;
await pasteFile(copyStore.file!, {
warrenId: warrenStore.current!.warrenId,
name: copyStore.file!.name,
path: warrenStore.current!.path,
});
pasting.value = false;
}
</script>
<template>
@@ -19,7 +43,11 @@ const props = defineProps<{
<slot />
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem @select="dialog.openDialog">
<ContextMenuItem @disabled="!validPaste" @select="onPaste">
<Icon name="lucide:clipboard-paste" />
Paste
</ContextMenuItem>
<ContextMenuItem @select="createDirectoryDialog.openDialog">
<Icon name="lucide:folder-plus" />
Create directory
</ContextMenuItem>

View File

@@ -335,3 +335,38 @@ export async function moveFile(
success: true,
};
}
export async function copyFile(
warrenId: string,
currentPath: string,
targetPath: string
): Promise<{ success: boolean }> {
const { status } = await useFetch(getApiUrl(`warrens/files/cp`), {
method: 'POST',
headers: getApiHeaders(),
body: JSON.stringify({
warrenId,
path: currentPath,
targetPath: targetPath,
}),
});
if (status.value !== 'success') {
toast.error('Copy', {
id: 'COPY_FILE_TOAST',
description: `Failed to copy file`,
});
return { success: false };
}
await refreshNuxtData('current-directory');
toast.success('Copy', {
id: 'COPY_FILE_TOAST',
description: `Successfully copied file`,
});
return {
success: true,
};
}

18
frontend/stores/copy.ts Normal file
View File

@@ -0,0 +1,18 @@
export const useCopyStore = defineStore('file-copy', {
state: () => ({
file: null as { warrenId: string; path: string; name: string } | null,
}),
actions: {
copyFile(warrenId: string, filePath: string, fileName: string) {
this.file = {
warrenId,
path: filePath,
name: fileName,
};
},
/** Removes the current file from the "clipboard" */
clearFile() {
this.file = null;
},
},
});

View File

@@ -1,4 +1,4 @@
import { moveFile } from '~/lib/api/warrens';
import { copyFile, moveFile } from '~/lib/api/warrens';
import type { DirectoryEntry } from '~/shared/types';
export function joinPaths(path: string, ...other: string[]): string {
@@ -58,3 +58,20 @@ export function onDirectoryEntryDrop(
await moveFile(warrenStore.current.warrenId, currentPath, targetPath);
};
}
export async function pasteFile(
current: { warrenId: string; path: string; name: string },
target: { warrenId: string; path: string; name: string }
): Promise<boolean> {
if (current.warrenId !== target.warrenId) {
throw new Error('Cross-warren copies are not supported yet');
}
const { success } = await copyFile(
current.warrenId,
joinPaths(current.path, current.name),
joinPaths(target.path, target.name)
);
return success;
}