Files
kite/frontend/src/views/FileManagerView.vue
T
curo1305 9ea51d6401 feat(10-06): wire breadcrumb mapping + toast call sites; delete FolderBreadcrumb
- FileManagerView: add mappedBreadcrumb computed ({id,label} from foldersStore.breadcrumb)
- FileManagerView: import useToastStore; add toast.show() to doMove, doDeleteDoc, onFilesSelected
- FileManagerView: remove console.error from doMove/doDeleteDoc (toast communicates errors)
- CloudFolderView: add mappedBreadcrumb computed; pass to StorageBrowser :breadcrumb
- Delete FolderBreadcrumb.vue + FolderBreadcrumb.test.js (no dead code per CLAUDE.md)
- FileManagerView.test.js: replace dead FolderBreadcrumb mock with BreadcrumbBar mock
- StorageBrowser.skeleton.test.js: remove unused FolderBreadcrumb stub
- Full suite: 164/164 pass, 0 failures
2026-06-15 20:26:06 +02:00

184 lines
6.7 KiB
Vue

<template>
<StorageBrowser
mode="local"
:folders="foldersStore.folders"
:files="docsStore.documents"
:breadcrumb="mappedBreadcrumb"
:upload-queue="uploadQueue"
:loading="docsStore.loading || foldersStore.loading"
:search-query="docsStore.searchQuery"
:sort-field="docsStore.sortField"
:sort-order="docsStore.sortOrder"
:root-folders="foldersStore.rootFolders"
:topic-color-fn="topicColor"
:empty-message="currentFolderId ? 'This folder is empty' : 'No folders yet'"
:empty-hint="currentFolderId ? 'Upload files above or create a sub-folder' : 'Create a folder to get started'"
ref="browserRef"
@breadcrumb-navigate="handleBreadcrumbNavigate"
@new-folder="browserRef?.startNewFolder()"
@search-change="val => (docsStore.searchQuery = val)"
@sort-change="handleSortChange"
@upload="onFilesSelected"
@folder-navigate="folder => navigateToFolder(folder.id)"
@folder-create="handleFolderCreate"
@folder-rename="handleFolderRename"
@folder-delete="folder => (folderToDelete = folder)"
@file-open="file => $router.push(`/document/${file.id}`)"
@file-share="file => (shareDoc = file)"
@file-move="({ fileId, folderId }) => doMove(fileId, folderId)"
@file-delete="doDeleteDoc"
/>
<FolderDeleteModal
v-if="folderToDelete"
:folder="folderToDelete"
@confirm="confirmDeleteFolder"
@cancel="folderToDelete = null"
/>
<ShareModal
v-if="shareDoc"
:doc="shareDoc"
@close="shareDoc = null"
@unshared="(id) => { const d = docsStore.documents.find(x => x.id === id); if (d) d.is_shared = false }"
/>
</template>
<script setup>
import { ref, reactive, computed, watch, onMounted, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useFoldersStore } from '../stores/folders.js'
import { useDocumentsStore } from '../stores/documents.js'
import { useTopicsStore } from '../stores/topics.js'
import { useToastStore } from '../stores/toast.js'
import StorageBrowser from '../components/storage/StorageBrowser.vue'
import FolderDeleteModal from '../components/folders/FolderDeleteModal.vue'
import ShareModal from '../components/sharing/ShareModal.vue'
const route = useRoute()
const router = useRouter()
const foldersStore = useFoldersStore()
const docsStore = useDocumentsStore()
const topicsStore = useTopicsStore()
const browserRef = ref(null)
// ── Navigation ────────────────────────────────────────────────────────────────
const currentFolderId = computed(() => route.params.folderId ?? null)
const mappedBreadcrumb = computed(() =>
(foldersStore.breadcrumb || []).map(f => ({ id: f.id, label: f.name }))
)
async function loadFolder(folderId) {
if (folderId === null) {
foldersStore.navigateTo(null)
await foldersStore.fetchFolders(null)
return
}
await foldersStore.navigateTo(folderId)
await Promise.all([
foldersStore.fetchFolders(folderId),
docsStore.fetchDocuments({ folderId, sort: docsStore.sortField, order: docsStore.sortOrder }),
])
docsStore.currentFolderId = folderId
}
onMounted(() => loadFolder(currentFolderId.value))
watch(currentFolderId, (id) => loadFolder(id))
function navigateToFolder(id) { router.push(`/folders/${id}`) }
function handleBreadcrumbNavigate(id) {
if (id == null) router.push('/')
else router.push(`/folders/${id}`)
}
function handleSortChange({ sort, order }) {
docsStore.sortField = sort
docsStore.sortOrder = order
docsStore.fetchDocuments({ folderId: currentFolderId.value, sort, order })
}
// ── Upload ────────────────────────────────────────────────────────────────────
const uploadQueue = ref([])
async function onFilesSelected({ files, autoClassify }) {
const folderId = currentFolderId.value
const toast = useToastStore()
const promises = files.map(file => {
const item = reactive({ name: file.name, done: false, error: null, quotaError: null, topics: null })
uploadQueue.value.unshift(item)
return docsStore.upload(file, autoClassify, folderId)
.then(({ doc }) => { item.done = true; item.topics = doc.topics ?? [] })
.catch(e => {
if (e.status === 413 && e.payload) item.quotaError = e.payload
else item.error = e.message
})
})
await Promise.allSettled(promises)
await topicsStore.fetchTopics()
const succeeded = uploadQueue.value.slice(0, files.length).filter(i => i.done).length
if (succeeded === files.length) {
toast.show(`${succeeded} file(s) uploaded`, 'success')
} else if (succeeded > 0) {
toast.show(`${succeeded} of ${files.length} file(s) uploaded`, 'warning')
} else {
toast.show('Upload failed', 'error')
}
}
// ── Folder CRUD ───────────────────────────────────────────────────────────────
async function handleFolderCreate({ name, onError, onSuccess }) {
try {
await foldersStore.createFolder(name, currentFolderId.value)
onSuccess()
} catch (e) {
onError(e.message || 'Failed to create folder.')
}
}
async function handleFolderRename({ id, name }) {
if (!name) return
try { await foldersStore.renameFolder(id, name) } catch {}
}
const folderToDelete = ref(null)
async function confirmDeleteFolder() {
if (!folderToDelete.value) return
try { await foldersStore.deleteFolder(folderToDelete.value.id) }
finally { folderToDelete.value = null }
}
// ── Document actions ──────────────────────────────────────────────────────────
const shareDoc = ref(null)
async function doMove(docId, folderId) {
const toast = useToastStore()
try {
await docsStore.moveToFolder(docId, folderId)
toast.show('Document moved', 'success')
} catch (e) {
toast.show('Move failed: ' + (e.message || 'unknown error'), 'error')
}
}
async function doDeleteDoc(docId) {
const toast = useToastStore()
try {
await docsStore.remove(docId)
toast.show('Document deleted', 'success')
} catch (e) {
toast.show('Delete failed: ' + (e.message || 'unknown error'), 'error')
}
}
// ── Topic color lookup ────────────────────────────────────────────────────────
function topicColor(name) {
return topicsStore.topics.find(t => t.name === name)?.color ?? '#6366f1'
}
</script>