dont use [...path] routing since it breaks building statically
This commit is contained in:
128
frontend/components/AppBreadcrumbs.vue
Normal file
128
frontend/components/AppBreadcrumbs.vue
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
Breadcrumb,
|
||||||
|
BreadcrumbList,
|
||||||
|
BreadcrumbItem,
|
||||||
|
BreadcrumbLink,
|
||||||
|
BreadcrumbPage,
|
||||||
|
BreadcrumbSeparator,
|
||||||
|
} from '@/components/ui/breadcrumb';
|
||||||
|
import type { BreadcrumbData } from '~/types';
|
||||||
|
const route = useRoute();
|
||||||
|
const store = useWarrenStore();
|
||||||
|
|
||||||
|
const routeCrumbs = computed<BreadcrumbData[]>(() => {
|
||||||
|
if (route.path === '/') {
|
||||||
|
return [{ name: '/', href: '/' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawCrumbs = route.path.split('/');
|
||||||
|
const crumbs: BreadcrumbData[] = [];
|
||||||
|
|
||||||
|
crumbs[0] = {
|
||||||
|
name: '/',
|
||||||
|
href: '/',
|
||||||
|
};
|
||||||
|
for (let i = 1; i < rawCrumbs.length; i++) {
|
||||||
|
crumbs.push({
|
||||||
|
name: rawCrumbs[i],
|
||||||
|
href: rawCrumbs.slice(0, i + 1).join('/'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return crumbs;
|
||||||
|
});
|
||||||
|
|
||||||
|
type WarrenBreadcrumbData = {
|
||||||
|
name: string;
|
||||||
|
action: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const warrenCrumbs = computed<WarrenBreadcrumbData[]>(() => {
|
||||||
|
if (store.current == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const warren = store.warrens[store.current.warrenId];
|
||||||
|
const rawCrumbs = store.current.path.split('/').filter((c) => c.length > 0);
|
||||||
|
const crumbs: WarrenBreadcrumbData[] = [
|
||||||
|
{
|
||||||
|
name: '/',
|
||||||
|
action: async () => {
|
||||||
|
await navigateTo({
|
||||||
|
path: '/',
|
||||||
|
});
|
||||||
|
store.clearCurrentWarren();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'warrens',
|
||||||
|
action: async () => {
|
||||||
|
await navigateTo({
|
||||||
|
path: '/warrens',
|
||||||
|
});
|
||||||
|
store.clearCurrentWarren();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: warren.name,
|
||||||
|
action: () => store.setCurrentWarren(warren.id, '/'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < rawCrumbs.length; i++) {
|
||||||
|
const path = rawCrumbs.slice(0, i + 1).join('/');
|
||||||
|
crumbs.push({
|
||||||
|
name: rawCrumbs[i],
|
||||||
|
action: () => store.setCurrentWarrenPath(path),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return crumbs;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbList>
|
||||||
|
<template v-if="store.current == null">
|
||||||
|
<template v-for="(crumb, i) in routeCrumbs" :key="i">
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<NuxtLink
|
||||||
|
v-if="i < routeCrumbs.length - 1"
|
||||||
|
:to="crumb.href"
|
||||||
|
as-child
|
||||||
|
>
|
||||||
|
<BreadcrumbLink>
|
||||||
|
{{ crumb.name }}
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</NuxtLink>
|
||||||
|
<BreadcrumbPage v-else>{{ crumb.name }}</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<BreadcrumbSeparator
|
||||||
|
v-if="i < routeCrumbs.length - 1"
|
||||||
|
class="hidden md:block"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
v-for="(crumb, i) in warrenCrumbs"
|
||||||
|
v-else-if="route.path.startsWith('/warrens')"
|
||||||
|
:key="i"
|
||||||
|
>
|
||||||
|
<BreadcrumbItem
|
||||||
|
v-if="i < warrenCrumbs.length - 1"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="crumb.action"
|
||||||
|
>
|
||||||
|
<BreadcrumbLink>
|
||||||
|
{{ crumb.name }}
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<BreadcrumbPage v-else>{{ crumb.name }}</BreadcrumbPage>
|
||||||
|
<BreadcrumbSeparator
|
||||||
|
v-if="i < warrenCrumbs.length - 1"
|
||||||
|
class="hidden md:block"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</BreadcrumbList>
|
||||||
|
</Breadcrumb>
|
||||||
|
</template>
|
||||||
@@ -9,8 +9,7 @@ import {
|
|||||||
import { deleteWarrenDirectory, deleteWarrenFile } from '~/lib/api/warrens';
|
import { deleteWarrenDirectory, deleteWarrenFile } from '~/lib/api/warrens';
|
||||||
import type { DirectoryEntry } from '~/types';
|
import type { DirectoryEntry } from '~/types';
|
||||||
|
|
||||||
const route = useRoute();
|
const warrenStore = useWarrenStore();
|
||||||
const warrenRoute = useWarrenRoute();
|
|
||||||
const renameDialog = useRenameDirectoryDialog();
|
const renameDialog = useRenameDirectoryDialog();
|
||||||
|
|
||||||
const { entry, disabled } = defineProps<{
|
const { entry, disabled } = defineProps<{
|
||||||
@@ -21,12 +20,25 @@ const { entry, disabled } = defineProps<{
|
|||||||
const deleting = ref(false);
|
const deleting = ref(false);
|
||||||
|
|
||||||
async function submitDelete(force: boolean = false) {
|
async function submitDelete(force: boolean = false) {
|
||||||
|
if (warrenStore.current == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
deleting.value = true;
|
deleting.value = true;
|
||||||
|
|
||||||
if (entry.fileType === 'directory') {
|
if (entry.fileType === 'directory') {
|
||||||
await deleteWarrenDirectory(warrenRoute.value, entry.name, force);
|
await deleteWarrenDirectory(
|
||||||
|
warrenStore.current.warrenId,
|
||||||
|
warrenStore.current.path,
|
||||||
|
entry.name,
|
||||||
|
force
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await deleteWarrenFile(warrenRoute.value, entry.name);
|
await deleteWarrenFile(
|
||||||
|
warrenStore.current.warrenId,
|
||||||
|
warrenStore.current.path,
|
||||||
|
entry.name
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleting.value = false;
|
deleting.value = false;
|
||||||
@@ -40,32 +52,32 @@ async function openRenameDialog() {
|
|||||||
<template>
|
<template>
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<ContextMenuTrigger>
|
<ContextMenuTrigger>
|
||||||
<NuxtLink
|
<button
|
||||||
:to="joinPaths(route.path, entry.name)"
|
|
||||||
:class="[
|
:class="[
|
||||||
'bg-accent/30 border-border flex w-52 flex-row gap-4 rounded-md border-1 px-4 py-2 select-none',
|
'bg-accent/30 border-border flex w-52 flex-row gap-4 overflow-hidden rounded-md border-1 px-4 py-2 select-none',
|
||||||
{
|
{
|
||||||
'pointer-events-none':
|
'pointer-events-none':
|
||||||
disabled || entry.fileType === 'file',
|
disabled || entry.fileType === 'file',
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
|
@click="() => warrenStore.addToCurrentWarrenPath(entry.name)"
|
||||||
>
|
>
|
||||||
<div class="flex flex-row items-center">
|
<div class="flex flex-row items-center">
|
||||||
<Icon class="size-6" :name="getFileIcon(entry.mimeType)" />
|
<Icon class="size-6" :name="getFileIcon(entry.mimeType)" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex flex-col gap-0 truncate overflow-hidden leading-6"
|
class="flex w-full flex-col items-start justify-stretch gap-0 overflow-hidden text-left leading-6"
|
||||||
>
|
>
|
||||||
<span>{{ entry.name }}</span>
|
<span class="w-full truncate">{{ entry.name }}</span>
|
||||||
<NuxtTime
|
<NuxtTime
|
||||||
v-if="entry.createdAt != null"
|
v-if="entry.createdAt != null"
|
||||||
:datetime="entry.createdAt * 1000"
|
:datetime="entry.createdAt * 1000"
|
||||||
class="text-muted-foreground text-sm"
|
class="text-muted-foreground w-full truncate text-sm"
|
||||||
relative
|
relative
|
||||||
></NuxtTime>
|
></NuxtTime>
|
||||||
</div>
|
</div>
|
||||||
</NuxtLink>
|
</button>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
<ContextMenuItem @select="openRenameDialog">
|
<ContextMenuItem @select="openRenameDialog">
|
||||||
|
|||||||
@@ -10,20 +10,28 @@ import {
|
|||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { createDirectory } from '~/lib/api/warrens';
|
import { createDirectory } from '~/lib/api/warrens';
|
||||||
|
|
||||||
const warrenRoute = useWarrenRoute();
|
const warrenStore = useWarrenStore();
|
||||||
const dialog = useCreateDirectoryDialog();
|
const dialog = useCreateDirectoryDialog();
|
||||||
|
|
||||||
const creating = ref(false);
|
const creating = ref(false);
|
||||||
const directoryNameValid = computed(() => dialog.value.trim().length > 0);
|
const directoryNameValid = computed(() => dialog.value.trim().length > 0);
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
if (!directoryNameValid.value || creating.value) {
|
if (
|
||||||
|
!directoryNameValid.value ||
|
||||||
|
creating.value ||
|
||||||
|
warrenStore.current == null
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
creating.value = true;
|
creating.value = true;
|
||||||
|
|
||||||
const { success } = await createDirectory(warrenRoute.value, dialog.value);
|
const { success } = await createDirectory(
|
||||||
|
warrenStore.current.warrenId,
|
||||||
|
warrenStore.current.path,
|
||||||
|
dialog.value
|
||||||
|
);
|
||||||
|
|
||||||
creating.value = false;
|
creating.value = false;
|
||||||
|
|
||||||
|
|||||||
@@ -9,21 +9,27 @@ import {
|
|||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { renameWarrenEntry } from '~/lib/api/warrens';
|
import { renameWarrenEntry } from '~/lib/api/warrens';
|
||||||
|
|
||||||
const warrenRoute = useWarrenRoute();
|
const warrenStore = useWarrenStore();
|
||||||
const dialog = useRenameDirectoryDialog();
|
const dialog = useRenameDirectoryDialog();
|
||||||
|
|
||||||
const renaming = ref(false);
|
const renaming = ref(false);
|
||||||
const directoryNameValid = computed(() => dialog.value.trim().length > 0);
|
const directoryNameValid = computed(() => dialog.value.trim().length > 0);
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
if (dialog.entry == null || !directoryNameValid.value || renaming.value) {
|
if (
|
||||||
|
dialog.entry == null ||
|
||||||
|
!directoryNameValid.value ||
|
||||||
|
renaming.value ||
|
||||||
|
warrenStore.current == null
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
renaming.value = true;
|
renaming.value = true;
|
||||||
|
|
||||||
const { success } = await renameWarrenEntry(
|
const { success } = await renameWarrenEntry(
|
||||||
warrenRoute.value,
|
warrenStore.current.warrenId,
|
||||||
|
warrenStore.current.path,
|
||||||
dialog.entry.name,
|
dialog.entry.name,
|
||||||
dialog.value
|
dialog.value
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,9 +16,16 @@ import { uploadToWarren } from '~/lib/api/warrens';
|
|||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import UploadListEntry from './UploadListEntry.vue';
|
import UploadListEntry from './UploadListEntry.vue';
|
||||||
|
|
||||||
|
const warrenStore = useWarrenStore();
|
||||||
const uploadStore = useUploadStore();
|
const uploadStore = useUploadStore();
|
||||||
|
|
||||||
const warrenRoute = useWarrenRoute();
|
const currentAndUploadRouteMatch = computed(
|
||||||
|
() =>
|
||||||
|
uploadStore.destination != null &&
|
||||||
|
warrenStore.current != null &&
|
||||||
|
uploadStore.destination.warrenId == warrenStore.current.warrenId &&
|
||||||
|
uploadStore.destination.path == warrenStore.current.path
|
||||||
|
);
|
||||||
|
|
||||||
const fileInputElement = ref<HTMLInputElement>(
|
const fileInputElement = ref<HTMLInputElement>(
|
||||||
null as unknown as HTMLInputElement
|
null as unknown as HTMLInputElement
|
||||||
@@ -28,7 +35,7 @@ const uploading = ref(false);
|
|||||||
const presentPaths = new Set<string>();
|
const presentPaths = new Set<string>();
|
||||||
|
|
||||||
function onFilesChanged(event: Event) {
|
function onFilesChanged(event: Event) {
|
||||||
if (warrenRoute.value == null) {
|
if (warrenStore.current == null) {
|
||||||
toast.warning('Upload', {
|
toast.warning('Upload', {
|
||||||
description: 'Enter a warren before attempting to upload files',
|
description: 'Enter a warren before attempting to upload files',
|
||||||
});
|
});
|
||||||
@@ -45,9 +52,9 @@ function onFilesChanged(event: Event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uploadStore.path == null) {
|
if (uploadStore.destination == null) {
|
||||||
uploadStore.path = warrenRoute.value;
|
uploadStore.destination = warrenStore.current;
|
||||||
} else if (uploadStore.path !== warrenRoute.value) {
|
} else if (currentAndUploadRouteMatch.value) {
|
||||||
toast.warning('Upload', {
|
toast.warning('Upload', {
|
||||||
description:
|
description:
|
||||||
'The unfinished items belong to a different directory. Remove them before attempting to upload to a different directory.',
|
'The unfinished items belong to a different directory. Remove them before attempting to upload to a different directory.',
|
||||||
@@ -78,7 +85,7 @@ function removeFile(index: number) {
|
|||||||
presentPaths.delete(file.data.name);
|
presentPaths.delete(file.data.name);
|
||||||
|
|
||||||
if (uploadStore.files.length < 1) {
|
if (uploadStore.files.length < 1) {
|
||||||
uploadStore.path = null;
|
uploadStore.destination = null;
|
||||||
uploadStore.progress = null;
|
uploadStore.progress = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,7 +101,7 @@ function clearCompletedFiles() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (uploadStore.files.length < 1) {
|
if (uploadStore.files.length < 1) {
|
||||||
uploadStore.path = null;
|
uploadStore.destination = null;
|
||||||
uploadStore.progress = null;
|
uploadStore.progress = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,7 +133,7 @@ function updateFileListStatus(loadedBytes: number, ended: boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
if (uploadStore.path == null) {
|
if (uploadStore.destination == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +161,8 @@ async function submit() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { success } = await uploadToWarren(
|
const { success } = await uploadToWarren(
|
||||||
uploadStore.path,
|
uploadStore.destination.warrenId,
|
||||||
|
uploadStore.destination.path,
|
||||||
dt.files,
|
dt.files,
|
||||||
(loaded, total) => {
|
(loaded, total) => {
|
||||||
if (uploadStore.progress != null) {
|
if (uploadStore.progress != null) {
|
||||||
@@ -192,10 +200,13 @@ async function submit() {
|
|||||||
<DialogDescription
|
<DialogDescription
|
||||||
>Upload files to
|
>Upload files to
|
||||||
<span class="text-foreground">{{
|
<span class="text-foreground">{{
|
||||||
uploadStore.path == null ||
|
currentAndUploadRouteMatch ||
|
||||||
warrenRoute === uploadStore.path
|
uploadStore.destination == null
|
||||||
? 'the current directory'
|
? 'the current directory'
|
||||||
: routeWithWarrenName(uploadStore.path)
|
: routeWithWarrenName(
|
||||||
|
uploadStore.destination!.warrenId,
|
||||||
|
uploadStore.destination!.path
|
||||||
|
)
|
||||||
}}</span></DialogDescription
|
}}</span></DialogDescription
|
||||||
>
|
>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import UploadDialog from '~/components/actions/UploadDialog.vue';
|
|||||||
const uploadStore = useUploadStore();
|
const uploadStore = useUploadStore();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const breadcrumbs = computed(() => getBreadcrumbs(route.path));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -27,33 +25,7 @@ const breadcrumbs = computed(() => getBreadcrumbs(route.path));
|
|||||||
<div class="flex w-full items-center gap-2 px-4">
|
<div class="flex w-full items-center gap-2 px-4">
|
||||||
<SidebarTrigger class="[&_svg]:size-4" />
|
<SidebarTrigger class="[&_svg]:size-4" />
|
||||||
<Separator orientation="vertical" class="mr-2 !h-4" />
|
<Separator orientation="vertical" class="mr-2 !h-4" />
|
||||||
<Breadcrumb>
|
<AppBreadcrumbs />
|
||||||
<BreadcrumbList>
|
|
||||||
<template
|
|
||||||
v-for="(crumb, i) in breadcrumbs"
|
|
||||||
:key="i"
|
|
||||||
>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<NuxtLink
|
|
||||||
v-if="i < breadcrumbs.length - 1"
|
|
||||||
:to="crumb.href"
|
|
||||||
as-child
|
|
||||||
>
|
|
||||||
<BreadcrumbLink>
|
|
||||||
{{ crumb.name }}
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</NuxtLink>
|
|
||||||
<BreadcrumbPage v-else>{{
|
|
||||||
crumb.name
|
|
||||||
}}</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<BreadcrumbSeparator
|
|
||||||
v-if="i < breadcrumbs.length - 1"
|
|
||||||
class="hidden md:block"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="ml-auto flex flex-row-reverse items-center gap-2"
|
class="ml-auto flex flex-row-reverse items-center gap-2"
|
||||||
|
|||||||
@@ -27,17 +27,9 @@ export async function getWarrens(): Promise<Record<string, Warren>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getWarrenDirectory(
|
export async function getWarrenDirectory(
|
||||||
|
warrenId: string,
|
||||||
path: string
|
path: string
|
||||||
): Promise<DirectoryEntry[]> {
|
): Promise<DirectoryEntry[]> {
|
||||||
// eslint-disable-next-line prefer-const
|
|
||||||
let [warrenId, rest] = splitOnce(path, '/');
|
|
||||||
|
|
||||||
if (rest == null) {
|
|
||||||
rest = '/';
|
|
||||||
} else {
|
|
||||||
rest = '/' + decodeURI(rest);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data, error } = await useFetch<
|
const { data, error } = await useFetch<
|
||||||
ApiResponse<{ files: DirectoryEntry[] }>
|
ApiResponse<{ files: DirectoryEntry[] }>
|
||||||
>(getApiUrl(`warrens/files`), {
|
>(getApiUrl(`warrens/files`), {
|
||||||
@@ -47,7 +39,7 @@ export async function getWarrenDirectory(
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
warrenId,
|
warrenId,
|
||||||
path: rest,
|
path,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -61,19 +53,15 @@ export async function getWarrenDirectory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createDirectory(
|
export async function createDirectory(
|
||||||
|
warrenId: string,
|
||||||
path: string,
|
path: string,
|
||||||
directoryName: string
|
directoryName: string
|
||||||
): Promise<{ success: boolean }> {
|
): Promise<{ success: boolean }> {
|
||||||
// eslint-disable-next-line prefer-const
|
if (!path.endsWith('/')) {
|
||||||
let [warrenId, rest] = splitOnce(path, '/');
|
path += '/';
|
||||||
|
|
||||||
if (rest == null) {
|
|
||||||
rest = '/';
|
|
||||||
} else {
|
|
||||||
rest = '/' + decodeURI(rest) + '/';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rest += directoryName;
|
path += directoryName;
|
||||||
|
|
||||||
const { status } = await useFetch(getApiUrl(`warrens/files/directory`), {
|
const { status } = await useFetch(getApiUrl(`warrens/files/directory`), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -82,7 +70,7 @@ export async function createDirectory(
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
warrenId,
|
warrenId,
|
||||||
path: rest,
|
path,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -105,20 +93,16 @@ export async function createDirectory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteWarrenDirectory(
|
export async function deleteWarrenDirectory(
|
||||||
|
warrenId: string,
|
||||||
path: string,
|
path: string,
|
||||||
directoryName: string,
|
directoryName: string,
|
||||||
force: boolean
|
force: boolean
|
||||||
): Promise<{ success: boolean }> {
|
): Promise<{ success: boolean }> {
|
||||||
// eslint-disable-next-line prefer-const
|
if (!path.endsWith('/')) {
|
||||||
let [warrenId, rest] = splitOnce(path, '/');
|
path += '/';
|
||||||
|
|
||||||
if (rest == null) {
|
|
||||||
rest = '/';
|
|
||||||
} else {
|
|
||||||
rest = '/' + decodeURI(rest) + '/';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rest += directoryName;
|
path += directoryName;
|
||||||
|
|
||||||
const { status } = await useFetch(getApiUrl(`warrens/files/directory`), {
|
const { status } = await useFetch(getApiUrl(`warrens/files/directory`), {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@@ -127,7 +111,7 @@ export async function deleteWarrenDirectory(
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
warrenId,
|
warrenId,
|
||||||
path: rest,
|
path,
|
||||||
force,
|
force,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -152,19 +136,15 @@ export async function deleteWarrenDirectory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteWarrenFile(
|
export async function deleteWarrenFile(
|
||||||
|
warrenId: string,
|
||||||
path: string,
|
path: string,
|
||||||
fileName: string
|
fileName: string
|
||||||
): Promise<{ success: boolean }> {
|
): Promise<{ success: boolean }> {
|
||||||
// eslint-disable-next-line prefer-const
|
if (!path.endsWith('/')) {
|
||||||
let [warrenId, rest] = splitOnce(path, '/');
|
path += '/';
|
||||||
|
|
||||||
if (rest == null) {
|
|
||||||
rest = '/';
|
|
||||||
} else {
|
|
||||||
rest = '/' + decodeURI(rest) + '/';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rest += fileName;
|
path += fileName;
|
||||||
|
|
||||||
const { status } = await useFetch(getApiUrl(`warrens/files/file`), {
|
const { status } = await useFetch(getApiUrl(`warrens/files/file`), {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@@ -173,7 +153,7 @@ export async function deleteWarrenFile(
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
warrenId,
|
warrenId,
|
||||||
path: rest,
|
path,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,19 +177,11 @@ export async function deleteWarrenFile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function uploadToWarren(
|
export async function uploadToWarren(
|
||||||
|
warrenId: string,
|
||||||
path: string,
|
path: string,
|
||||||
files: FileList,
|
files: FileList,
|
||||||
onProgress: ((loaded: number, total: number) => void) | undefined
|
onProgress: ((loaded: number, total: number) => void) | undefined
|
||||||
): Promise<{ success: boolean }> {
|
): Promise<{ success: boolean }> {
|
||||||
// eslint-disable-next-line prefer-const
|
|
||||||
let [warrenId, rest] = splitOnce(path, '/');
|
|
||||||
|
|
||||||
if (rest == null) {
|
|
||||||
rest = '/';
|
|
||||||
} else {
|
|
||||||
rest = '/' + decodeURI(rest);
|
|
||||||
}
|
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open('POST', getApiUrl(`warrens/files/upload`));
|
xhr.open('POST', getApiUrl(`warrens/files/upload`));
|
||||||
xhr.upload.onprogress = (e) => {
|
xhr.upload.onprogress = (e) => {
|
||||||
@@ -232,7 +204,7 @@ export async function uploadToWarren(
|
|||||||
|
|
||||||
const body = new FormData();
|
const body = new FormData();
|
||||||
body.set('warrenId', warrenId);
|
body.set('warrenId', warrenId);
|
||||||
body.set('path', rest);
|
body.set('path', path);
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
body.append('files', file);
|
body.append('files', file);
|
||||||
}
|
}
|
||||||
@@ -260,20 +232,15 @@ export async function uploadToWarren(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function renameWarrenEntry(
|
export async function renameWarrenEntry(
|
||||||
|
warrenId: string,
|
||||||
path: string,
|
path: string,
|
||||||
currentName: string,
|
currentName: string,
|
||||||
newName: string
|
newName: string
|
||||||
): Promise<{ success: boolean }> {
|
): Promise<{ success: boolean }> {
|
||||||
// eslint-disable-next-line prefer-const
|
if (!path.endsWith('/')) {
|
||||||
let [warrenId, rest] = splitOnce(path, '/');
|
path += '/';
|
||||||
|
|
||||||
if (rest == null) {
|
|
||||||
rest = '/';
|
|
||||||
} else {
|
|
||||||
rest = '/' + decodeURI(rest) + '/';
|
|
||||||
}
|
}
|
||||||
|
path += currentName;
|
||||||
rest += currentName;
|
|
||||||
|
|
||||||
const { status } = await useFetch(getApiUrl(`warrens/files/rename`), {
|
const { status } = await useFetch(getApiUrl(`warrens/files/rename`), {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
@@ -282,7 +249,7 @@ export async function renameWarrenEntry(
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
warrenId,
|
warrenId,
|
||||||
path: rest,
|
path,
|
||||||
newName,
|
newName,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,9 +7,24 @@ definePageMeta({
|
|||||||
middleware: ['authenticated'],
|
middleware: ['authenticated'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const entries = useAsyncData('current-directory', () =>
|
const warrenStore = useWarrenStore();
|
||||||
getWarrenDirectory(useWarrenRoute().value)
|
|
||||||
).data;
|
if (warrenStore.current == null) {
|
||||||
|
await navigateTo({
|
||||||
|
path: '/warrens',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = useAsyncData('current-directory', async () => {
|
||||||
|
if (warrenStore.current == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await getWarrenDirectory(
|
||||||
|
warrenStore.current.warrenId,
|
||||||
|
warrenStore.current.path
|
||||||
|
);
|
||||||
|
}).data;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -1,26 +1,35 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
import type { Warren } from '~/types/warrens';
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: ['authenticated'],
|
middleware: ['authenticated'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = useWarrenStore();
|
const store = useWarrenStore();
|
||||||
|
|
||||||
|
function selectWarren(warren: Warren) {
|
||||||
|
store.setCurrentWarren(warren.id, '/');
|
||||||
|
navigateTo({
|
||||||
|
path: '/warrens/files',
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ScrollArea class="h-full w-full">
|
<ScrollArea class="h-full w-full">
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<NuxtLink
|
<Button
|
||||||
v-for="(warren, uuid) in store.warrens"
|
v-for="(warren, uuid) in store.warrens"
|
||||||
:key="uuid"
|
:key="uuid"
|
||||||
:to="`/warrens/${uuid}`"
|
class="h-12 w-44"
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
@click="() => selectWarren(warren)"
|
||||||
>
|
>
|
||||||
<Button class="h-12 w-44" variant="outline" size="lg">
|
<Icon name="lucide:folder-root" />
|
||||||
<Icon name="lucide:folder-root" />
|
<span clas="truncate">{{ warren.name }}</span>
|
||||||
<span clas="truncate">{{ warren.name }}</span>
|
</Button>
|
||||||
</Button>
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,20 +2,48 @@ import { defineStore } from 'pinia';
|
|||||||
import type { DirectoryEntry } from '~/types';
|
import type { DirectoryEntry } from '~/types';
|
||||||
import type { Warren } from '~/types/warrens';
|
import type { Warren } from '~/types/warrens';
|
||||||
|
|
||||||
export const useWarrenStore = defineStore<
|
export const useWarrenStore = defineStore('warrens', {
|
||||||
'warrens',
|
|
||||||
{
|
|
||||||
warrens: Record<string, Warren>;
|
|
||||||
}
|
|
||||||
>('warrens', {
|
|
||||||
state: () => ({
|
state: () => ({
|
||||||
warrens: {},
|
warrens: {} as Record<string, Warren>,
|
||||||
upload: null,
|
current: null as { warrenId: string; path: string } | null,
|
||||||
}),
|
}),
|
||||||
});
|
actions: {
|
||||||
|
async setCurrentWarren(warrenId: string, path: string) {
|
||||||
|
this.current = {
|
||||||
|
warrenId,
|
||||||
|
path,
|
||||||
|
};
|
||||||
|
await refreshNuxtData('current-directory');
|
||||||
|
},
|
||||||
|
async addToCurrentWarrenPath(path: string) {
|
||||||
|
if (this.current == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
export const useWarrenRoute = () =>
|
if (!this.current.path.endsWith('/')) {
|
||||||
computed(() => useRoute().path.split('/warrens/')[1]);
|
this.current.path += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.current.path += path;
|
||||||
|
await refreshNuxtData('current-directory');
|
||||||
|
},
|
||||||
|
async setCurrentWarrenPath(path: string) {
|
||||||
|
if (this.current == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!path.startsWith('/')) {
|
||||||
|
path = '/' + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.current.path = path;
|
||||||
|
await refreshNuxtData('current-directory');
|
||||||
|
},
|
||||||
|
clearCurrentWarren() {
|
||||||
|
this.current = null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const useCreateDirectoryDialog = defineStore('create_directory_dialog', {
|
export const useCreateDirectoryDialog = defineStore('create_directory_dialog', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const useUploadStore = defineStore<
|
|||||||
{
|
{
|
||||||
startIndex: number;
|
startIndex: number;
|
||||||
files: UploadFile[];
|
files: UploadFile[];
|
||||||
path: string | null;
|
destination: { warrenId: string; path: string } | null;
|
||||||
progress: {
|
progress: {
|
||||||
loadedBytes: number;
|
loadedBytes: number;
|
||||||
totalBytes: number;
|
totalBytes: number;
|
||||||
@@ -15,7 +15,7 @@ export const useUploadStore = defineStore<
|
|||||||
state: () => ({
|
state: () => ({
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
files: [],
|
files: [],
|
||||||
path: null,
|
destination: null,
|
||||||
progress: null,
|
progress: null,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ export function getApiUrl(path: string): string {
|
|||||||
* @param path - The warren path (e.g. `a3f79579-9155-4492-a579-b0253c8d3bf8/my-directory/`)
|
* @param path - The warren path (e.g. `a3f79579-9155-4492-a579-b0253c8d3bf8/my-directory/`)
|
||||||
* @returns A prettier path `a3f79579-9155-4492-a579-b0253c8d3bf8/my-directory` -> `my-warren/my-directory`
|
* @returns A prettier path `a3f79579-9155-4492-a579-b0253c8d3bf8/my-directory` -> `my-warren/my-directory`
|
||||||
*/
|
*/
|
||||||
export function routeWithWarrenName(path: string): string {
|
export function routeWithWarrenName(warrenId: string, path: string): string {
|
||||||
const warrens = useWarrenStore().warrens;
|
const warrenStore = useWarrenStore();
|
||||||
|
|
||||||
const id = path.split('/')[0];
|
if (!(warrenId in warrenStore.warrens)) {
|
||||||
|
|
||||||
if (!(id in warrens)) {
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
return path.replace(id, warrens[id].name);
|
const warrenName = warrenStore.warrens[warrenId].name;
|
||||||
|
|
||||||
|
return `${warrenName}${path}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +1,9 @@
|
|||||||
import type { BreadcrumbData } from '~/types';
|
|
||||||
|
|
||||||
export function getBreadcrumbs(path: string): BreadcrumbData[] {
|
|
||||||
const { warrens } = useWarrenStore();
|
|
||||||
|
|
||||||
const crumbs = path
|
|
||||||
.split('/')
|
|
||||||
.filter((v) => v.length > 0)
|
|
||||||
.map((v) => ({
|
|
||||||
name: v,
|
|
||||||
href: '#',
|
|
||||||
}));
|
|
||||||
|
|
||||||
crumbs.unshift({ name: '/', href: '/' });
|
|
||||||
|
|
||||||
for (let i = 1; i < crumbs.length; i++) {
|
|
||||||
crumbs[i].name = decodeURI(crumbs[i].name);
|
|
||||||
crumbs[i].href =
|
|
||||||
'/' +
|
|
||||||
path
|
|
||||||
.split('/')
|
|
||||||
.slice(1, i + 1)
|
|
||||||
.join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
crumbs.length >= 3 &&
|
|
||||||
crumbs[1].href === '/warrens' &&
|
|
||||||
crumbs[2].name in warrens
|
|
||||||
) {
|
|
||||||
crumbs[2].name = warrens[crumbs[2].name].name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return crumbs;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function preventDefault(event: Event) {
|
export function preventDefault(event: Event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function joinPaths(base: string, other: string): string {
|
|
||||||
if (!base.endsWith('/')) {
|
|
||||||
base += '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
return base + other;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function splitOnce(
|
export function splitOnce(
|
||||||
str: string,
|
str: string,
|
||||||
search: string
|
search: string
|
||||||
|
|||||||
Reference in New Issue
Block a user