feat(media): pagination
This commit is contained in:
1 parent
04dc151ed7
commit
63c1e7076e
65 files changed
+2960
-365
No files matched your search
@@ -19,6 +19,7 @@ import {
|
||||
sizeRejectionText,
|
||||
validateImageFile,
|
||||
} from '@/lib/imageUpload'
|
||||
import { toHashRequestQuery } from '@/lib/mediaQuery'
|
||||
import { libraryMatchText } from '@/lib/mediaFormat'
|
||||
import { insertedUrl } from '@/lib/variants/variantOptions'
|
||||
import { UNDERLINE_TAB_TRIGGER } from '@/lib/tabStyles'
|
||||
@@ -349,7 +350,9 @@ async function findExisting(file: File): Promise<MediaItem | null | 'handled'> {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
const response = await request<{ items: MediaItem[] }>(`/v1/media/?hash=${hash}`)
|
||||
const response = await request<{ items: MediaItem[] }>(
|
||||
`/v1/media/?${toHashRequestQuery(hash)}`,
|
||||
)
|
||||
return oldestOf(response.items) ?? null
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError && classifyMediaError(error) === 'handled') {
|
||||
@@ -796,7 +799,7 @@ onUnmounted(() => {
|
||||
:status="library.status.value"
|
||||
:show-skeleton="library.showSkeleton.value"
|
||||
:loading-more="library.loadingMore.value"
|
||||
:has-more="library.nextCursor.value !== null"
|
||||
:has-more="library.hasMore.value"
|
||||
:total="library.total.value"
|
||||
:query="library.query.value"
|
||||
:first-new-id="library.firstNewId.value"
|
||||
|
||||
@@ -115,11 +115,11 @@ describe('The library has images, so Library opens', () => {
|
||||
expect(skeletonTiles()).toHaveLength(9)
|
||||
})
|
||||
|
||||
it('asks for 24 images and nothing else', async () => {
|
||||
it('asks for the first page of 25 images and nothing else', async () => {
|
||||
await openLibrary()
|
||||
|
||||
expect(listCalls()).toHaveLength(1)
|
||||
expect(listCalls()[0]!.toString()).toBe('limit=24')
|
||||
expect(listCalls()[0]!.toString()).toBe('page=1&size=25')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -197,7 +197,7 @@ describe('A search is active, so the count is not shown', () => {
|
||||
})
|
||||
|
||||
describe('Load more is hidden on the last page', () => {
|
||||
it('does not show "Load more" without a cursor', async () => {
|
||||
it('does not show "Load more" once every matching image is loaded', async () => {
|
||||
await openLibrary({ list: () => pageBody(fixture(), null) })
|
||||
|
||||
expect(findButton('Load more')).toBeUndefined()
|
||||
@@ -373,7 +373,7 @@ describe('Insert', () => {
|
||||
})
|
||||
|
||||
describe('Search', () => {
|
||||
it('searches after a 120 ms debounce, with no cursor, and clears the selection', async () => {
|
||||
it('searches after a 120 ms debounce, on page 1, and clears the selection', async () => {
|
||||
await openWithFakeTimers()
|
||||
tile('kitchen-after.webp').click()
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
@@ -388,8 +388,8 @@ describe('Search', () => {
|
||||
|
||||
expect(listCalls()).toHaveLength(2)
|
||||
expect(listCalls()[1]!.get('q')).toBe('wall')
|
||||
expect(listCalls()[1]!.get('limit')).toBe('24')
|
||||
expect(listCalls()[1]!.has('cursor')).toBe(false)
|
||||
expect(listCalls()[1]!.get('size')).toBe('25')
|
||||
expect(listCalls()[1]!.get('page')).toBe('1')
|
||||
expect(findButton('Insert image')?.disabled).toBe(true)
|
||||
})
|
||||
|
||||
@@ -419,7 +419,7 @@ describe('Search', () => {
|
||||
expect(searchField().value).toBe('')
|
||||
const last = listCalls().at(-1)!
|
||||
expect(last.has('q')).toBe(false)
|
||||
expect(last.get('limit')).toBe('24')
|
||||
expect(last.get('size')).toBe('25')
|
||||
})
|
||||
|
||||
it('shows the no results panel and "Clear search" empties the field', async () => {
|
||||
@@ -447,7 +447,7 @@ describe('Load more', () => {
|
||||
it('shows progress, appends the new tiles and moves focus to the first one', async () => {
|
||||
const more = deferred<unknown>()
|
||||
await openWithFakeTimers({
|
||||
list: (query) => (query.get('cursor') ? more.promise : pageBody()),
|
||||
list: (query) => (query.get('page') === '2' ? more.promise : pageBody()),
|
||||
})
|
||||
const button = findButton('Load more')!
|
||||
button.focus()
|
||||
@@ -456,8 +456,8 @@ describe('Load more', () => {
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
|
||||
const last = listCalls().at(-1)!
|
||||
expect(last.get('cursor')).toBe('c24')
|
||||
expect(last.get('limit')).toBe('24')
|
||||
expect(last.get('page')).toBe('2')
|
||||
expect(last.get('size')).toBe('25')
|
||||
const loading = findButton('Loading…')!
|
||||
expect(loading.getAttribute('aria-disabled')).toBe('true')
|
||||
expect(document.activeElement).toBe(loading)
|
||||
@@ -475,7 +475,10 @@ describe('Load more', () => {
|
||||
|
||||
it('draws a row of 3 skeleton tiles on mobile', async () => {
|
||||
await openWithFakeTimers(
|
||||
{ list: (query) => (query.get('cursor') ? new Promise(() => undefined) : pageBody()) },
|
||||
{
|
||||
list: (query) =>
|
||||
query.get('page') === '2' ? new Promise(() => undefined) : pageBody(),
|
||||
},
|
||||
MOBILE_WIDTH,
|
||||
)
|
||||
|
||||
@@ -485,9 +488,9 @@ describe('Load more', () => {
|
||||
expect(skeletonTiles()).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('combines the cursor with the search', async () => {
|
||||
it('combines the next page with the search', async () => {
|
||||
await openLibrary({
|
||||
list: (query) => pageBody(query.get('q') ? fixture([1, 2]) : fixture(), 'c24'),
|
||||
list: (query) => pageBody(query.get('q') ? fixture([1, 2]) : fixture(), 48),
|
||||
})
|
||||
await searchFor('wall')
|
||||
|
||||
@@ -495,8 +498,8 @@ describe('Load more', () => {
|
||||
|
||||
const last = listCalls().at(-1)!
|
||||
expect(last.get('q')).toBe('wall')
|
||||
expect(last.get('limit')).toBe('24')
|
||||
expect(last.get('cursor')).toBe('c24')
|
||||
expect(last.get('size')).toBe('25')
|
||||
expect(last.get('page')).toBe('2')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -11,14 +11,11 @@ import { fixture, textOf } from '@/views/__tests__/mediaTestKit'
|
||||
|
||||
export { textOf }
|
||||
|
||||
export const FIRST_PAGE_CURSOR = 'c24'
|
||||
export const FIRST_PAGE_TOTAL = 48
|
||||
export const PICKER_COUNTS = { all: 48, unused: 2, duplicates: 2 }
|
||||
|
||||
export function pageBody(
|
||||
items: MediaItem[] = fixture(),
|
||||
next_cursor: string | null = FIRST_PAGE_CURSOR,
|
||||
) {
|
||||
return { items, next_cursor, counts: PICKER_COUNTS }
|
||||
export function pageBody(items: MediaItem[] = fixture(), total: number | null = FIRST_PAGE_TOTAL) {
|
||||
return { items, total: total ?? items.length, page: 1, size: 25, counts: PICKER_COUNTS }
|
||||
}
|
||||
|
||||
export function secondPage() {
|
||||
|
||||
Reference in new issue
Block a user