feat(SPA): media library indications for custom medial field type usage

This commit is contained in:
Florian Sylvain committed 2026-10-21 16:17:09 +02:00
1 parent 3e7fc9a4a5
commit 0fc3511e2b
28 files changed
+2060 -131

No files matched your search

+101 -1
View File
@@ -1697,6 +1697,58 @@ const mediaFloorboardsUsage = [
{ id: 12, title: 'Before and after: the Rue Verte kitchen', published: true },
{ id: 13, title: 'Why we reuse original floorboards', published: true },
]
const mediaDesktopWidth = 768
const mediaBathroomTilesId = 10
const mediaLimePlasterId = 11
const mediaEntryUsedIn: Record<number, number> = {
[mediaBathroomTilesId]: 3,
[mediaLimePlasterId]: 7,
}
const mediaEventTitles = ['Lime plaster workshop', 'Heritage stone walk', 'Winter open day']
const mediaLimeArticles = [
{ type: 'article', id: 10, title: 'How we price a bathroom renovation', published: true },
{ type: 'article', id: 11, title: 'Winter checklist for timber windows', published: false },
].map((article) => ({ ...article, fields: [] }))
function mediaEventEntries(count: number) {
return Array.from({ length: count }, (_, index) => ({
type: 'entry',
id: index + 1,
fields: ['thumbnail'],
content_type_key: 'events',
content_type_name: 'Events',
first_field: {
key: 'title',
type: 'text',
value: mediaEventTitles[index] ?? `Event ${index + 1}`,
},
}))
}
function mediaUsageApi(overrides: Record<string, ApiMock> = {}) {
return mediaApi({
'GET /v1/media/': {
status: 200,
bodyFor: () =>
mediaList(
mediaItems().map((item) => ({
...item,
used_in: mediaEntryUsedIn[item.id] ?? item.used_in,
})),
),
},
[`GET /v1/media/${mediaBathroomTilesId}/usage`]: {
status: 200,
body: mediaEventEntries(3),
},
[`GET /v1/media/${mediaLimePlasterId}/usage`]: {
status: 200,
body: [...mediaLimeArticles, ...mediaEventEntries(5)],
},
...overrides,
})
}
const mediaDuplicateIds = [12, 5, 6]
const mediaDuplicateGroups = [mediaDuplicateIds, [7, 9]]
@@ -2218,6 +2270,25 @@ const mediaStates: Record<string, VisualState> = {
await page.mouse.move(0, 0)
},
),
'Grid: usage badge tooltip': mediaState(mediaUsageApi(), async (page) => {
const badge = mediaCard(page, mediaBathroomTilesId).getByText('Used in 3', {
exact: true,
})
const desktop = (page.viewportSize()?.width ?? 0) >= mediaDesktopWidth
if (desktop) {
await badge.hover()
await page.getByText('Used in entries: 3', { exact: true }).first().waitFor()
}
}),
'List view: usage badge': mediaState(mediaUsageApi(), undefined, '/media?view=list'),
'Detail panel: used in entries': mediaState(mediaUsageApi(), async (page) => {
await openMediaPanel(page, mediaLimePlasterId)
await page.getByText('and 2 more', { exact: true }).waitFor()
}),
'Delete: image used by entries': mediaState(mediaUsageApi(), async (page) => {
await openMediaDialog(page, mediaLimePlasterId, 'Delete')
await page.getByRole('alertdialog').getByText('and 2 more', { exact: true }).waitFor()
}),
'Delete: unused image': mediaState(mediaApi(), (page) => openMediaDialog(page, 8, 'Delete')),
'Delete: image in use': mediaState(mediaApi(), (page) => openMediaDialog(page, 1, 'Delete')),
'Delete: in progress': mediaState(
@@ -4706,6 +4777,35 @@ function withVariantsRowException(
const MEDIA_DESKTOP_BUDGET = 10
const usageCeilings: Record<string, { desktop: number; mobile: number }> = {
'Grid: usage badge tooltip': { desktop: MEDIA_DESKTOP_BUDGET, mobile: 32 },
'List view: usage badge': { desktop: 15, mobile: 24 },
'Detail panel: used in entries': { desktop: 17, mobile: 28 },
'Delete: image used by entries': { desktop: MEDIA_DESKTOP_BUDGET, mobile: 29 },
}
function withUsageException(states: Record<string, VisualState>): Record<string, VisualState> {
return Object.fromEntries(
Object.entries(states).map(([name, state]) => {
const maxPercent = usageCeilings[name]
if (maxPercent === undefined) {
return [name, state]
}
return [
name,
{
...state,
exception: {
maxPercent,
reason: 'mobile: variants filter row not drawn on the Media board; desktop: stray miniature in the board capture and the Details | Variants tab row not drawn on the board',
approvedBy: 'user (Florian Sylvain), 2026-10-21',
},
},
]
}),
)
}
function withDetailTabsException(states: Record<string, VisualState>): Record<string, VisualState> {
const name = 'Detail panel: duplicate'
const state = states[name]
@@ -9468,7 +9568,7 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
...withVariantExceptions(variantsEditorStates),
},
media: withDetailTabsException(
withVariantsRowException({ ...mediaStates, ...mediaPurgeStates }),
withUsageException(withVariantsRowException({ ...mediaStates, ...mediaPurgeStates })),
),
'media-picker': pickerStates,
}