directory back up (parent) button + drag entry into parent to move
This commit is contained in:
38
frontend/components/DirectoryBackEntry.vue
Normal file
38
frontend/components/DirectoryBackEntry.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import type { DirectoryEntry } from '~/shared/types';
|
||||
|
||||
const { entry } = defineProps<{ entry: DirectoryEntry }>();
|
||||
|
||||
const warrenStore = useWarrenStore();
|
||||
|
||||
const onDrop = onDirectoryEntryDrop(entry, true);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
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"
|
||||
@contextmenu.prevent
|
||||
@click="() => warrenStore.backCurrentPath()"
|
||||
@drop="onDrop"
|
||||
>
|
||||
<div class="flex flex-row items-center">
|
||||
<Icon class="size-6" name="lucide:folder-up" />
|
||||
</div>
|
||||
<div
|
||||
class="flex w-full flex-col items-start justify-stretch gap-0 overflow-hidden text-left leading-6"
|
||||
>
|
||||
<span class="w-full truncate"
|
||||
>..
|
||||
<span class="text-muted-foreground truncate text-sm"
|
||||
>({{ entry.name }})</span
|
||||
></span
|
||||
>
|
||||
<NuxtTime
|
||||
v-if="entry.createdAt != null"
|
||||
:datetime="entry.createdAt * 1000"
|
||||
class="text-muted-foreground w-full truncate text-sm"
|
||||
relative
|
||||
></NuxtTime>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
@@ -89,28 +89,7 @@ function onDragStart(e: DragEvent) {
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
}
|
||||
|
||||
async function onDrop(e: DragEvent) {
|
||||
if (e.dataTransfer == null || warrenStore.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.fileType !== 'directory') {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = e.dataTransfer.getData('application/warren');
|
||||
|
||||
if (entry.name === fileName) {
|
||||
return;
|
||||
}
|
||||
|
||||
await moveFile(
|
||||
warrenStore.current.warrenId,
|
||||
warrenStore.current.path,
|
||||
fileName,
|
||||
`${warrenStore.current.path}/${entry.name}`
|
||||
);
|
||||
}
|
||||
const onDrop = onDirectoryEntryDrop(entry);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -118,7 +97,7 @@ async function onDrop(e: DragEvent) {
|
||||
<ContextMenuTrigger>
|
||||
<button
|
||||
:disabled="warrenStore.loading || disabled"
|
||||
class="bg-accent/30 border-border select-none, flex w-52 translate-0 flex-row gap-4 overflow-hidden rounded-md border-1 px-4 py-2"
|
||||
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"
|
||||
draggable="true"
|
||||
@dragstart="onDragStart"
|
||||
@drop="onDrop"
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import type { DirectoryEntry } from '#shared/types';
|
||||
|
||||
const { entries, isOverDropZone } = defineProps<{
|
||||
const { entries, parent, isOverDropZone } = defineProps<{
|
||||
entries: DirectoryEntry[];
|
||||
parent: DirectoryEntry | null;
|
||||
isOverDropZone?: boolean;
|
||||
}>();
|
||||
|
||||
@@ -23,6 +24,7 @@ const sortedEntries = computed(() =>
|
||||
<Icon class="size-16 animate-pulse" name="lucide:upload" />
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap gap-2">
|
||||
<DirectoryBackEntry v-if="parent != null" :entry="parent" />
|
||||
<DirectoryEntry
|
||||
v-for="entry in sortedEntries"
|
||||
:key="entry.name"
|
||||
|
||||
@@ -5,5 +5,5 @@ export function useWarrenPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `${store.current.warrenId}/${store.current.path}`;
|
||||
return `${store.current.warrenId}${store.current.path}`;
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ export async function getWarrens(): Promise<Record<string, WarrenData>> {
|
||||
export async function getWarrenDirectory(
|
||||
warrenId: string,
|
||||
path: string
|
||||
): Promise<DirectoryEntry[]> {
|
||||
): Promise<{ files: DirectoryEntry[]; parent: DirectoryEntry | null }> {
|
||||
const { data, error } = await useFetch<
|
||||
ApiResponse<{ files: DirectoryEntry[] }>
|
||||
ApiResponse<{ files: DirectoryEntry[]; parent: DirectoryEntry | null }>
|
||||
>(getApiUrl(`warrens/files/ls`), {
|
||||
method: 'POST',
|
||||
headers: getApiHeaders(),
|
||||
@@ -44,9 +44,9 @@ export async function getWarrenDirectory(
|
||||
throw error.value?.name;
|
||||
}
|
||||
|
||||
const { files } = data.value.data;
|
||||
const { files, parent } = data.value.data;
|
||||
|
||||
return files;
|
||||
return { files, parent };
|
||||
}
|
||||
|
||||
export async function createDirectory(
|
||||
|
||||
@@ -27,7 +27,7 @@ if (warrenStore.current == null) {
|
||||
});
|
||||
}
|
||||
|
||||
const entries = useAsyncData(
|
||||
const dirData = useAsyncData(
|
||||
'current-directory',
|
||||
async () => {
|
||||
if (warrenStore.current == null) {
|
||||
@@ -37,7 +37,7 @@ const entries = useAsyncData(
|
||||
loadingIndicator.start();
|
||||
warrenStore.loading = true;
|
||||
|
||||
const entries = await getWarrenDirectory(
|
||||
const { files, parent } = await getWarrenDirectory(
|
||||
warrenStore.current.warrenId,
|
||||
warrenStore.current.path
|
||||
);
|
||||
@@ -45,7 +45,7 @@ const entries = useAsyncData(
|
||||
warrenStore.loading = false;
|
||||
loadingIndicator.finish();
|
||||
|
||||
return entries;
|
||||
return { files, parent };
|
||||
},
|
||||
{ watch: [warrenPath] }
|
||||
).data;
|
||||
@@ -80,12 +80,13 @@ function onDrop(files: File[] | null, e: DragEvent) {
|
||||
<div ref="dropZoneRef" class="grow">
|
||||
<DirectoryListContextMenu class="w-full grow">
|
||||
<DirectoryList
|
||||
v-if="entries != null"
|
||||
v-if="dirData != null"
|
||||
:is-over-drop-zone="
|
||||
dropZone.isOverDropZone.value &&
|
||||
dropZone.files.value != null
|
||||
"
|
||||
:entries="entries"
|
||||
:entries="dirData.files"
|
||||
:parent="dirData.parent"
|
||||
/>
|
||||
</DirectoryListContextMenu>
|
||||
<RenameEntryDialog />
|
||||
|
||||
@@ -11,6 +11,7 @@ export type DirectoryEntry = {
|
||||
mimeType: string | null;
|
||||
/// Timestamp in seconds
|
||||
createdAt: number | null;
|
||||
isParent: boolean;
|
||||
};
|
||||
|
||||
export type UploadStatus =
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import type { DirectoryEntry } from '#shared/types';
|
||||
import type { WarrenData } from '#shared/types/warrens';
|
||||
import { getParentPath } from '~/utils/files';
|
||||
|
||||
export const useWarrenStore = defineStore('warrens', {
|
||||
state: () => ({
|
||||
@@ -29,6 +30,13 @@ export const useWarrenStore = defineStore('warrens', {
|
||||
|
||||
this.current.path += path;
|
||||
},
|
||||
backCurrentPath() {
|
||||
if (this.current == null || this.current.path === '/') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.current.path = getParentPath(this.current.path);
|
||||
},
|
||||
setCurrentWarrenPath(path: string) {
|
||||
if (this.current == null) {
|
||||
return;
|
||||
|
||||
49
frontend/utils/files.ts
Normal file
49
frontend/utils/files.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { moveFile } from '~/lib/api/warrens';
|
||||
import type { DirectoryEntry } from '~/shared/types';
|
||||
|
||||
export function getParentPath(path: string): string {
|
||||
const sliceEnd = Math.max(1, path.lastIndexOf('/'));
|
||||
return path.slice(0, sliceEnd);
|
||||
}
|
||||
|
||||
export function onDirectoryEntryDrop(
|
||||
entry: DirectoryEntry,
|
||||
isParent: boolean = false
|
||||
): (e: DragEvent) => void {
|
||||
return async (e: DragEvent) => {
|
||||
const warrenStore = useWarrenStore();
|
||||
|
||||
if (e.dataTransfer == null || warrenStore.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entry.fileType !== 'directory') {
|
||||
return;
|
||||
}
|
||||
|
||||
const fileName = e.dataTransfer.getData('application/warren');
|
||||
|
||||
if (entry.name === fileName) {
|
||||
return;
|
||||
}
|
||||
|
||||
let targetPath: string;
|
||||
|
||||
if (isParent) {
|
||||
targetPath = getParentPath(warrenStore.current.path);
|
||||
} else {
|
||||
targetPath = warrenStore.current.path;
|
||||
if (!targetPath.endsWith('/')) {
|
||||
targetPath += '/';
|
||||
}
|
||||
targetPath += entry.name;
|
||||
}
|
||||
|
||||
await moveFile(
|
||||
warrenStore.current.warrenId,
|
||||
warrenStore.current.path,
|
||||
fileName,
|
||||
targetPath
|
||||
);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user