From 4f1381cec8c8d956207ef0f76911dee07208efb4 Mon Sep 17 00:00:00 2001 From: IK Date: Mon, 21 Sep 2026 20:19:22 +0700 Subject: [PATCH] feat: paginate and label media library --- apps/web/src/pages/media.astro | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/web/src/pages/media.astro b/apps/web/src/pages/media.astro index a3c1d34..e6246c5 100644 --- a/apps/web/src/pages/media.astro +++ b/apps/web/src/pages/media.astro @@ -1,18 +1,23 @@ --- import EntityMedia from "../components/EntityMedia.astro"; +import Pagination from "../components/Pagination.astro"; import PageHero from "../components/PageHero.astro"; import StatePanel from "../components/StatePanel.astro"; import Layout from "../layouts/Layout.astro"; import { api, type MediaAsset } from "../lib/api"; +import { mediaRoleLabel } from "../lib/media"; const allowed = new Set(["fish", "waterbody", "tackle", "reference"]); const requested = Astro.url.searchParams.get("type") ?? "fish"; const type = allowed.has(requested) ? requested : "fish"; const requestedRole = Astro.url.searchParams.get("role") ?? ""; const search = Astro.url.searchParams.get("q")?.trim() ?? ""; +const requestedOffset = Number(Astro.url.searchParams.get("offset") ?? 0); +let offset = Number.isInteger(requestedOffset) && requestedOffset >= 0 ? requestedOffset : 0; +const pageLimit = 48; const roleOptions: Record> = { waterbody: [["waterbody_cover", "Заставки"], ["waterbody_map", "Карты"], ["waterbody_depth_map", "Карты глубин"], ["waterbody_screenshot", "Скриншоты"]], tackle: [["tackle_card", "Карточки снастей"], ["tackle_detail", "Детали снастей"], ["rig_diagram", "Схемы сборок"], ["tackle_screenshot", "Скриншоты снастей"]] }; const selectedRole = (roleOptions[type] ?? []).some(([value]) => value === requestedRole) ? requestedRole : ""; -let assets: MediaAsset[] = [], unavailable = false; -try { const query = new URLSearchParams({ entity_type: type }); if (selectedRole) query.set("media_role", selectedRole); assets = await api(`/api/v1/media/catalog?${query}`); if (search) { const needle = search.toLocaleLowerCase("ru-RU"); assets = assets.filter(asset => (asset.label ?? "").toLocaleLowerCase("ru-RU").includes(needle)); } } catch { unavailable = true; } +let assets: MediaAsset[] = [], allAssets: MediaAsset[] = [], unavailable = false; +try { const query = new URLSearchParams({ entity_type: type }); if (selectedRole) query.set("media_role", selectedRole); allAssets = await api(`/api/v1/media/catalog?${query}`); if (search) { const needle = search.toLocaleLowerCase("ru-RU"); allAssets = allAssets.filter(asset => (asset.label ?? "").toLocaleLowerCase("ru-RU").includes(needle)); } if (allAssets.length > 0 && offset >= allAssets.length) offset = Math.floor((allAssets.length - 1) / pageLimit) * pageLimit; assets = allAssets.slice(offset, offset + pageLimit); } catch { unavailable = true; } if (unavailable) { Astro.response.status = 503; Astro.response.headers.set("Retry-After", "60"); @@ -21,10 +26,11 @@ if (unavailable) { const labels: Record = {fish:"Рыбы",waterbody:"Водоёмы",tackle:"Снасти и приманки",reference:"Справочные материалы"}; --- - +
- +
{roleOptions[type]?.length > 0 && }{(selectedRole || search) && Сбросить}
- {unavailable ? : assets.length ?
{assets.map(asset =>

{asset.label ?? "Без подписи"}

{labels[asset.entity_type]}
)}
: } + {unavailable ? : assets.length ?
{assets.map(asset =>

{asset.label ?? "Без подписи"}

{labels[asset.entity_type]} · {mediaRoleLabel(asset.media_role ?? "")}Источник: {asset.source_system}
)}
: } + {!unavailable && }