Files
Lesstime/frontend/pages/documents.vue
T

152 lines
5.2 KiB
Vue

<template>
<div>
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ $t('sharedFiles.title') }}</h1>
<!-- Fil d'Ariane -->
<nav class="mt-4 flex flex-wrap items-center gap-1 text-sm text-neutral-500">
<button class="hover:text-primary-500" @click="openPath('')">{{ $t('sharedFiles.root') }}</button>
<template v-for="crumb in breadcrumb" :key="crumb.path">
<span>/</span>
<button class="hover:text-primary-500" @click="openPath(crumb.path)">{{ crumb.name }}</button>
</template>
</nav>
<!-- Filtre local -->
<div class="mt-4 max-w-sm">
<MalioInputText
v-model="filter"
:placeholder="$t('sharedFiles.filterPlaceholder')"
input-class="w-full"
/>
</div>
<!-- États -->
<div v-if="loading" class="mt-10 flex justify-center">
<Icon name="heroicons:arrow-path" class="h-6 w-6 animate-spin text-neutral-400" />
</div>
<p v-else-if="error" class="mt-10 text-sm text-red-600">{{ error }}</p>
<p v-else-if="visibleEntries.length === 0" class="mt-10 text-sm text-neutral-400">{{ $t('sharedFiles.empty') }}</p>
<!-- Tableau -->
<table v-else class="mt-6 w-full text-sm">
<thead class="border-b border-neutral-200 text-left text-xs uppercase tracking-wider text-neutral-400">
<tr>
<th class="py-2">{{ $t('sharedFiles.colName') }}</th>
<th class="py-2">{{ $t('sharedFiles.colSize') }}</th>
<th class="py-2">{{ $t('sharedFiles.colModified') }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="entry in visibleEntries"
:key="entry.path"
class="cursor-pointer border-b border-neutral-100 hover:bg-neutral-50"
@click="onEntryClick(entry)"
>
<td class="flex items-center gap-2 py-2">
<Icon :name="entry.isDir ? 'mdi:folder-outline' : iconForMime(entry.mimeType)" class="h-5 w-5 text-neutral-400" />
<span class="truncate">{{ entry.name }}</span>
</td>
<td class="py-2 text-neutral-500">{{ entry.isDir ? '' : formatFileSize(entry.size) }}</td>
<td class="py-2 text-neutral-500">{{ formatDate(entry.modifiedAt) }}</td>
</tr>
</tbody>
</table>
<SharedFilePreview
:entry="previewEntry"
:has-prev="previewIndex > 0"
:has-next="previewIndex >= 0 && previewIndex < fileEntries.length - 1"
@close="previewEntry = null"
@prev="stepPreview(-1)"
@next="stepPreview(1)"
/>
</div>
</template>
<script setup lang="ts">
import type { Breadcrumb, FileEntry } from '~/services/dto/share'
import { useShareService } from '~/services/share'
import { formatFileSize } from '~/utils/format'
useHead({ title: 'Documents' })
const { browse } = useShareService()
const { enabled, ensureLoaded } = useShareStatus()
const currentPath = ref('')
const breadcrumb = ref<Breadcrumb[]>([])
const entries = ref<FileEntry[]>([])
const filter = ref('')
const loading = ref(false)
const error = ref<string | null>(null)
const previewEntry = ref<FileEntry | null>(null)
const visibleEntries = computed(() => {
const f = filter.value.trim().toLowerCase()
if (!f) return entries.value
return entries.value.filter((e) => e.name.toLowerCase().includes(f))
})
const fileEntries = computed(() => visibleEntries.value.filter((e) => !e.isDir))
const previewIndex = computed(() => previewEntry.value ? fileEntries.value.findIndex((e) => e.path === previewEntry.value!.path) : -1)
async function load(path: string) {
loading.value = true
error.value = null
try {
const result = await browse(path)
currentPath.value = result.path
breadcrumb.value = result.breadcrumb
entries.value = result.entries
} catch (e: unknown) {
error.value = (e as Error)?.message ?? 'Erreur'
entries.value = []
} finally {
loading.value = false
}
}
function openPath(path: string) {
filter.value = ''
load(path)
}
function onEntryClick(entry: FileEntry) {
if (entry.isDir) {
openPath(entry.path)
} else {
previewEntry.value = entry
}
}
function stepPreview(delta: number) {
const idx = previewIndex.value + delta
if (idx >= 0 && idx < fileEntries.value.length) {
previewEntry.value = fileEntries.value[idx] ?? null
}
}
function iconForMime(mime: string): string {
if (mime.startsWith('image/')) return 'mdi:file-image-outline'
if (mime === 'application/pdf') return 'mdi:file-pdf-box'
if (mime.startsWith('text/')) return 'mdi:file-document-outline'
return 'mdi:file-outline'
}
function formatDate(ts: number | null): string {
if (!ts) return '—'
return new Date(ts * 1000).toLocaleString()
}
onMounted(async () => {
await ensureLoaded()
if (enabled.value === false) {
await navigateTo('/')
return
}
load('')
})
</script>