Save workspace changes

This commit is contained in:
2026-04-18 17:02:56 +02:00
parent f02ea9a711
commit 87d60af5a9
4220 changed files with 1388603 additions and 1554 deletions

View File

@@ -109,7 +109,7 @@ async function fetchPageData(url) {
: null;
return {
artworks,
artworks: artworks.map(normalizeArtworkItem),
nextCursor,
nextPageUrl,
hasMore,
@@ -127,7 +127,7 @@ async function fetchPageData(url) {
try { artworks = JSON.parse(el.dataset.artworks || '[]'); } catch { /* empty */ }
return {
artworks,
artworks: artworks.map(normalizeArtworkItem),
nextCursor: el.dataset.nextCursor || null,
nextPageUrl: el.dataset.nextPageUrl || null,
hasMore: null,
@@ -142,37 +142,61 @@ function SkeletonCard() {
// ── Ranking API helpers ───────────────────────────────────────────────────
/**
* Map a single ArtworkListResource item (from /api/rank/*) to the internal
* artwork object shape used by ArtworkCard.
* Normalize API / Blade artwork payloads into the internal shape used by the
* gallery layout helpers and ArtworkCard.
*/
function mapRankApiArtwork(item) {
const w = item.dimensions?.width ?? null;
const h = item.dimensions?.height ?? null;
const thumb = item.thumbnail_url ?? null;
const webUrl = item.urls?.web ?? item.category?.url ?? null;
function normalizeArtworkItem(item) {
if (!item || typeof item !== 'object') return item;
const category = item.category && typeof item.category === 'object' ? item.category : null;
const author = item.author && typeof item.author === 'object'
? item.author
: (item.creator && typeof item.creator === 'object' ? item.creator : null);
const publisher = item.publisher && typeof item.publisher === 'object' ? item.publisher : null;
const w = item.dimensions?.width ?? item.width ?? null;
const h = item.dimensions?.height ?? item.height ?? null;
const thumb = item.thumbnail_url ?? item.thumb_url ?? item.thumb ?? item.image ?? null;
const canonicalUrl = item.canonical_url
?? item.urls?.canonical
?? item.urls?.direct
?? item.url
?? item.href
?? item.urls?.web
?? category?.url
?? null;
return {
...item,
id: item.id ?? null,
name: item.title ?? item.name ?? null,
thumb: thumb,
thumb_url: thumb,
uname: item.author?.name ?? '',
username: publisher?.type === 'group' ? '' : (item.author?.username ?? ''),
avatar_url: item.author?.avatar_url ?? null,
profile_url: publisher?.profile_url ?? item.author?.profile_url ?? null,
published_as_type: publisher?.type ?? null,
publisher: publisher,
content_type_name: item.category?.content_type_name ?? item.category?.content_type_slug ?? item.category?.content_type ?? '',
content_type_slug: item.category?.content_type_slug ?? item.category?.content_type ?? '',
category_name: item.category?.name ?? '',
category_slug: item.category?.slug ?? '',
name: item.name ?? item.title ?? null,
title: item.title ?? item.name ?? null,
thumb: item.thumb ?? thumb,
thumb_url: item.thumb_url ?? thumb,
thumbnail_url: item.thumbnail_url ?? thumb,
author,
uname: item.uname ?? author?.name ?? item.author_name ?? '',
username: item.username ?? (publisher?.type === 'group' ? '' : (author?.username ?? '')),
avatar_url: item.avatar_url ?? author?.avatar_url ?? null,
profile_url: item.profile_url ?? publisher?.profile_url ?? author?.profile_url ?? null,
published_as_type: item.published_as_type ?? publisher?.type ?? null,
publisher: publisher ?? null,
content_type_name: item.content_type_name ?? category?.content_type_name ?? category?.content_type_slug ?? category?.content_type ?? '',
content_type_slug: item.content_type_slug ?? category?.content_type_slug ?? category?.content_type ?? '',
category_name: item.category_name ?? category?.name ?? (typeof item.category === 'string' ? item.category : ''),
category_slug: item.category_slug ?? category?.slug ?? '',
category: typeof item.category === 'string' ? item.category : (category?.name ?? item.category_name ?? ''),
slug: item.slug ?? '',
url: webUrl,
canonical_url: item.canonical_url ?? item.urls?.canonical ?? item.urls?.direct ?? null,
url: canonicalUrl,
width: w,
height: h,
};
}
function mapRankApiArtwork(item) {
return normalizeArtworkItem(item);
}
/**
* Fetch ranked artworks from the ranking API.
* Returns { artworks: [...] } in internal shape, or { artworks: [] } on failure.
@@ -261,7 +285,8 @@ function MasonryGallery({
discoveryEndpoint = null,
algoVersion: initialAlgoVersion = null,
}) {
const [artworks, setArtworks] = useState(initialArtworks);
const normalizedInitialArtworks = initialArtworks.map(normalizeArtworkItem);
const [artworks, setArtworks] = useState(normalizedInitialArtworks);
const [nextCursor, setNextCursor] = useState(initialNextCursor);
const [nextPageUrl, setNextPageUrl] = useState(initialNextPageUrl);
const [loading, setLoading] = useState(false);
@@ -279,7 +304,7 @@ function MasonryGallery({
// client-side fetch from the ranking API to hydrate the grid.
// Satisfies spec: "Fallback: Latest if ranking missing".
useEffect(() => {
if (initialArtworks.length > 0) return; // SSR artworks already present
if (normalizedInitialArtworks.length > 0) return; // SSR artworks already present
if (!rankApiEndpoint) return; // no API endpoint configured
let cancelled = false;