feat: pagination

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-09-05 20:14:40 -04:00
parent 47d97a8d1a
commit 6942469259
9 changed files with 200 additions and 326 deletions
+57 -78
View File
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { ref, computed, onMounted, watch, nextTick, defineAsyncComponent } from 'vue'
import { ref, computed, onMounted, onUnmounted, watch, nextTick, defineAsyncComponent } from 'vue'
import { useDisplay } from 'vuetify'
import { useModuleStore, useIntegrationStore, usePreviewStore } from '@KTXC'
import { useDocumentManager, useDocumentSelection, useFileUpload, useFileEditor } from '@/composables'
import type { ViewMode, SortField, SortOrder, BreadcrumbItem } from '@/types'
import type { ListFilter, ListSort } from '@DocumentsManager/types'
import { CollectionObject } from '@DocumentsManager/models/collection'
import { EntityObject } from '@DocumentsManager/models/entity'
import { ServiceObject } from '@DocumentsManager/models/service'
@@ -118,7 +119,7 @@ const mediaViewerDialog = computed(() => {
})
const viewerItems = computed<ViewerItem[]>(() =>
sortedItems.value.entities.map(entityToViewerItem),
documentManager.currentEntities.value.map(entityToViewerItem),
)
function entityToViewerItem(entity: EntityObject): ViewerItem {
@@ -294,76 +295,45 @@ const breadcrumbs = computed<BreadcrumbItem[]>(() => {
return items
})
const sortedItems = computed(() => {
const collections = [...documentManager.currentCollections.value]
const entities = [...documentManager.currentEntities.value]
// Sort collections
collections.sort((a, b) => {
const aVal = sortField.value === 'label'
? (a.properties.label || nodeId(a))
: sortField.value === 'modifiedOn'
? (a.modified?.getTime() ?? 0)
: sortField.value === 'createdOn'
? (a.created?.getTime() ?? 0)
: ''
const bVal = sortField.value === 'label'
? (b.properties.label || nodeId(b))
: sortField.value === 'modifiedOn'
? (b.modified?.getTime() ?? 0)
: sortField.value === 'createdOn'
? (b.created?.getTime() ?? 0)
: ''
const cmp = String(aVal).localeCompare(String(bVal))
return sortOrder.value === 'asc' ? cmp : -cmp
})
// Sort entities
entities.sort((a, b) => {
const aVal = sortField.value === 'label'
? (a.properties.label || nodeId(a))
: sortField.value === 'mime'
? (a.properties.mime || '')
: sortField.value === 'size'
? a.properties.size
: sortField.value === 'modifiedOn'
? (a.modified?.getTime() ?? 0)
: sortField.value === 'createdOn'
? (a.created?.getTime() ?? 0)
: ''
const bVal = sortField.value === 'label'
? (b.properties.label || nodeId(b))
: sortField.value === 'mime'
? (b.properties.mime || '')
: sortField.value === 'size'
? b.properties.size
: sortField.value === 'modifiedOn'
? (b.modified?.getTime() ?? 0)
: sortField.value === 'createdOn'
? (b.created?.getTime() ?? 0)
: ''
const cmp = String(aVal).localeCompare(String(bVal))
return sortOrder.value === 'asc' ? cmp : -cmp
})
// Filter by search
const filterFn = (item: CollectionObject | EntityObject) => {
if (!searchQuery.value) return true
const label = item instanceof EntityObject
? (item.properties.label || nodeId(item))
: (item.properties.label || nodeId(item))
return label.toLowerCase().includes(searchQuery.value.toLowerCase())
}
return {
collections: collections.filter(filterFn),
entities: entities.filter(filterFn),
const hasItems = computed(() =>
documentManager.currentCollections.value.length > 0 || documentManager.currentEntities.value.length > 0
)
// Search/sort are server-driven: build the current filter/sort and reset-load
// the current location whenever either changes.
function buildFilter(): ListFilter | undefined {
return searchQuery.value ? { label: searchQuery.value } : undefined
}
function buildSort(): ListSort {
return { [sortField.value]: sortOrder.value === 'asc' }
}
async function reload() {
await documentManager.resetAndLoad(buildFilter(), buildSort())
}
let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null
watch(searchQuery, () => {
if (searchDebounceTimer !== null) {
clearTimeout(searchDebounceTimer)
}
searchDebounceTimer = setTimeout(() => {
searchDebounceTimer = null
reload()
}, 300)
})
const hasItems = computed(() =>
sortedItems.value.collections.length > 0 || sortedItems.value.entities.length > 0
)
watch([sortField, sortOrder], () => {
reload()
})
onUnmounted(() => {
if (searchDebounceTimer !== null) {
clearTimeout(searchDebounceTimer)
}
})
const hasSelection = computed(() => selection.hasSelection.value)
@@ -695,7 +665,7 @@ async function readEntriesRecursively(
async function handleUploadAll() {
try {
await upload.uploadAll()
await documentManager.refresh()
await reload()
notify('Uploads completed', 'success')
} catch (error) {
console.error('Failed to upload files:', error)
@@ -712,7 +682,7 @@ function handleUploadDialogClose() {
// Refresh
async function handleRefresh() {
try {
await documentManager.refresh()
await reload()
} catch (error) {
console.error('Failed to refresh files:', error)
notify(getErrorMessage(error, 'Failed to refresh files'), 'error')
@@ -725,7 +695,7 @@ onMounted(async () => {
await documentManager.initialize()
await servicesStore.list()
syncActiveService(availableServices.value)
await documentManager.refresh()
await reload()
console.log('[Files] - Initialized with:', {
provider: activeProviderId.value,
service: activeServiceId.value,
@@ -818,9 +788,11 @@ onMounted(async () => {
<!-- Grid View -->
<FilesGridView
v-else-if="viewMode === 'grid'"
:collections="sortedItems.collections"
:entities="sortedItems.entities"
:collections="documentManager.currentCollections.value"
:entities="documentManager.currentEntities.value"
:selected-ids="selectedIds"
:has-more="documentManager.hasMore.value"
:is-loading-more="documentManager.isLoadingMore.value"
:preview-source="previewsAvailable ? resolvePreviewSource : undefined"
:thumbnail-source="previewsAvailable ? resolveThumbnailSource : undefined"
@item-click="handleItemClick"
@@ -830,14 +802,17 @@ onMounted(async () => {
@delete="handleDeleteItem"
@download="handleDownloadItem"
@show-details="handleShowDetails"
@load-more="documentManager.loadMore"
/>
<!-- List View -->
<FilesListView
v-else-if="viewMode === 'list'"
:collections="sortedItems.collections"
:entities="sortedItems.entities"
:collections="documentManager.currentCollections.value"
:entities="documentManager.currentEntities.value"
:selected-ids="selectedIds"
:has-more="documentManager.hasMore.value"
:is-loading-more="documentManager.isLoadingMore.value"
:preview-source="previewsAvailable ? resolvePreviewSource : undefined"
:thumbnail-source="previewsAvailable ? resolveThumbnailSource : undefined"
@item-click="handleItemClick"
@@ -847,14 +822,17 @@ onMounted(async () => {
@delete="handleDeleteItem"
@download="handleDownloadItem"
@show-details="handleShowDetails"
@load-more="documentManager.loadMore"
/>
<!-- Details View -->
<FilesDetailsView
v-else-if="viewMode === 'details'"
:collections="sortedItems.collections"
:entities="sortedItems.entities"
:collections="documentManager.currentCollections.value"
:entities="documentManager.currentEntities.value"
:selected-ids="selectedIds"
:has-more="documentManager.hasMore.value"
:is-loading-more="documentManager.isLoadingMore.value"
:preview-source="previewsAvailable ? resolvePreviewSource : undefined"
:thumbnail-source="previewsAvailable ? resolveThumbnailSource : undefined"
@item-click="handleItemClick"
@@ -864,6 +842,7 @@ onMounted(async () => {
@delete="handleDeleteItem"
@download="handleDownloadItem"
@show-details="handleShowDetails"
@load-more="documentManager.loadMore"
/>
</div>
</div>