ENH - overhaul Photos media experience

This commit is contained in:
smx.pusha
2026-08-14 10:46:36 +02:00
parent 6cfdd44551
commit 3fc0ebb08f
18 changed files with 1651 additions and 267 deletions
+29 -4
View File
@@ -1,17 +1,20 @@
import { describe, expect, it } from 'vitest'
import {
bottomRightGridPosition,
filterMedia,
formatMediaSize,
formatRecordingDuration,
hasNextMediaPage,
mediaErrorKey,
mergeMedia,
orderMedia,
orderMediaOldestFirst,
} from './media'
const media = [
{ createdAt: 10, id: 1, mediaType: 'photo' as const, url: 'photo' },
{ createdAt: 20, id: 2, mediaType: 'video' as const, url: 'video' },
{ createdAt: 10, favorite: false, id: 1, mediaType: 'photo' as const, url: 'photo' },
{ createdAt: 20, favorite: false, id: 2, mediaType: 'video' as const, url: 'video' },
]
describe('media utilities', () => {
@@ -24,8 +27,8 @@ describe('media utilities', () => {
it('merges pages without duplicates and keeps newest first', () => {
expect(
mergeMedia(media, [
{ createdAt: 30, id: 1, mediaType: 'photo', url: 'updated' },
{ createdAt: 25, id: 3, mediaType: 'photo', url: 'new' },
{ createdAt: 30, favorite: true, id: 1, mediaType: 'photo', url: 'updated' },
{ createdAt: 25, favorite: false, id: 3, mediaType: 'photo', url: 'new' },
]).map((entry) => [entry.id, entry.url]),
).toEqual([
[1, 'updated'],
@@ -34,6 +37,28 @@ describe('media utilities', () => {
])
})
it('orders the photo grid from oldest to newest', () => {
expect(orderMediaOldestFirst(media).map((entry) => entry.id)).toEqual([
1, 2,
])
})
it('orders the photo grid in the selected direction', () => {
expect(orderMedia(media, 'oldest').map((entry) => entry.id)).toEqual([1, 2])
expect(orderMedia(media, 'newest').map((entry) => entry.id)).toEqual([2, 1])
})
it('fills the gallery from bottom-right to top-left', () => {
expect(bottomRightGridPosition(0, 1)).toEqual({ column: 3, row: 1 })
expect(bottomRightGridPosition(0, 3)).toEqual({ column: 3, row: 1 })
expect(bottomRightGridPosition(1, 3)).toEqual({ column: 2, row: 1 })
expect(bottomRightGridPosition(2, 3)).toEqual({ column: 1, row: 1 })
expect(bottomRightGridPosition(0, 4)).toEqual({ column: 3, row: 2 })
expect(bottomRightGridPosition(3, 4)).toEqual({ column: 3, row: 1 })
expect(bottomRightGridPosition(4, 5)).toEqual({ column: 2, row: 1 })
expect(bottomRightGridPosition(10, 11)).toEqual({ column: 2, row: 1 })
})
it('loads another gallery page only after a full 30-item batch', () => {
expect(hasNextMediaPage(30)).toBe(true)
expect(hasNextMediaPage(29)).toBe(false)
+37 -1
View File
@@ -1,4 +1,9 @@
import type { GalleryFilter, MediaType, PhoneMedia } from '@/types/media'
import type {
GalleryFilter,
GallerySortOrder,
MediaType,
PhoneMedia,
} from '@/types/media'
export const MEDIA_PAGE_SIZE = 30
@@ -26,6 +31,37 @@ export function mergeMedia(
)
}
export function orderMediaOldestFirst(media: PhoneMedia[]): PhoneMedia[] {
return [...media].sort(
(left, right) => left.createdAt - right.createdAt || left.id - right.id,
)
}
export function orderMedia(
media: PhoneMedia[],
sortOrder: GallerySortOrder,
): PhoneMedia[] {
return sortOrder === 'oldest'
? orderMediaOldestFirst(media)
: [...media].sort(
(left, right) =>
right.createdAt - left.createdAt || right.id - left.id,
)
}
export function bottomRightGridPosition(
itemIndex: number,
itemCount: number,
columnCount = 3,
): { column: number; row: number } {
const count = Math.max(1, itemCount)
const index = Math.max(0, Math.min(itemIndex, count - 1))
return {
column: columnCount - (index % columnCount),
row: Math.ceil(count / columnCount) - Math.floor(index / columnCount),
}
}
export function hasNextMediaPage(pageLength: number): boolean {
return pageLength === MEDIA_PAGE_SIZE
}