Files
FileHost/app/composables/useImageList.ts
T
O96u 469d3be735
ci / ci (22, ubuntu-latest) (push) Canceled after 0s
Publish Docker image / build (linux/amd64, ubuntu-latest, amd64) (push) Canceled after 0s
Publish Docker image / build (linux/arm64, ubuntu-24.04-arm, arm64) (push) Canceled after 0s
Publish Docker image / merge (push) Canceled after 0s
GitHub Release / release (push) Canceled after 0s
release: v1.2.0 — unified images/ storage and legacy migration
Consolidate local images under data/images/, add docker migrate CLI,
startup index sync with orphan cleanup, and remove upload folder UI/API.
2026-08-28 17:18:03 +08:00

244 lines
6.3 KiB
TypeScript

import type {
ImageItem,
ImageListResponse
} from '~/types/image'
const PAGE_SIZE = 12
export function useImageList() {
const { t } = useI18n()
const items = ref<ImageItem[]>([])
const page = ref(1)
const totalPages = ref(1)
const total = ref(0)
const loading = ref(false)
const totalCount = ref<number | null>(null)
const loadingTotal = ref(false)
const searchQuery = ref('')
const activeSearch = ref('')
const activeStorageBackend = ref('all')
const storageBackendOptions = ref<Array<{ id: string, name: string }>>([])
function storageQueryParams(): Record<string, string> {
const backendId = activeStorageBackend.value.trim()
if (!backendId || backendId === 'all') return {}
return { backendId }
}
function listQueryParams(): Record<string, string> {
return storageQueryParams()
}
function matchesActiveStorage(item: ImageItem) {
const backendId = activeStorageBackend.value.trim()
if (!backendId || backendId === 'all') return true
return item.storage?.id === backendId
}
async function loadStorageBackendOptions() {
try {
const data = await $fetch<{
backends: Array<{ id: string, name: string, type: string }>
}>('/api/storage-backends', {
credentials: 'include'
})
storageBackendOptions.value = data.backends.map(backend => ({
id: backend.id,
name: backend.name
}))
} catch {
storageBackendOptions.value = []
}
}
async function fetchTotal() {
loadingTotal.value = true
try {
const data = await $fetch<{ total: number }>('/api/images/count', {
credentials: 'include',
query: listQueryParams()
})
totalCount.value = data.total
} catch {
totalCount.value = null
} finally {
loadingTotal.value = false
}
}
async function fetchList(targetPage = 1) {
loading.value = true
try {
const response = await $fetch<ImageListResponse>('/api/images', {
credentials: 'include',
query: {
limit: PAGE_SIZE,
page: targetPage,
...listQueryParams()
}
})
items.value = response.items
page.value = response.page
totalPages.value = response.totalPages
total.value = response.total
} finally {
loading.value = false
}
}
async function fetchSearch(targetPage = 1) {
const q = activeSearch.value.trim()
if (!q) {
await fetchList(1)
return
}
loading.value = true
try {
const response = await $fetch<ImageListResponse>('/api/images/search', {
credentials: 'include',
query: {
q,
limit: PAGE_SIZE,
page: targetPage,
...listQueryParams()
}
})
items.value = response.items
page.value = response.page
totalPages.value = response.totalPages
total.value = response.total
} finally {
loading.value = false
}
}
async function refreshList() {
activeSearch.value = ''
searchQuery.value = ''
page.value = 1
await Promise.all([fetchList(1), fetchTotal()])
}
async function initializeList() {
page.value = 1
activeSearch.value = ''
searchQuery.value = ''
await Promise.all([fetchList(1), fetchTotal(), loadStorageBackendOptions()])
}
async function setActiveStorageBackend(backendId: string) {
const next = backendId.trim() || 'all'
if (next === activeStorageBackend.value) return
activeStorageBackend.value = next
page.value = 1
if (activeSearch.value) {
await Promise.all([fetchSearch(1), fetchTotal()])
} else {
await Promise.all([fetchList(1), fetchTotal()])
}
}
async function submitSearch() {
const q = searchQuery.value.trim()
activeSearch.value = q
page.value = 1
if (!q) {
await refreshList()
return
}
await fetchSearch(1)
}
async function goToPage(targetPage: number) {
if (targetPage < 1 || targetPage > totalPages.value || loading.value) return
if (activeSearch.value) {
await fetchSearch(targetPage)
} else {
await fetchList(targetPage)
}
}
function prependItems(newItems: ImageItem[]) {
const matched = newItems.filter(item => matchesActiveStorage(item))
if (!matched.length || page.value !== 1) return
items.value = [...matched, ...items.value]
total.value += matched.length
if (totalCount.value !== null) {
totalCount.value += matched.length
}
}
function removeItems(keys: string[]) {
const keySet = new Set(keys)
const removed = items.value.filter(item => keySet.has(item.key)).length
items.value = items.value.filter(item => !keySet.has(item.key))
if (removed > 0) {
total.value = Math.max(0, total.value - removed)
}
if (totalCount.value !== null && removed > 0) {
totalCount.value = Math.max(0, totalCount.value - removed)
}
}
const activeStorageLabel = computed(() => {
if (activeStorageBackend.value === 'all') return ''
return storageBackendOptions.value.find(
backend => backend.id === activeStorageBackend.value
)?.name ?? activeStorageBackend.value
})
const listSummary = computed(() => {
const storageLabel = activeStorageLabel.value
if (loadingTotal.value && totalCount.value === null) {
return storageLabel
? `${storageLabel} · ${t('stats.counting')}`
: t('stats.counting')
}
if (activeSearch.value) {
if (storageLabel) {
return t('stats.summarySearchStorage', {
q: activeSearch.value,
total: total.value,
storage: storageLabel
})
}
return t('stats.summarySearch', { q: activeSearch.value, total: total.value })
}
const count = totalCount.value === null ? total.value : totalCount.value
if (storageLabel) {
return t('stats.summaryTotalStorage', {
total: count,
storage: storageLabel
})
}
return t('stats.summaryTotal', { total: count })
})
return {
items,
page,
totalPages,
total,
loading,
activeStorageBackend,
storageBackendOptions,
searchQuery,
activeSearch,
listSummary,
fetchList,
fetchSearch,
fetchTotal,
refreshList,
initializeList,
setActiveStorageBackend,
loadStorageBackendOptions,
submitSearch,
goToPage,
prependItems,
removeItems
}
}