Now using attributes instead of tag groups

This commit is contained in:
DrMint 2024-05-15 15:37:44 +02:00
parent 600a2374e1
commit 0e148f06cf
23 changed files with 356 additions and 292 deletions

View File

@ -0,0 +1,87 @@
---
import { AttributeTypes, type EndpointAttribute } from "src/shared/payload/payload-sdk";
import Metadata from "./Metadata.astro";
import { getI18n } from "src/i18n/i18n";
import ErrorMessage from "./ErrorMessage.astro";
import type { Attribute } from "src/utils/attributes";
interface Props {
attributes: (EndpointAttribute | Attribute)[];
}
const { attributes } = Astro.props;
const { getLocalizedMatch, getLocalizedUrl, formatNumber } = await getI18n(
Astro.locals.currentLocale
);
---
{/* ------------------------------------------- HTML ------------------------------------------- */}
<div>
{
attributes.map((attribute) => {
if ("title" in attribute) {
return <Metadata {...attribute} />;
}
const { icon, translations, value, type } = attribute;
const translation = getLocalizedMatch(translations);
switch (type) {
case AttributeTypes.Number:
return (
<Metadata
icon={icon}
title={translation.name}
values={[{ name: formatNumber(value) }]}
withBorder={false}
/>
);
case AttributeTypes.Text:
return (
<Metadata
icon={icon}
title={translation.name}
values={[{ name: value }]}
withBorder={false}
/>
);
case AttributeTypes.Tags:
return (
<Metadata
icon={icon}
title={translation.name}
values={value.map(({ translations, page }) => ({
name: getLocalizedMatch(translations).name,
...(page ? { href: getLocalizedUrl(`/pages/${page.slug}`) } : {}),
}))}
/>
);
default:
return (
<ErrorMessage
title={`Unknown attribute type: ${type}`}
description="Please contact website technical administrator."
/>
);
}
})
}
<slot />
</div>
{/* ------------------------------------------- CSS -------------------------------------------- */}
<style>
div {
display: flex;
flex-direction: column;
gap: 2em;
@media (max-width: 35rem) {
gap: 3em;
}
}
</style>

View File

@ -0,0 +1,83 @@
---
import { AttributeTypes, type EndpointAttribute } from "src/shared/payload/payload-sdk";
import InlineMetadata from "./InlineMetadata.astro";
import { getI18n } from "src/i18n/i18n";
import ErrorMessage from "./ErrorMessage.astro";
import type { Attribute } from "src/utils/attributes";
interface Props {
attributes: (EndpointAttribute | Attribute)[];
}
const { attributes } = Astro.props;
const { getLocalizedMatch, getLocalizedUrl, formatNumber } = await getI18n(
Astro.locals.currentLocale
);
---
{/* ------------------------------------------- HTML ------------------------------------------- */}
<div id="container">
{
attributes.map((attribute) => {
if ("title" in attribute) {
return <InlineMetadata {...attribute} />;
}
const { icon, translations, value, type } = attribute;
const translation = getLocalizedMatch(translations);
switch (type) {
case AttributeTypes.Number:
return (
<InlineMetadata
icon={icon}
title={translation.name}
values={[{ name: formatNumber(value) }]}
withBorder={false}
/>
);
case AttributeTypes.Text:
return (
<InlineMetadata
icon={icon}
title={translation.name}
values={[{ name: value }]}
withBorder={false}
/>
);
case AttributeTypes.Tags:
return (
<InlineMetadata
icon={icon}
title={translation.name}
values={value.map(({ translations, page }) => ({
name: getLocalizedMatch(translations).name,
...(page ? { href: getLocalizedUrl(`/pages/${page.slug}`) } : {}),
}))}
/>
);
default:
return (
<ErrorMessage
title={`Unknown attribute type: ${type}`}
description="Please contact website technical administrator."
/>
);
}
})
}
<slot />
</div>
{/* ------------------------------------------- CSS -------------------------------------------- */}
<style>
#container {
display: flex;
gap: 0.5em 1.5em;
flex-wrap: wrap;
}
</style>

View File

@ -1,6 +1,7 @@
--- ---
import type { EndpointCredit } from "src/shared/payload/payload-sdk"; import type { EndpointCredit } from "src/shared/payload/payload-sdk";
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import InlineMetadata from "./InlineMetadata.astro";
interface Props { interface Props {
credits: EndpointCredit[]; credits: EndpointCredit[];
@ -12,13 +13,14 @@ const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
{/* ------------------------------------------- HTML ------------------------------------------- */} {/* ------------------------------------------- HTML ------------------------------------------- */}
<div id="credits"> <div>
{ {
credits.map(({ recorders, role: { translations } }) => ( credits.map(({ recorders, role: { icon, translations } }) => (
<div> <InlineMetadata
<p>{getLocalizedMatch(translations).name}</p> icon={icon}
{recorders.map(({ username }) => username).join(", ")} title={getLocalizedMatch(translations).name}
</div> values={recorders.map(({ username }) => ({ name: username }))}
/>
)) ))
} }
</div> </div>
@ -26,21 +28,12 @@ const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
{/* ------------------------------------------- CSS -------------------------------------------- */} {/* ------------------------------------------- CSS -------------------------------------------- */}
<style> <style>
#credits { div {
margin-top: 0.5em; display: grid;
display: flex; gap: 2em;
flex-direction: column;
gap: 0.5em 1.5em;
& > div { @media (max-width: 35rem) {
font-weight: 400; gap: 3em;
font-size: 80%;
display: flex;
gap: 0.5em;
& > p {
color: var(--color-base-750);
}
} }
} }
</style> </style>

View File

@ -0,0 +1,38 @@
---
import { Icon } from "astro-icon/components";
import type { Attribute } from "src/utils/attributes";
interface Props extends Attribute {}
const { icon, title, values } = Astro.props;
if (values.length === 0) return;
---
{/* ------------------------------------------- HTML ------------------------------------------- */}
<div id="container">
<Icon name={icon} />
<p>{title}</p>
<div>{values.map(({ name }) => name).join(", ")}</div>
</div>
{/* ------------------------------------------- CSS -------------------------------------------- */}
<style>
#container {
font-weight: 400;
font-size: 80%;
display: flex;
gap: 0.5em;
& > p {
color: var(--color-base-750);
}
& > svg {
color: var(--color-base-750);
flex-shrink: 0;
}
}
</style>

View File

@ -1,64 +0,0 @@
---
import { Icon } from "astro-icon/components";
import { getI18n } from "src/i18n/i18n";
import type { EndpointTagsGroup } from "src/shared/payload/payload-sdk";
interface Props {
tagGroups: (EndpointTagsGroup | { title: string; icon: string; values: string[] })[];
}
const { tagGroups } = Astro.props;
const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
const groups = tagGroups.map((group) => {
if ("title" in group) {
return group;
} else {
return {
title: getLocalizedMatch(group.translations).name,
icon: group.icon,
values: group.tags.map(({ translations }) => getLocalizedMatch(translations).name),
};
}
});
---
{/* ------------------------------------------- HTML ------------------------------------------- */}
<div id="tags">
{
groups.map(({ icon, title, values }) => (
<div>
<Icon name={icon} />
<p>{title}</p>
<div>{values.join(", ")}</div>
</div>
))
}
</div>
{/* ------------------------------------------- CSS -------------------------------------------- */}
<style>
#tags {
display: flex;
gap: 0.5em 1.5em;
flex-wrap: wrap;
& > div {
font-weight: 400;
font-size: 80%;
display: flex;
gap: 0.5em;
& > p {
color: var(--color-base-750);
}
& > svg {
color: var(--color-base-750);
flex-shrink: 0;
}
}
}
</style>

View File

@ -5,12 +5,12 @@ import {
type PayloadImage, type PayloadImage,
type RichTextContent, type RichTextContent,
} from "src/shared/payload/payload-sdk"; } from "src/shared/payload/payload-sdk";
import TagGroups from "./TagGroups.astro";
import Credits from "./Credits.astro"; import Credits from "./Credits.astro";
import DownloadButton from "./DownloadButton.astro"; import DownloadButton from "./DownloadButton.astro";
import AppLayoutTitle from "./AppLayout/components/AppLayoutTitle.astro"; import AppLayoutTitle from "./AppLayout/components/AppLayoutTitle.astro";
import type { ComponentProps } from "astro/types"; import type { ComponentProps } from "astro/types";
import AppLayoutDescription from "./AppLayout/components/AppLayoutDescription.astro"; import AppLayoutDescription from "./AppLayout/components/AppLayoutDescription.astro";
import Attributes from "./Attributes.astro";
interface Props { interface Props {
previousImageHref?: string | undefined; previousImageHref?: string | undefined;
@ -20,7 +20,8 @@ interface Props {
title: string; title: string;
subtitle?: string | undefined; subtitle?: string | undefined;
description?: RichTextContent | undefined; description?: RichTextContent | undefined;
tagGroups?: ComponentProps<typeof TagGroups>["tagGroups"] | undefined; attributes?: ComponentProps<typeof Attributes>["attributes"] | undefined;
metaAttributes?: ComponentProps<typeof Attributes>["attributes"] | undefined;
credits?: EndpointCredit[] | undefined; credits?: EndpointCredit[] | undefined;
filename?: string | undefined; filename?: string | undefined;
} }
@ -29,8 +30,9 @@ const {
nextImageHref, nextImageHref,
previousImageHref, previousImageHref,
image: { url, width, height }, image: { url, width, height },
tagGroups = [], attributes = [],
credits, metaAttributes = [],
credits = [],
description, description,
pretitle, pretitle,
title, title,
@ -63,7 +65,7 @@ const smallTitle = !subtitle && !pretitle;
id="info" id="info"
class:list={{ class:list={{
complex: complex:
(tagGroups && tagGroups.length > 0) || (credits && credits.length > 0) || description, attributes.length > 0 || metaAttributes.length > 0 || credits.length > 0 || description,
}}> }}>
<div> <div>
{ {
@ -75,8 +77,9 @@ const smallTitle = !subtitle && !pretitle;
} }
{description && <AppLayoutDescription description={description} />} {description && <AppLayoutDescription description={description} />}
</div> </div>
{tagGroups && tagGroups.length > 0 && <TagGroups tagGroups={tagGroups} />} {attributes.length > 0 && <Attributes attributes={attributes} />}
{credits && credits.length > 0 && <Credits credits={credits} />} {credits.length > 0 && <Credits credits={credits} />}
{metaAttributes.length > 0 && <Attributes attributes={metaAttributes} />}
{filename && <DownloadButton href={url} filename={filename} />} {filename && <DownloadButton href={url} filename={filename} />}
</div> </div>
</div> </div>

View File

@ -1,12 +1,8 @@
--- ---
import { Icon } from "astro-icon/components"; import { Icon } from "astro-icon/components";
import type { Attribute } from "src/utils/attributes";
interface Props { interface Props extends Attribute {}
icon: string;
title: string;
values: { name: string; href?: string }[];
withBorder?: boolean | undefined;
}
const { icon, title, values, withBorder = true } = Astro.props; const { icon, title, values, withBorder = true } = Astro.props;

View File

@ -12,7 +12,7 @@ const { getLocalizedMatch, getLocalizedUrl, t, formatDuration } = await getI18n(
); );
const { const {
audio: { id, translations, tagGroups, filename, thumbnail, duration }, audio: { id, translations, attributes, filename, thumbnail, duration },
} = Astro.props; } = Astro.props;
const { pretitle, title, subtitle } = const { pretitle, title, subtitle } =
@ -20,12 +20,12 @@ const { pretitle, title, subtitle } =
? getLocalizedMatch(translations) ? getLocalizedMatch(translations)
: { pretitle: undefined, title: filename, subtitle: undefined }; : { pretitle: undefined, title: filename, subtitle: undefined };
const tagsAndAttributes = [ const attributesWithMeta = [
...tagGroups, ...attributes,
{ {
title: t("global.media.attributes.duration"), title: t("global.media.attributes.duration"),
icon: "material-symbols:hourglass-empty", icon: "material-symbols:hourglass-empty",
values: [formatDuration(duration)], values: [{ name: formatDuration(duration) }],
}, },
]; ];
--- ---
@ -38,7 +38,7 @@ const tagsAndAttributes = [
subtitle={subtitle} subtitle={subtitle}
thumbnail={thumbnail} thumbnail={thumbnail}
href={getLocalizedUrl(`/audios/${id}`)} href={getLocalizedUrl(`/audios/${id}`)}
tagGroups={tagsAndAttributes} attributes={attributesWithMeta}
icon="material-symbols:music-note" icon="material-symbols:music-note"
iconHoverLabel={t("global.previewTypes.audio")} iconHoverLabel={t("global.previewTypes.audio")}
smallTitle={title === filename} smallTitle={title === filename}

View File

@ -10,7 +10,7 @@ interface Props {
const { getLocalizedMatch, getLocalizedUrl, t } = await getI18n(Astro.locals.currentLocale); const { getLocalizedMatch, getLocalizedUrl, t } = await getI18n(Astro.locals.currentLocale);
const { const {
collectible: { slug, translations, thumbnail, tagGroups }, collectible: { slug, translations, thumbnail, attributes },
} = Astro.props; } = Astro.props;
const { title, pretitle, subtitle } = getLocalizedMatch(translations); const { title, pretitle, subtitle } = getLocalizedMatch(translations);
@ -24,7 +24,7 @@ const { title, pretitle, subtitle } = getLocalizedMatch(translations);
subtitle={subtitle} subtitle={subtitle}
thumbnail={thumbnail} thumbnail={thumbnail}
href={getLocalizedUrl(`/collectibles/${slug}`)} href={getLocalizedUrl(`/collectibles/${slug}`)}
tagGroups={tagGroups} attributes={attributes}
icon="material-symbols:category" icon="material-symbols:category"
iconHoverLabel={t("global.previewTypes.collectible")} iconHoverLabel={t("global.previewTypes.collectible")}
disableRoundedTop disableRoundedTop

View File

@ -4,7 +4,7 @@ import Card from "components/Card.astro";
import { Icon } from "astro-icon/components"; import { Icon } from "astro-icon/components";
import type { ComponentProps } from "astro/types"; import type { ComponentProps } from "astro/types";
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import InlineTagGroups from "components/InlineTagGroups.astro"; import InlineAttributes from "components/InlineAttributes.astro";
interface Props { interface Props {
thumbnail?: PayloadImage | undefined; thumbnail?: PayloadImage | undefined;
@ -12,7 +12,7 @@ interface Props {
title: string; title: string;
subtitle?: string | undefined; subtitle?: string | undefined;
href?: string | undefined; href?: string | undefined;
tagGroups?: ComponentProps<typeof InlineTagGroups>["tagGroups"]; attributes?: ComponentProps<typeof InlineAttributes>["attributes"];
disableRoundedTop?: boolean; disableRoundedTop?: boolean;
smallTitle?: boolean; smallTitle?: boolean;
icon?: string; icon?: string;
@ -27,7 +27,7 @@ const {
pretitle, pretitle,
subtitle, subtitle,
href, href,
tagGroups = [], attributes = [],
smallTitle = false, smallTitle = false,
disableRoundedTop = false, disableRoundedTop = false,
icon = "material-symbols:unknown-document", icon = "material-symbols:unknown-document",
@ -63,11 +63,11 @@ const {
} }
{ {
tagGroups.length > 0 && ( attributes.length > 0 && (
<> <>
{subtitle && <hr />} {subtitle && <hr />}
<div id="tags"> <div id="tags">
<InlineTagGroups tagGroups={tagGroups} /> <InlineAttributes attributes={attributes} />
</div> </div>
</> </>
) )

View File

@ -11,7 +11,7 @@ const { getLocalizedMatch, getLocalizedUrl, t } = await getI18n(Astro.locals.cur
const { const {
image: thumbnail, image: thumbnail,
image: { id, translations, tagGroups, filename }, image: { id, translations, attributes, filename },
} = Astro.props; } = Astro.props;
const { pretitle, title, subtitle } = const { pretitle, title, subtitle } =
@ -28,7 +28,7 @@ const { pretitle, title, subtitle } =
subtitle={subtitle} subtitle={subtitle}
thumbnail={thumbnail} thumbnail={thumbnail}
href={getLocalizedUrl(`/images/${id}`)} href={getLocalizedUrl(`/images/${id}`)}
tagGroups={tagGroups} attributes={attributes}
icon="material-symbols:imagesmode" icon="material-symbols:imagesmode"
iconHoverLabel={t("global.previewTypes.image")} iconHoverLabel={t("global.previewTypes.image")}
smallTitle={title === filename} smallTitle={title === filename}

View File

@ -10,7 +10,7 @@ interface Props {
const { getLocalizedMatch, getLocalizedUrl, t } = await getI18n(Astro.locals.currentLocale); const { getLocalizedMatch, getLocalizedUrl, t } = await getI18n(Astro.locals.currentLocale);
const { const {
page: { slug, translations, thumbnail, tagGroups }, page: { slug, translations, thumbnail, attributes },
} = Astro.props; } = Astro.props;
const { title, pretitle, subtitle } = getLocalizedMatch(translations); const { title, pretitle, subtitle } = getLocalizedMatch(translations);
@ -24,7 +24,7 @@ const { title, pretitle, subtitle } = getLocalizedMatch(translations);
subtitle={subtitle} subtitle={subtitle}
thumbnail={thumbnail} thumbnail={thumbnail}
href={getLocalizedUrl(`/pages/${slug}`)} href={getLocalizedUrl(`/pages/${slug}`)}
tagGroups={tagGroups} attributes={attributes}
icon="material-symbols:docs" icon="material-symbols:docs"
iconHoverLabel={t("global.previewTypes.page")} iconHoverLabel={t("global.previewTypes.page")}
/> />

View File

@ -12,7 +12,7 @@ const { getLocalizedMatch, getLocalizedUrl, t, formatDuration } = await getI18n(
); );
const { const {
video: { id, translations, tagGroups, filename, thumbnail, duration }, video: { id, translations, attributes, filename, thumbnail, duration },
} = Astro.props; } = Astro.props;
const { pretitle, title, subtitle } = const { pretitle, title, subtitle } =
@ -20,12 +20,12 @@ const { pretitle, title, subtitle } =
? getLocalizedMatch(translations) ? getLocalizedMatch(translations)
: { pretitle: undefined, title: filename, subtitle: undefined }; : { pretitle: undefined, title: filename, subtitle: undefined };
const tagsAndAttributes = [ const attributesWithMeta = [
...tagGroups, ...attributes,
{ {
title: t("global.media.attributes.duration"), title: t("global.media.attributes.duration"),
icon: "material-symbols:hourglass-empty", icon: "material-symbols:hourglass-empty",
values: [formatDuration(duration)], values: [{ name: formatDuration(duration) }],
}, },
]; ];
--- ---
@ -38,7 +38,7 @@ const tagsAndAttributes = [
subtitle={subtitle} subtitle={subtitle}
thumbnail={thumbnail} thumbnail={thumbnail}
href={getLocalizedUrl(`/videos/${id}`)} href={getLocalizedUrl(`/videos/${id}`)}
tagGroups={tagsAndAttributes} attributes={attributesWithMeta}
icon="material-symbols:smart-display" icon="material-symbols:smart-display"
iconHoverLabel={t("global.previewTypes.video")} iconHoverLabel={t("global.previewTypes.video")}
smallTitle={title === filename} smallTitle={title === filename}

View File

@ -1,50 +0,0 @@
---
import type { EndpointTagsGroup } from "src/shared/payload/payload-sdk";
import { getI18n } from "src/i18n/i18n";
import Metadata from "./Metadata.astro";
import type { ComponentProps } from "astro/types";
interface Props {
tagGroups: (EndpointTagsGroup | ComponentProps<typeof Metadata>)[];
}
const { tagGroups } = Astro.props;
const { getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
const groups = tagGroups.map((group) => {
if ("title" in group) {
return group;
} else {
return {
title: getLocalizedMatch(group.translations).name,
icon: group.icon,
values: group.tags.map(({ translations, page }) => ({
name: getLocalizedMatch(translations).name,
...(page ? { href: getLocalizedUrl(`/pages/${page.slug}`) } : {}),
})),
withBorder: true,
};
}
});
---
{/* ------------------------------------------- HTML ------------------------------------------- */}
<div>
{groups.map((group) => <Metadata {...group} />)}
<slot />
</div>
{/* ------------------------------------------- CSS -------------------------------------------- */}
<style>
div {
display: flex;
flex-direction: column;
gap: 2em;
@media (max-width: 35rem) {
gap: 3em;
}
}
</style>

View File

@ -3,13 +3,13 @@ import RichText from "components/RichText/RichText.astro";
import { payload, type EndpointPage } from "src/shared/payload/payload-sdk"; import { payload, type EndpointPage } from "src/shared/payload/payload-sdk";
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro"; import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
import MasoTarget from "components/Maso/MasoTarget.astro"; import MasoTarget from "components/Maso/MasoTarget.astro";
import TagGroups from "components/TagGroups.astro";
import TableOfContent from "components/TableOfContent/TableOfContent.astro"; import TableOfContent from "components/TableOfContent/TableOfContent.astro";
import LanguageOverride from "components/LanguageOverride.astro"; import LanguageOverride from "components/LanguageOverride.astro";
import Credits from "components/Credits.astro"; import Credits from "components/Credits.astro";
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import AsideLayout from "components/AppLayout/AsideLayout.astro"; import AsideLayout from "components/AppLayout/AsideLayout.astro";
import AppLayoutDescription from "components/AppLayout/components/AppLayoutDescription.astro"; import AppLayoutDescription from "components/AppLayout/components/AppLayoutDescription.astro";
import Attributes from "components/Attributes.astro";
export const partial = true; export const partial = true;
@ -22,7 +22,7 @@ interface Props {
const reqUrl = new URL(Astro.request.url); const reqUrl = new URL(Astro.request.url);
const lang = Astro.props.lang ?? reqUrl.searchParams.get("lang")!; const lang = Astro.props.lang ?? reqUrl.searchParams.get("lang")!;
const slug = Astro.props.slug ?? reqUrl.searchParams.get("slug")!; const slug = Astro.props.slug ?? reqUrl.searchParams.get("slug")!;
const { translations, thumbnail, tagGroups, createdAt, updatedAt, updatedBy } = const { translations, thumbnail, createdAt, updatedAt, updatedBy, attributes } =
Astro.props.page ?? (await payload.getPage(slug)); Astro.props.page ?? (await payload.getPage(slug));
const { getLocalizedUrl, t, formatDate } = await getI18n(Astro.locals.currentLocale); const { getLocalizedUrl, t, formatDate } = await getI18n(Astro.locals.currentLocale);
@ -31,7 +31,7 @@ const { getLocalizedMatch } = await getI18n(lang);
const { pretitle, title, subtitle, summary, content, credits, toc, language, sourceLanguage } = const { pretitle, title, subtitle, summary, content, credits, toc, language, sourceLanguage } =
getLocalizedMatch(translations); getLocalizedMatch(translations);
const attributes = [ const metaAttributes = [
{ {
title: t("global.media.attributes.createdAt"), title: t("global.media.attributes.createdAt"),
icon: "material-symbols:calendar-add-on-outline", icon: "material-symbols:calendar-add-on-outline",
@ -47,7 +47,7 @@ const attributes = [
]; ];
if (updatedBy) { if (updatedBy) {
attributes.push({ metaAttributes.push({
title: t("global.media.attributes.updatedBy"), title: t("global.media.attributes.updatedBy"),
icon: "material-symbols:person-edit-outline", icon: "material-symbols:person-edit-outline",
values: [{ name: updatedBy.username }], values: [{ name: updatedBy.username }],
@ -82,9 +82,9 @@ if (updatedBy) {
<Fragment slot="meta"> <Fragment slot="meta">
{summary && <AppLayoutDescription description={summary} />} {summary && <AppLayoutDescription description={summary} />}
{ {
tagGroups.length > 0 && ( attributes.length > 0 && (
<div id="tags"> <div id="tags">
<TagGroups tagGroups={tagGroups} /> <Attributes attributes={attributes} />
</div> </div>
) )
} }
@ -108,7 +108,7 @@ if (updatedBy) {
{credits.length > 0 && <Credits credits={credits} />} {credits.length > 0 && <Credits credits={credits} />}
</div> </div>
{attributes.length > 0 && <TagGroups tagGroups={attributes} />} {metaAttributes.length > 0 && <Attributes attributes={metaAttributes} />}
{toc.length > 0 && <TableOfContent toc={toc} />} {toc.length > 0 && <TableOfContent toc={toc} />}
</div> </div>

View File

@ -1,11 +1,11 @@
--- ---
import AppLayout from "components/AppLayout/AppLayout.astro"; import AppLayout from "components/AppLayout/AppLayout.astro";
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro"; import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
import Attributes from "components/Attributes.astro";
import AudioPlayer from "components/AudioPlayer.astro"; import AudioPlayer from "components/AudioPlayer.astro";
import Credits from "components/Credits.astro"; import Credits from "components/Credits.astro";
import DownloadButton from "components/DownloadButton.astro"; import DownloadButton from "components/DownloadButton.astro";
import RichText from "components/RichText/RichText.astro"; import RichText from "components/RichText/RichText.astro";
import TagGroups from "components/TagGroups.astro";
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import { payload } from "src/shared/payload/payload-sdk"; import { payload } from "src/shared/payload/payload-sdk";
import { formatInlineTitle, formatRichTextToString } from "src/utils/format"; import { formatInlineTitle, formatRichTextToString } from "src/utils/format";
@ -22,7 +22,7 @@ const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n(
); );
const { const {
translations, translations,
tagGroups, attributes,
filename, filename,
url, url,
credits, credits,
@ -36,7 +36,7 @@ const { pretitle, title, subtitle, description } = getLocalizedMatch(translation
const smallTitle = !subtitle && !pretitle; const smallTitle = !subtitle && !pretitle;
const attributes = [ const metaAttributes = [
...(filename && title !== filename ...(filename && title !== filename
? [ ? [
{ {
@ -89,9 +89,9 @@ const attributes = [
) )
} }
{description && <RichText content={description} />} {description && <RichText content={description} />}
{tagGroups.length > 0 && <TagGroups tagGroups={tagGroups} />} {attributes.length > 0 && <Attributes attributes={attributes} />}
{credits.length > 0 && <Credits credits={credits} />} {credits.length > 0 && <Credits credits={credits} />}
{attributes.length > 0 && <TagGroups tagGroups={attributes} />} {metaAttributes.length > 0 && <Attributes attributes={metaAttributes} />}
<DownloadButton href={url} filename={filename} /> <DownloadButton href={url} filename={filename} />
</div> </div>
</div> </div>

View File

@ -4,10 +4,10 @@ import RichText from "components/RichText/RichText.astro";
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import { Collections, type EndpointCollectible } from "src/shared/payload/payload-sdk"; import { Collections, type EndpointCollectible } from "src/shared/payload/payload-sdk";
import { formatInlineTitle } from "src/utils/format"; import { formatInlineTitle } from "src/utils/format";
import InlineTagGroups from "../../../../../../components/InlineTagGroups.astro";
import Card from "components/Card.astro"; import Card from "components/Card.astro";
import AudioPlayer from "components/AudioPlayer.astro"; import AudioPlayer from "components/AudioPlayer.astro";
import VideoPlayer from "components/VideoPlayer.astro"; import VideoPlayer from "components/VideoPlayer.astro";
import InlineAttributes from "components/InlineAttributes.astro";
interface Props { interface Props {
content: EndpointCollectible["contents"][number]; content: EndpointCollectible["contents"][number];
@ -99,9 +99,9 @@ const href = (() => {
(content.relationTo === Collections.Pages || (content.relationTo === Collections.Pages ||
content.relationTo === Collections.Audios || content.relationTo === Collections.Audios ||
content.relationTo === Collections.Videos) && content.relationTo === Collections.Videos) &&
content.value.tagGroups.length > 0 && ( content.value.attributes.length > 0 && (
<div id="tags"> <div id="tags">
<InlineTagGroups tagGroups={content.value.tagGroups} /> <InlineAttributes attributes={content.value.attributes} />
</div> </div>
) )
} }

View File

@ -18,15 +18,14 @@ if (galleryImage instanceof Response) {
} }
const { parentPages, previousIndex, nextIndex, image } = galleryImage; const { parentPages, previousIndex, nextIndex, image } = galleryImage;
const { filename, translations, createdAt, updatedAt, credits, tagGroups } = image; const { filename, translations, createdAt, updatedAt, credits, attributes } = image;
const { pretitle, title, subtitle, description } = const { pretitle, title, subtitle, description } =
translations.length > 0 translations.length > 0
? getLocalizedMatch(translations) ? getLocalizedMatch(translations)
: { pretitle: undefined, title: filename, subtitle: undefined, description: undefined }; : { pretitle: undefined, title: filename, subtitle: undefined, description: undefined };
const tagsAndAttributes = [ const metaAttributes = [
...tagGroups,
...(filename && title !== filename ...(filename && title !== filename
? [ ? [
{ {
@ -74,7 +73,8 @@ const tagsAndAttributes = [
: undefined} : undefined}
description={description} description={description}
filename={filename} filename={filename}
tagGroups={tagsAndAttributes} attributes={attributes}
metaAttributes={metaAttributes}
credits={credits} credits={credits}
/> />
</AppLayout> </AppLayout>

View File

@ -1,7 +1,6 @@
--- ---
import AppLayout from "components/AppLayout/AppLayout.astro"; import AppLayout from "components/AppLayout/AppLayout.astro";
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro"; import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
import TagGroups from "components/TagGroups.astro";
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import { CollectibleNature, payload } from "src/shared/payload/payload-sdk"; import { CollectibleNature, payload } from "src/shared/payload/payload-sdk";
import { fetchOr404 } from "src/utils/responses"; import { fetchOr404 } from "src/utils/responses";
@ -16,6 +15,7 @@ import { formatInlineTitle, formatLocale, formatRichTextToString } from "src/uti
import AsideLayout from "components/AppLayout/AsideLayout.astro"; import AsideLayout from "components/AppLayout/AsideLayout.astro";
import AppLayoutDescription from "components/AppLayout/components/AppLayoutDescription.astro"; import AppLayoutDescription from "components/AppLayout/components/AppLayoutDescription.astro";
import { convert } from "src/utils/currencies"; import { convert } from "src/utils/currencies";
import Attributes from "components/Attributes.astro";
const { slug } = Astro.params; const { slug } = Astro.params;
const { getLocalizedMatch, getLocalizedUrl, t, formatDate, formatPrice } = await getI18n( const { getLocalizedMatch, getLocalizedUrl, t, formatDate, formatPrice } = await getI18n(
@ -41,7 +41,7 @@ const {
scans, scans,
subitems, subitems,
parentPages, parentPages,
tagGroups, attributes,
contents, contents,
createdAt, createdAt,
updatedAt, updatedAt,
@ -53,7 +53,7 @@ const {
const translation = getLocalizedMatch(translations); const translation = getLocalizedMatch(translations);
const { pretitle, title, subtitle, description } = translation; const { pretitle, title, subtitle, description } = translation;
const attributes = [ const metaAttributes = [
{ {
title: t("global.media.attributes.createdAt"), title: t("global.media.attributes.createdAt"),
icon: "material-symbols:calendar-add-on-outline", icon: "material-symbols:calendar-add-on-outline",
@ -69,7 +69,7 @@ const attributes = [
]; ];
if (updatedBy) { if (updatedBy) {
attributes.push({ metaAttributes.push({
title: t("global.media.attributes.updatedBy"), title: t("global.media.attributes.updatedBy"),
icon: "material-symbols:person-edit-outline", icon: "material-symbols:person-edit-outline",
values: [{ name: updatedBy.username }], values: [{ name: updatedBy.username }],
@ -77,8 +77,7 @@ if (updatedBy) {
}); });
} }
const tagGroupWithAttributes = [ const additionalAttributes = [
...tagGroups,
{ {
title: t("collectibles.nature"), title: t("collectibles.nature"),
icon: "material-symbols:leaf-spark-outline", icon: "material-symbols:leaf-spark-outline",
@ -96,7 +95,7 @@ const tagGroupWithAttributes = [
]; ];
if (releaseDate) { if (releaseDate) {
tagGroupWithAttributes.push({ additionalAttributes.push({
title: t("collectibles.releaseDate"), title: t("collectibles.releaseDate"),
icon: "material-symbols:calendar-month-outline", icon: "material-symbols:calendar-month-outline",
values: [{ name: formatDate(new Date(releaseDate)) }], values: [{ name: formatDate(new Date(releaseDate)) }],
@ -105,7 +104,7 @@ if (releaseDate) {
} }
if (languages.length > 0) { if (languages.length > 0) {
tagGroupWithAttributes.push({ additionalAttributes.push({
title: t("collectibles.languages"), title: t("collectibles.languages"),
icon: "material-symbols:translate", icon: "material-symbols:translate",
values: languages.map((lang) => ({ name: formatLocale(lang) })), values: languages.map((lang) => ({ name: formatLocale(lang) })),
@ -127,7 +126,7 @@ if (price) {
priceText += ` (${formatPrice(convertedPrice)})`; priceText += ` (${formatPrice(convertedPrice)})`;
} }
tagGroupWithAttributes.push({ additionalAttributes.push({
title: t("collectibles.price"), title: t("collectibles.price"),
icon: "material-symbols:sell-outline", icon: "material-symbols:sell-outline",
values: [{ name: priceText }], values: [{ name: priceText }],
@ -194,9 +193,9 @@ if (price) {
<Fragment slot="aside"> <Fragment slot="aside">
{ {
attributes.length > 0 && ( metaAttributes.length > 0 && (
<div id="attributes"> <div id="attributes">
<TagGroups tagGroups={attributes} /> <Attributes attributes={metaAttributes} />
</div> </div>
) )
} }
@ -206,7 +205,7 @@ if (price) {
{description && <AppLayoutDescription description={description} />} {description && <AppLayoutDescription description={description} />}
<div id="tags"> <div id="tags">
<TagGroups tagGroups={tagGroupWithAttributes}> <Attributes attributes={[...attributes, ...additionalAttributes]}>
<AvailabilityInfo urls={urls} price={price !== undefined} releaseDate={releaseDate} /> <AvailabilityInfo urls={urls} price={price !== undefined} releaseDate={releaseDate} />
{size && <SizeInfo size={size} />} {size && <SizeInfo size={size} />}
@ -214,7 +213,7 @@ if (price) {
{weight && <WeightInfo weight={weight} />} {weight && <WeightInfo weight={weight} />}
{pageInfo && <PageInfo pageInfo={pageInfo} />} {pageInfo && <PageInfo pageInfo={pageInfo} />}
</TagGroups> </Attributes>
</div> </div>
</Fragment> </Fragment>

View File

@ -13,15 +13,14 @@ if (image instanceof Response) {
} }
const { getLocalizedMatch, formatDate, t } = await getI18n(Astro.locals.currentLocale); const { getLocalizedMatch, formatDate, t } = await getI18n(Astro.locals.currentLocale);
const { filename, translations, tagGroups, credits, createdAt, updatedAt } = image; const { filename, translations, attributes, credits, createdAt, updatedAt } = image;
const { pretitle, title, subtitle, description } = const { pretitle, title, subtitle, description } =
translations.length > 0 translations.length > 0
? getLocalizedMatch(translations) ? getLocalizedMatch(translations)
: { pretitle: undefined, title: filename, subtitle: undefined, description: undefined }; : { pretitle: undefined, title: filename, subtitle: undefined, description: undefined };
const tagsAndAttributes = [ const metaAttributes = [
...tagGroups,
...(filename && title !== filename ...(filename && title !== filename
? [ ? [
{ {
@ -62,7 +61,8 @@ const tagsAndAttributes = [
subtitle={subtitle} subtitle={subtitle}
description={description} description={description}
filename={filename} filename={filename}
tagGroups={tagsAndAttributes} attributes={attributes}
metaAttributes={metaAttributes}
credits={credits} credits={credits}
/> />
</AppLayout> </AppLayout>

View File

@ -1,10 +1,10 @@
--- ---
import AppLayout from "components/AppLayout/AppLayout.astro"; import AppLayout from "components/AppLayout/AppLayout.astro";
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro"; import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
import Attributes from "components/Attributes.astro";
import Credits from "components/Credits.astro"; import Credits from "components/Credits.astro";
import DownloadButton from "components/DownloadButton.astro"; import DownloadButton from "components/DownloadButton.astro";
import RichText from "components/RichText/RichText.astro"; import RichText from "components/RichText/RichText.astro";
import TagGroups from "components/TagGroups.astro";
import VideoPlayer from "components/VideoPlayer.astro"; import VideoPlayer from "components/VideoPlayer.astro";
import { getI18n } from "src/i18n/i18n"; import { getI18n } from "src/i18n/i18n";
import { payload } from "src/shared/payload/payload-sdk"; import { payload } from "src/shared/payload/payload-sdk";
@ -22,7 +22,7 @@ const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n(
); );
const { const {
translations, translations,
tagGroups, attributes,
filename, filename,
url, url,
credits, credits,
@ -35,7 +35,7 @@ const {
const { pretitle, title, subtitle, description } = getLocalizedMatch(translations); const { pretitle, title, subtitle, description } = getLocalizedMatch(translations);
const smallTitle = !subtitle && !pretitle; const smallTitle = !subtitle && !pretitle;
const attributes = [ const metaAttributes = [
...(filename && title !== filename ...(filename && title !== filename
? [ ? [
{ {
@ -88,9 +88,9 @@ const attributes = [
) )
} }
{description && <RichText content={description} />} {description && <RichText content={description} />}
{tagGroups.length > 0 && <TagGroups tagGroups={tagGroups} />} {attributes.length > 0 && <Attributes attributes={attributes} />}
{credits.length > 0 && <Credits credits={credits} />} {credits.length > 0 && <Credits credits={credits} />}
{attributes.length > 0 && <TagGroups tagGroups={attributes} />} {metaAttributes.length > 0 && <Attributes attributes={metaAttributes} />}
<DownloadButton href={url} filename={filename} /> <DownloadButton href={url} filename={filename} />
</div> </div>
</div> </div>

View File

@ -32,7 +32,6 @@ export interface Config {
"videos-channels": VideosChannel; "videos-channels": VideosChannel;
scans: Scan; scans: Scan;
tags: Tag; tags: Tag;
"tags-groups": TagsGroup;
attributes: Attribute; attributes: Attribute;
"credits-roles": CreditsRole; "credits-roles": CreditsRole;
recorders: Recorder; recorders: Recorder;
@ -56,8 +55,8 @@ export interface Page {
slug: string; slug: string;
thumbnail?: string | Image | null; thumbnail?: string | Image | null;
backgroundImage?: string | Image | null; backgroundImage?: string | Image | null;
attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null;
tags?: (string | Tag)[] | null; tags?: (string | Tag)[] | null;
attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null;
translations: { translations: {
language: string | Language; language: string | Language;
sourceLanguage: string | Language; sourceLanguage: string | Language;
@ -135,6 +134,7 @@ export interface Image {
}[] }[]
| null; | null;
tags?: (string | Tag)[] | null; tags?: (string | Tag)[] | null;
attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null;
credits?: Credits; credits?: Credits;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@ -177,26 +177,8 @@ export interface Language {
*/ */
export interface Tag { export interface Tag {
id: string; id: string;
name?: string | null;
slug: string; slug: string;
translations: {
language: string | Language;
name: string;
id?: string | null;
}[];
group: string | TagsGroup;
page?: (string | null) | Page; page?: (string | null) | Page;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "tags-groups".
*/
export interface TagsGroup {
id: string;
slug: string;
icon?: string | null;
translations: { translations: {
language: string | Language; language: string | Language;
name: string; name: string;
@ -205,42 +187,6 @@ export interface TagsGroup {
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "credits-roles".
*/
export interface CreditsRole {
id: string;
slug: string;
icon?: string | null;
translations: {
language: string | Language;
name: string;
id?: string | null;
}[];
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "recorders".
*/
export interface Recorder {
id: string;
username: string;
avatar?: string | Image | null;
languages?: (string | Language)[] | null;
role?: ("Admin" | "Recorder" | "Api")[] | null;
anonymize: boolean;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "TagsBlock". * via the `definition` "TagsBlock".
@ -291,6 +237,42 @@ export interface TextBlock {
blockName?: string | null; blockName?: string | null;
blockType: "textBlock"; blockType: "textBlock";
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "credits-roles".
*/
export interface CreditsRole {
id: string;
slug: string;
icon?: string | null;
translations: {
language: string | Language;
name: string;
id?: string | null;
}[];
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "recorders".
*/
export interface Recorder {
id: string;
username: string;
avatar?: string | Image | null;
languages?: (string | Language)[] | null;
role?: ("Admin" | "Recorder" | "Api")[] | null;
anonymize: boolean;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "folders". * via the `definition` "folders".
@ -371,6 +353,7 @@ export interface Collectible {
nature: "Physical" | "Digital"; nature: "Physical" | "Digital";
languages?: (string | Language)[] | null; languages?: (string | Language)[] | null;
tags?: (string | Tag)[] | null; tags?: (string | Tag)[] | null;
attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null;
translations: { translations: {
language: string | Language; language: string | Language;
pretitle?: string | null; pretitle?: string | null;
@ -635,6 +618,7 @@ export interface Audio {
id?: string | null; id?: string | null;
}[]; }[];
tags?: (string | Tag)[] | null; tags?: (string | Tag)[] | null;
attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null;
credits?: Credits; credits?: Credits;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
@ -710,6 +694,7 @@ export interface Video {
id?: string | null; id?: string | null;
}[]; }[];
tags?: (string | Tag)[] | null; tags?: (string | Tag)[] | null;
attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null;
credits?: Credits; credits?: Credits;
platformEnabled?: boolean | null; platformEnabled?: boolean | null;
platform?: { platform?: {
@ -1042,27 +1027,26 @@ export interface SectionBlock {
export enum Collections { export enum Collections {
Attributes = "attributes",
Audios = "audios", Audios = "audios",
ChronologyEvents = "chronology-events", ChronologyEvents = "chronology-events",
Collectibles = "collectibles",
CreditsRole = "credits-roles",
Currencies = "currencies", Currencies = "currencies",
Folders = "folders",
GenericContents = "generic-contents",
Images = "images",
Languages = "languages", Languages = "languages",
MediaThumbnails = "media-thumbnails",
Pages = "pages", Pages = "pages",
Recorders = "recorders", Recorders = "recorders",
Folders = "folders",
Tags = "tags",
TagsGroups = "tags-groups",
Images = "images",
Wordings = "wordings",
Collectibles = "collectibles",
GenericContents = "generic-contents",
WebsiteConfig = "website-config",
Videos = "videos",
VideosSubtitles = "videos-subtitles",
VideosChannels = "videos-channels",
MediaThumbnails = "media-thumbnails",
Scans = "scans", Scans = "scans",
CreditsRole = "credits-roles", Tags = "tags",
Attributes = "attributes", Videos = "videos",
VideosChannels = "videos-channels",
VideosSubtitles = "videos-subtitles",
Wordings = "wordings",
WebsiteConfig = "website-config",
} }
export enum CollectionGroups { export enum CollectionGroups {
@ -1507,16 +1491,6 @@ export type EndpointTag = {
}[]; }[];
}; };
export type EndpointTagsGroup = {
slug: string;
icon: string;
translations: {
language: string;
name: string;
}[];
tags: EndpointTag[];
};
export type EndpointGenericAttribute = { export type EndpointGenericAttribute = {
slug: string; slug: string;
icon: string; icon: string;
@ -1562,7 +1536,6 @@ export type EndpointCredit = {
export type EndpointPage = { export type EndpointPage = {
slug: string; slug: string;
thumbnail?: EndpointImage; thumbnail?: EndpointImage;
tagGroups: EndpointTagsGroup[];
attributes: EndpointAttribute[]; attributes: EndpointAttribute[];
backgroundImage?: EndpointImage; backgroundImage?: EndpointImage;
translations: { translations: {
@ -1592,7 +1565,7 @@ export type EndpointCollectible = {
subtitle?: string; subtitle?: string;
description?: RichTextContent; description?: RichTextContent;
}[]; }[];
tagGroups: EndpointTagsGroup[]; attributes: EndpointAttribute[];
releaseDate?: string; releaseDate?: string;
languages: string[]; languages: string[];
backgroundImage?: EndpointImage; backgroundImage?: EndpointImage;
@ -1816,7 +1789,7 @@ export type EndpointMedia = {
filesize: number; filesize: number;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
tagGroups: EndpointTagsGroup[]; attributes: EndpointAttribute[];
translations: { translations: {
language: string; language: string;
pretitle?: string; pretitle?: string;

6
src/utils/attributes.ts Normal file
View File

@ -0,0 +1,6 @@
export type Attribute = {
icon: string;
title: string;
values: { name: string; href?: string }[];
withBorder?: boolean | undefined;
};