diff --git a/frontend/pages/files/index.vue b/frontend/pages/files/index.vue index 82ce9d4..2e49088 100644 --- a/frontend/pages/files/index.vue +++ b/frontend/pages/files/index.vue @@ -28,7 +28,9 @@ const displayModes = [ const createFolderModalOpen = ref(false) const createFolderData = ref({ name: '', standardFiletype: null, standardFiletypeIsOptional: true }) -const selectedFiles = ref({}) +const renameModalOpen = ref(false) +const renameData = ref({ id: null, name: '', type: '' }) + const selectedFileIndex = ref(0) const draggedItem = ref(null) @@ -45,42 +47,79 @@ watch(searchString, (val) => { }, 300) }) -// --- Logic --- +// --- Data Fetching --- const setupPage = async () => { loadingDocs.value = true - const [fRes, dRes, tRes] = await Promise.all([ - useEntities("folders").select(), - files.selectDocuments(), - useEntities("filetags").select() - ]) - folders.value = fRes - documents.value = dRes - filetags.value = tRes + try { + const [fRes, dRes, tRes] = await Promise.all([ + useEntities("folders").select(), + files.selectDocuments(), + useEntities("filetags").select() + ]) + folders.value = fRes || [] + documents.value = dRes || [] + filetags.value = tRes || [] - if (route.query?.folder) { - currentFolder.value = folders.value.find(i => i.id === route.query.folder) || null - } else { - currentFolder.value = null + if (route.query?.folder) { + currentFolder.value = folders.value.find(i => i.id === route.query.folder) || null + } else { + currentFolder.value = null + } + } finally { + loadingDocs.value = false + loaded.value = true } - loadingDocs.value = false - loaded.value = true } onMounted(() => setupPage()) -// --- Navigation & Breadcrumbs --- +// --- Navigation --- const changeFolder = async (folder) => { currentFolder.value = folder await router.push(folder ? `/files?folder=${folder.id}` : `/files`) } +const navigateUp = () => { + if (!currentFolder.value) return + const parent = folders.value.find(f => f.id === currentFolder.value.parent) + changeFolder(parent || null) +} + +// --- Drag & Drop --- +const handleDragStart = (entry) => { + draggedItem.value = entry +} + +const handleDrop = async (targetFolderId) => { + // targetFolderId kann null sein (Root) + if (!draggedItem.value) return + if (draggedItem.value.id === targetFolderId) return + + loadingDocs.value = true + try { + if (draggedItem.value.type === 'file') { + await useEntities("files").update(draggedItem.value.id, { folder: targetFolderId }) + } else { + await useEntities("folders").update(draggedItem.value.id, { parent: targetFolderId }) + } + toast.add({ title: 'Erfolgreich verschoben', icon: 'i-heroicons-check-circle', color: 'green' }) + await setupPage() + } catch (e) { + toast.add({ title: 'Fehler beim Verschieben', color: 'red' }) + } finally { + draggedItem.value = null + loadingDocs.value = false + } +} + +// --- Breadcrumbs --- const breadcrumbLinks = computed(() => { const links = [{ label: "Home", icon: "i-heroicons-home", click: () => changeFolder(null) }] if (currentFolder.value) { const path = [] let curr = currentFolder.value while (curr) { - const folderObj = curr // Closure Sicherheit + const folderObj = curr path.unshift({ label: folderObj.name, icon: "i-heroicons-folder", @@ -93,50 +132,35 @@ const breadcrumbLinks = computed(() => { return links }) -// --- Drag & Drop --- -const handleDragStart = (entry) => { - draggedItem.value = entry -} - -const handleDrop = async (targetFolder) => { - if (!draggedItem.value || draggedItem.value.id === targetFolder.id) return - - // Verhindern, dass Ordner in sich selbst verschoben werden - if (draggedItem.value.type === 'folder' && draggedItem.value.id === targetFolder.id) return - - loadingDocs.value = true - try { - if (draggedItem.value.type === 'file') { - await useEntities("files").update(draggedItem.value.id, { folder: targetFolder.id }) - } else { - await useEntities("folders").update(draggedItem.value.id, { parent: targetFolder.id }) - } - toast.add({ title: 'Erfolgreich verschoben', icon: 'i-heroicons-check-circle', color: 'green' }) - await setupPage() - } catch (e) { - toast.add({ title: 'Fehler beim Verschieben', color: 'red' }) - } finally { - draggedItem.value = null - loadingDocs.value = false - } -} - // --- Data Mapping --- const renderedFileList = computed(() => { + // 1. Aktuelle Ordner filtern const folderList = folders.value .filter(i => currentFolder.value ? i.parent === currentFolder.value.id : !i.parent) .map(i => ({ ...i, label: i.name, type: "folder" })) - .sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })) + .sort((a, b) => a.label.localeCompare(b.label)) + // 2. Aktuelle Dateien filtern const fileList = documents.value .filter(i => currentFolder.value ? i.folder === currentFolder.value.id : !i.folder) .map(i => ({ ...i, label: i.path.split("/").pop(), type: "file" })) - .sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })) + .sort((a, b) => a.label.localeCompare(b.label)) let combined = [...folderList, ...fileList] if (debouncedSearch.value) { combined = useSearch(debouncedSearch.value, combined) } + + // 3. "Nach oben" (..) einfügen, wenn wir nicht im Root sind und nicht suchen + if (currentFolder.value && !debouncedSearch.value) { + combined.unshift({ + id: 'go-up', + label: '..', + type: 'up', + parentId: currentFolder.value.parent || null + }) + } + return combined }) @@ -144,15 +168,38 @@ const renderedFileList = computed(() => { const createFolder = async () => { await useEntities("folders").create({ parent: currentFolder.value?.id, - name: createFolderData.value.name, - standardFiletype: createFolderData.value.standardFiletype, - standardFiletypeIsOptional: createFolderData.value.standardFiletypeIsOptional + name: createFolderData.value.name }) createFolderModalOpen.value = false - createFolderData.value = { name: '', standardFiletype: null, standardFiletypeIsOptional: true } + createFolderData.value = { name: '' } setupPage() } +const openRenameModal = (entry) => { + renameData.value = { id: entry.id, name: entry.label, type: entry.type } + renameModalOpen.value = true +} + +const updateName = async () => { + if (!renameData.value.name) return + loadingDocs.value = true + try { + if (renameData.value.type === 'folder') { + await useEntities("folders").update(renameData.value.id, { name: renameData.value.name }) + } else { + const file = documents.value.find(d => d.id === renameData.value.id) + const pathParts = file.path.split('/') + pathParts[pathParts.length - 1] = renameData.value.name + await useEntities("files").update(renameData.value.id, { path: pathParts.join('/') }) + } + toast.add({ title: 'Umbenannt', color: 'green' }) + setupPage() + } finally { + renameModalOpen.value = false + loadingDocs.value = false + } +} + const showFile = (fileId) => { modal.open(DocumentDisplayModal, { documentData: documents.value.find(i => i.id === fileId), @@ -160,15 +207,8 @@ const showFile = (fileId) => { }) } -const clearSearchString = () => { - searchString.value = '' - debouncedSearch.value = '' - tempStore.clearSearchString("files") -} - -// --- Shortcuts --- defineShortcuts({ - '/': () => document.getElementById("searchinput").focus(), + '/': () => document.getElementById("searchinput")?.focus(), 'Enter': { usingInput: true, handler: () => { @@ -176,193 +216,123 @@ defineShortcuts({ if (!entry) return if (entry.type === "file") showFile(entry.id) else if (entry.type === "folder") changeFolder(entry) + else if (entry.type === "up") navigateUp() } }, - 'arrowdown': () => { - if (selectedFileIndex.value < renderedFileList.value.length - 1) selectedFileIndex.value++ - }, - 'arrowup': () => { - if (selectedFileIndex.value > 0) selectedFileIndex.value-- - } + 'arrowdown': () => { if (selectedFileIndex.value < renderedFileList.value.length - 1) selectedFileIndex.value++ }, + 'arrowup': () => { if (selectedFileIndex.value > 0) selectedFileIndex.value-- } }) - \ No newline at end of file + + + + + + + + \ No newline at end of file