delete multiple files with selection
This commit is contained in:
@@ -6,7 +6,6 @@ import {
|
||||
ContextMenuItem,
|
||||
ContextMenuSeparator,
|
||||
} from '@/components/ui/context-menu';
|
||||
import { deleteWarrenDirectory, deleteWarrenFile } from '~/lib/api/warrens';
|
||||
import type { DirectoryEntry } from '#shared/types';
|
||||
|
||||
const warrenStore = useWarrenStore();
|
||||
@@ -26,9 +25,9 @@ const {
|
||||
const emit = defineEmits<{
|
||||
'entry-click': [entry: DirectoryEntry, event: MouseEvent];
|
||||
'entry-download': [entry: DirectoryEntry];
|
||||
'entry-delete': [entry: DirectoryEntry, force: boolean];
|
||||
}>();
|
||||
|
||||
const deleting = ref(false);
|
||||
const isCopied = computed(
|
||||
() =>
|
||||
warrenStore.current != null &&
|
||||
@@ -39,32 +38,11 @@ const isCopied = computed(
|
||||
);
|
||||
const isSelected = computed(() => warrenStore.isSelected(entry));
|
||||
|
||||
async function submitDelete(force: boolean = false) {
|
||||
if (warrenStore.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleting.value = true;
|
||||
|
||||
if (entry.fileType === 'directory') {
|
||||
await deleteWarrenDirectory(
|
||||
warrenStore.current.warrenId,
|
||||
warrenStore.current.path,
|
||||
entry.name,
|
||||
force
|
||||
);
|
||||
} else {
|
||||
await deleteWarrenFile(
|
||||
warrenStore.current.warrenId,
|
||||
warrenStore.current.path,
|
||||
entry.name
|
||||
);
|
||||
}
|
||||
|
||||
deleting.value = false;
|
||||
function onDelete(force: boolean = false) {
|
||||
emit('entry-delete', entry, force);
|
||||
}
|
||||
|
||||
async function openRenameDialog() {
|
||||
function openRenameDialog() {
|
||||
renameDialog.openDialog(entry);
|
||||
}
|
||||
|
||||
@@ -183,15 +161,14 @@ function onClearCopy() {
|
||||
|
||||
<ContextMenuItem
|
||||
:class="[warrenStore.current == null && 'hidden']"
|
||||
@select="() => submitDelete(false)"
|
||||
@select="() => onDelete(false)"
|
||||
>
|
||||
<Icon name="lucide:trash-2" />
|
||||
Delete
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
v-if="entry.fileType === 'directory'"
|
||||
:class="[warrenStore.current == null && 'hidden']"
|
||||
@select="() => submitDelete(true)"
|
||||
@select="() => onDelete(true)"
|
||||
>
|
||||
<Icon
|
||||
class="text-destructive-foreground"
|
||||
|
||||
@@ -19,6 +19,7 @@ const {
|
||||
const emit = defineEmits<{
|
||||
'entry-click': [entry: DirectoryEntry, event: MouseEvent];
|
||||
'entry-download': [entry: DirectoryEntry];
|
||||
'entry-delete': [entry: DirectoryEntry, force: boolean];
|
||||
back: [];
|
||||
}>();
|
||||
|
||||
@@ -35,6 +36,10 @@ function onEntryClicked(entry: DirectoryEntry, event: MouseEvent) {
|
||||
function onEntryDownload(entry: DirectoryEntry) {
|
||||
emit('entry-download', entry);
|
||||
}
|
||||
|
||||
function onEntryDelete(entry: DirectoryEntry, force: boolean) {
|
||||
emit('entry-delete', entry, force);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -61,6 +66,7 @@ function onEntryDownload(entry: DirectoryEntry) {
|
||||
:draggable="entriesDraggable"
|
||||
@entry-click="onEntryClicked"
|
||||
@entry-download="onEntryDownload"
|
||||
@entry-delete="onEntryDelete"
|
||||
/>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
@@ -87,6 +87,40 @@ export async function createDirectory(
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
export async function warrenRm(
|
||||
warrenId: string,
|
||||
paths: string[],
|
||||
force: boolean
|
||||
): Promise<{ success: boolean }> {
|
||||
const { status } = await useFetch(getApiUrl(`warrens/files/rm`), {
|
||||
method: 'POST',
|
||||
headers: getApiHeaders(),
|
||||
body: JSON.stringify({
|
||||
warrenId,
|
||||
paths,
|
||||
force,
|
||||
}),
|
||||
});
|
||||
|
||||
const TOAST_TITLE = 'Delete';
|
||||
|
||||
if (status.value !== 'success') {
|
||||
toast.error(TOAST_TITLE, {
|
||||
id: 'WARREN_RM_TOAST',
|
||||
description: `Failed to delete directory`,
|
||||
});
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
await refreshNuxtData('current-directory');
|
||||
|
||||
toast.success(TOAST_TITLE, {
|
||||
id: 'WARREN_RM_TOAST',
|
||||
description: `Successfully deleted files`,
|
||||
});
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
export async function deleteWarrenDirectory(
|
||||
warrenId: string,
|
||||
path: string,
|
||||
@@ -104,7 +138,7 @@ export async function deleteWarrenDirectory(
|
||||
headers: getApiHeaders(),
|
||||
body: JSON.stringify({
|
||||
warrenId,
|
||||
path,
|
||||
paths: [path],
|
||||
force,
|
||||
}),
|
||||
});
|
||||
@@ -113,7 +147,7 @@ export async function deleteWarrenDirectory(
|
||||
|
||||
if (status.value !== 'success') {
|
||||
toast.error(TOAST_TITLE, {
|
||||
id: 'DELETE_DIRECTORY_TOAST',
|
||||
id: 'WARREN_RM_TOAST',
|
||||
description: `Failed to delete directory`,
|
||||
});
|
||||
return { success: false };
|
||||
@@ -122,7 +156,7 @@ export async function deleteWarrenDirectory(
|
||||
await refreshNuxtData('current-directory');
|
||||
|
||||
toast.success(TOAST_TITLE, {
|
||||
id: 'DELETE_DIRECTORY_TOAST',
|
||||
id: 'WARREN_RM_TOAST',
|
||||
description: `Successfully deleted ${directoryName}`,
|
||||
});
|
||||
return { success: true };
|
||||
@@ -144,7 +178,7 @@ export async function deleteWarrenFile(
|
||||
headers: getApiHeaders(),
|
||||
body: JSON.stringify({
|
||||
warrenId,
|
||||
path,
|
||||
paths: [path],
|
||||
force: false,
|
||||
}),
|
||||
});
|
||||
@@ -153,7 +187,7 @@ export async function deleteWarrenFile(
|
||||
|
||||
if (status.value !== 'success') {
|
||||
toast.error(TOAST_TITLE, {
|
||||
id: 'DELETE_FILE_TOAST',
|
||||
id: 'WARREN_RM_TOAST',
|
||||
description: `Failed to delete file`,
|
||||
});
|
||||
return { success: false };
|
||||
@@ -162,7 +196,7 @@ export async function deleteWarrenFile(
|
||||
await refreshNuxtData('current-directory');
|
||||
|
||||
toast.success(TOAST_TITLE, {
|
||||
id: 'DELETE_FILE_TOAST',
|
||||
id: 'WARREN_RM_TOAST',
|
||||
description: `Successfully deleted ${fileName}`,
|
||||
});
|
||||
return { success: true };
|
||||
|
||||
@@ -3,7 +3,13 @@ import { useDropZone } from '@vueuse/core';
|
||||
import { toast } from 'vue-sonner';
|
||||
import DirectoryListContextMenu from '~/components/DirectoryListContextMenu.vue';
|
||||
import RenameEntryDialog from '~/components/actions/RenameEntryDialog.vue';
|
||||
import { fetchFile, getWarrenDirectory } from '~/lib/api/warrens';
|
||||
import {
|
||||
deleteWarrenDirectory,
|
||||
deleteWarrenFile,
|
||||
fetchFile,
|
||||
getWarrenDirectory,
|
||||
warrenRm,
|
||||
} from '~/lib/api/warrens';
|
||||
import type { DirectoryEntry } from '~/shared/types';
|
||||
|
||||
definePageMeta({
|
||||
@@ -119,27 +125,39 @@ function onEntryDownload(entry: DirectoryEntry) {
|
||||
let downloadName: string;
|
||||
let downloadApiUrl: string;
|
||||
|
||||
const selectionSize = warrenStore.selection.size;
|
||||
|
||||
if (selectionSize === 0 || !warrenStore.isSelected(entry)) {
|
||||
const targets = getTargetsFromSelection(entry, warrenStore.selection);
|
||||
if (targets.length === 1) {
|
||||
downloadName =
|
||||
entry.fileType === 'directory' ? `${entry.name}.zip` : entry.name;
|
||||
entry.fileType === 'directory'
|
||||
? `${targets[0].name}.zip`
|
||||
: targets[0].name;
|
||||
downloadApiUrl = getApiUrl(
|
||||
`warrens/files/cat?warrenId=${warrenStore.current.warrenId}&paths=${joinPaths(warrenStore.current.path, entry.name)}`
|
||||
`warrens/files/cat?warrenId=${warrenStore.current.warrenId}&paths=${joinPaths(warrenStore.current.path, targets[0].name)}`
|
||||
);
|
||||
} else {
|
||||
downloadName = 'download.zip';
|
||||
const paths = Array.from(warrenStore.selection).map((entry) =>
|
||||
joinPaths(warrenStore.current!.path, entry.name)
|
||||
);
|
||||
const paths = targets
|
||||
.map((entry) => joinPaths(warrenStore.current!.path, entry.name))
|
||||
.join(':');
|
||||
|
||||
downloadApiUrl = getApiUrl(
|
||||
`warrens/files/cat?warrenId=${warrenStore.current.warrenId}&paths=${paths.join(':')}`
|
||||
`warrens/files/cat?warrenId=${warrenStore.current.warrenId}&paths=${paths}`
|
||||
);
|
||||
}
|
||||
|
||||
downloadFile(downloadName, downloadApiUrl);
|
||||
}
|
||||
async function onEntryDelete(entry: DirectoryEntry, force: boolean) {
|
||||
if (warrenStore.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targets = getTargetsFromSelection(entry, warrenStore.selection).map(
|
||||
(entry) => joinPaths(warrenStore.current!.path, entry.name)
|
||||
);
|
||||
|
||||
await warrenRm(warrenStore.current.warrenId, targets, force);
|
||||
}
|
||||
|
||||
function onBack() {
|
||||
warrenStore.backCurrentPath();
|
||||
@@ -162,6 +180,7 @@ function onBack() {
|
||||
:parent="warrenStore.current.dir.parent"
|
||||
@entry-click="onEntryClicked"
|
||||
@entry-download="onEntryDownload"
|
||||
@entry-delete="onEntryDelete"
|
||||
@back="onBack"
|
||||
/>
|
||||
</DirectoryListContextMenu>
|
||||
|
||||
17
frontend/utils/selection.ts
Normal file
17
frontend/utils/selection.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { DirectoryEntry } from '~/shared/types';
|
||||
|
||||
/** Converts a selection and the entry that triggered an action into the target entries
|
||||
* @param targetEntry - The entry that triggered an action
|
||||
* @param selection - The selected entries
|
||||
* @returns If there are no selected elements or the target was not included only the target is returned. Otherwise the selection is returned
|
||||
*/
|
||||
export function getTargetsFromSelection(
|
||||
targetEntry: DirectoryEntry,
|
||||
selection: Set<DirectoryEntry>
|
||||
): DirectoryEntry[] {
|
||||
if (selection.size === 0 || !selection.has(targetEntry)) {
|
||||
return [targetEntry];
|
||||
}
|
||||
|
||||
return Array.from(selection);
|
||||
}
|
||||
Reference in New Issue
Block a user