feat(media): pagination

This commit is contained in:
Florian Sylvain committed 2026-10-13 19:54:59 +02:00
1 parent 04dc151ed7
commit 63c1e7076e
65 files changed
+2960 -365

No files matched your search

+56 -3
View File
@@ -1,4 +1,5 @@
import type { LocationQuery } from 'vue-router'
import { MEDIA_PAGE_SIZE_KEY, resolvePageSize } from '@/lib/pageSizeMemory'
export type MediaFilter = 'all' | 'unused' | 'duplicates'
export type MediaSort = 'newest' | 'oldest' | 'name' | 'largest' | 'total_size'
@@ -11,9 +12,15 @@ export interface MediaControls {
sort: MediaSort
variants: MediaVariantsFilter
view: MediaView
page: number
size: number
}
export const MEDIA_PAGE_LIMIT = 100
export const MEDIA_PAGE_SIZES = [5, 25, 50, 100]
export const DEFAULT_MEDIA_PAGE_SIZE = 25
export const MEDIA_FULL_SET_PAGE_SIZE = 100
const FIRST_PAGE = 1
const POSITIVE_INTEGER = /^[1-9]\d*$/
export const DEFAULT_CONTROLS: MediaControls = {
q: '',
@@ -21,6 +28,8 @@ export const DEFAULT_CONTROLS: MediaControls = {
sort: 'newest',
variants: 'all',
view: 'grid',
page: FIRST_PAGE,
size: DEFAULT_MEDIA_PAGE_SIZE,
}
const FILTERS: MediaFilter[] = ['all', 'unused', 'duplicates']
@@ -36,18 +45,57 @@ function pick<T extends string>(allowed: T[], value: string, fallback: T) {
return (allowed as string[]).includes(value) ? (value as T) : fallback
}
export function parseMediaControls(query: LocationQuery): MediaControls {
function parsePage(value: string) {
return POSITIVE_INTEGER.test(value) ? Number(value) : FIRST_PAGE
}
function parseSize(value: LocationQuery[string] | undefined, useStoredSize: boolean) {
const first = Array.isArray(value) ? value[0] : value
const raw = first === null ? '' : first
if (raw === undefined && !useStoredSize) {
return DEFAULT_MEDIA_PAGE_SIZE
}
return resolvePageSize(raw, MEDIA_PAGE_SIZE_KEY, MEDIA_PAGE_SIZES, DEFAULT_MEDIA_PAGE_SIZE)
}
export function parseMediaControls(query: LocationQuery, useStoredSize = true): MediaControls {
return {
q: firstValue(query.q).trim(),
filter: pick(FILTERS, firstValue(query.filter), DEFAULT_CONTROLS.filter),
sort: pick(SORTS, firstValue(query.sort), DEFAULT_CONTROLS.sort),
variants: pick(VARIANTS_FILTERS, firstValue(query.variants), DEFAULT_CONTROLS.variants),
view: pick(VIEWS, firstValue(query.view), DEFAULT_CONTROLS.view),
page: parsePage(firstValue(query.page)),
size: parseSize(query.size, useStoredSize),
}
}
export function isFullSet(controls: MediaControls) {
return controls.filter === 'duplicates'
}
export function sameMediaControls(left: MediaControls, right: MediaControls) {
const samePaging =
(isFullSet(left) && isFullSet(right)) ||
(left.page === right.page && left.size === right.size)
return (
left.q === right.q &&
left.filter === right.filter &&
left.sort === right.sort &&
left.variants === right.variants &&
left.view === right.view &&
samePaging
)
}
export function toRouteQuery(controls: MediaControls) {
const query: Record<string, string> = {}
if (!isFullSet(controls) && controls.page !== DEFAULT_CONTROLS.page) {
query.page = String(controls.page)
}
if (!isFullSet(controls) && controls.size !== DEFAULT_CONTROLS.size) {
query.size = String(controls.size)
}
if (controls.q) {
query.q = controls.q
}
@@ -72,7 +120,12 @@ export function toRequestQuery(controls: MediaControls) {
`filter=${controls.filter}`,
`sort=${controls.sort}`,
...(controls.variants === 'all' ? [] : [`variants=${controls.variants}`]),
`limit=${MEDIA_PAGE_LIMIT}`,
`page=${controls.page}`,
`size=${controls.size}`,
]
return parts.join('&')
}
export function toHashRequestQuery(hash: string) {
return `hash=${hash}&page=${FIRST_PAGE}&size=${MEDIA_FULL_SET_PAGE_SIZE}`
}