Added recorders pages
This commit is contained in:
parent
0e148f06cf
commit
91e67a7645
3
TODO.md
3
TODO.md
|
@ -3,7 +3,6 @@
|
||||||
## Short term
|
## Short term
|
||||||
|
|
||||||
- Number of audio players seems limited (on Chrome and Firefox)
|
- Number of audio players seems limited (on Chrome and Firefox)
|
||||||
- Create a tool to upload scans images and apply them to collectible
|
|
||||||
- Automatically generate different sizes of images
|
- Automatically generate different sizes of images
|
||||||
- Handle relationship in RichText Content
|
- Handle relationship in RichText Content
|
||||||
|
|
||||||
|
@ -15,7 +14,6 @@
|
||||||
- [Timeline] Error if collectible not published?
|
- [Timeline] Error if collectible not published?
|
||||||
- [Timeline] display source language
|
- [Timeline] display source language
|
||||||
- [Timeline] Add details button in footer with credits + last updated / created
|
- [Timeline] Add details button in footer with credits + last updated / created
|
||||||
- When the tags overflow, the tag group name should be align start (see http://localhost:12499/en/pages/magnitude-negative-chapter-1)
|
|
||||||
- [SDK] create a initPayload() that return a payload sdk (and stop hard wirring to ENV or node-cache)
|
- [SDK] create a initPayload() that return a payload sdk (and stop hard wirring to ENV or node-cache)
|
||||||
- [Videos] see why no video on Firefox and no poster on Chrome https://v3.accords-library.com/en/videos/661b672825d380e548dbb8c8
|
- [Videos] see why no video on Firefox and no poster on Chrome https://v3.accords-library.com/en/videos/661b672825d380e548dbb8c8
|
||||||
- [Videos] Display platform info + channel page
|
- [Videos] Display platform info + channel page
|
||||||
|
@ -41,6 +39,7 @@
|
||||||
- Convert Rich text to simple text for indexing and open graph purposes
|
- Convert Rich text to simple text for indexing and open graph purposes
|
||||||
- Anonymous comments
|
- Anonymous comments
|
||||||
- [Images] add images group (which could be named or not)
|
- [Images] add images group (which could be named or not)
|
||||||
|
- [Recorders] add list of contributions on recorders' pages
|
||||||
|
|
||||||
## Bonus
|
## Bonus
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ const { t } = await getI18n(Astro.locals.currentLocale);
|
||||||
parentPages.length === 1 && parentPages[0] ? (
|
parentPages.length === 1 && parentPages[0] ? (
|
||||||
<ReturnToButton parentPage={parentPages[0]} />
|
<ReturnToButton parentPage={parentPages[0]} />
|
||||||
) : (
|
) : (
|
||||||
<Tooltip trigger="click">
|
<Tooltip trigger="click" class="when-js">
|
||||||
<div id="tooltip-content" slot="tooltip-content">
|
<div id="tooltip-content" slot="tooltip-content">
|
||||||
<p>{t("header.nav.parentPages.tooltip")}</p>
|
<p>{t("header.nav.parentPages.tooltip")}</p>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -8,7 +8,7 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { credits } = Astro.props;
|
const { credits } = Astro.props;
|
||||||
const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
|
const { getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||||
---
|
---
|
||||||
|
|
||||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
@ -19,7 +19,10 @@ const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
|
||||||
<Metadata
|
<Metadata
|
||||||
icon={icon}
|
icon={icon}
|
||||||
title={getLocalizedMatch(translations).name}
|
title={getLocalizedMatch(translations).name}
|
||||||
values={recorders.map(({ username }) => ({ name: username }))}
|
values={recorders.map(({ username, id }) => ({
|
||||||
|
name: username,
|
||||||
|
href: getLocalizedUrl(`/recorders/${id}`),
|
||||||
|
}))}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
---
|
---
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import type { Attribute } from "src/utils/attributes";
|
import type { Attribute } from "src/utils/attributes";
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ if (values.length === 0) return;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
gap: 0.5em 1em;
|
gap: 0.5em 1em;
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
@media (max-width: 35em) {
|
@media (max-width: 35em) {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
@ -46,7 +45,6 @@ if (values.length === 0) return;
|
||||||
|
|
||||||
& > #title {
|
& > #title {
|
||||||
display: flex;
|
display: flex;
|
||||||
place-items: center;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
& > p {
|
& > p {
|
||||||
|
|
|
@ -10,6 +10,7 @@ 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";
|
import Attributes from "components/Attributes.astro";
|
||||||
|
import type { Attribute } from "src/utils/attributes";
|
||||||
|
|
||||||
export const partial = true;
|
export const partial = true;
|
||||||
|
|
||||||
|
@ -31,18 +32,16 @@ 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 metaAttributes = [
|
const metaAttributes: Attribute[] = [
|
||||||
{
|
{
|
||||||
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",
|
||||||
values: [{ name: formatDate(new Date(createdAt)) }],
|
values: [{ name: formatDate(new Date(createdAt)) }],
|
||||||
withBorder: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("global.media.attributes.updatedAt"),
|
title: t("global.media.attributes.updatedAt"),
|
||||||
icon: "material-symbols:edit-calendar",
|
icon: "material-symbols:edit-calendar",
|
||||||
values: [{ name: formatDate(new Date(updatedAt)) }],
|
values: [{ name: formatDate(new Date(updatedAt)) }],
|
||||||
withBorder: false,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -50,7 +49,7 @@ if (updatedBy) {
|
||||||
metaAttributes.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, href: getLocalizedUrl(`/recorders/${updatedBy.id}`) }],
|
||||||
withBorder: true,
|
withBorder: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ 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";
|
import Attributes from "components/Attributes.astro";
|
||||||
|
import type { Attribute } from "src/utils/attributes";
|
||||||
|
|
||||||
const { slug } = Astro.params;
|
const { slug } = Astro.params;
|
||||||
const { getLocalizedMatch, getLocalizedUrl, t, formatDate, formatPrice } = await getI18n(
|
const { getLocalizedMatch, getLocalizedUrl, t, formatDate, formatPrice } = await getI18n(
|
||||||
|
@ -53,18 +54,16 @@ const {
|
||||||
const translation = getLocalizedMatch(translations);
|
const translation = getLocalizedMatch(translations);
|
||||||
const { pretitle, title, subtitle, description } = translation;
|
const { pretitle, title, subtitle, description } = translation;
|
||||||
|
|
||||||
const metaAttributes = [
|
const metaAttributes: Attribute[] = [
|
||||||
{
|
{
|
||||||
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",
|
||||||
values: [{ name: formatDate(new Date(createdAt)) }],
|
values: [{ name: formatDate(new Date(createdAt)) }],
|
||||||
withBorder: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("global.media.attributes.updatedAt"),
|
title: t("global.media.attributes.updatedAt"),
|
||||||
icon: "material-symbols:edit-calendar",
|
icon: "material-symbols:edit-calendar",
|
||||||
values: [{ name: formatDate(new Date(updatedAt)) }],
|
values: [{ name: formatDate(new Date(updatedAt)) }],
|
||||||
withBorder: false,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -72,7 +71,7 @@ if (updatedBy) {
|
||||||
metaAttributes.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, href: getLocalizedUrl(`/recorders/${updatedBy.id}`) }],
|
||||||
withBorder: true,
|
withBorder: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -259,7 +258,6 @@ if (price) {
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 5px 20px -10px var(--color-shadow);
|
box-shadow: 0 5px 20px -10px var(--color-shadow);
|
||||||
transition: 100ms scale;
|
transition: 100ms scale;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
---
|
||||||
|
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||||
|
import AsideLayout from "components/AppLayout/AsideLayout.astro";
|
||||||
|
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
|
||||||
|
import Attributes from "components/Attributes.astro";
|
||||||
|
import RichText from "components/RichText/RichText.astro";
|
||||||
|
import { getI18n } from "src/i18n/i18n";
|
||||||
|
import { payload } from "src/shared/payload/payload-sdk";
|
||||||
|
import type { Attribute } from "src/utils/attributes";
|
||||||
|
import { formatLocale } from "src/utils/format";
|
||||||
|
import { fetchOr404 } from "src/utils/responses";
|
||||||
|
|
||||||
|
const id = Astro.params.id!;
|
||||||
|
const recorder = await fetchOr404(() => payload.getRecorderByID(id));
|
||||||
|
if (recorder instanceof Response) {
|
||||||
|
return recorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t, getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
|
||||||
|
|
||||||
|
const { username, languages, avatar, translations } = recorder;
|
||||||
|
|
||||||
|
const { biography } =
|
||||||
|
translations.length > 0 ? getLocalizedMatch(translations) : { biography: undefined };
|
||||||
|
|
||||||
|
const additionalAttributes: Attribute[] = [];
|
||||||
|
|
||||||
|
if (languages.length > 0) {
|
||||||
|
additionalAttributes.push({
|
||||||
|
title: t("collectibles.languages"),
|
||||||
|
icon: "material-symbols:translate",
|
||||||
|
values: languages.map((lang) => ({ name: formatLocale(lang) })),
|
||||||
|
withBorder: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||||
|
|
||||||
|
<AppLayout>
|
||||||
|
<AsideLayout>
|
||||||
|
<Fragment slot="header">
|
||||||
|
<AppLayoutTitle pretitle={`Recorder#${id.substring(0, 5)}`} title={username} />
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
|
{
|
||||||
|
avatar && (
|
||||||
|
<Fragment slot="header-aside">
|
||||||
|
<img src={avatar.url} width={avatar.width} height={avatar.height} />
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
avatar && additionalAttributes.length > 0 && (
|
||||||
|
<Fragment slot="aside">
|
||||||
|
<Attributes attributes={additionalAttributes} />
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<div id="info">
|
||||||
|
{
|
||||||
|
!avatar && additionalAttributes.length > 0 && (
|
||||||
|
<Attributes attributes={additionalAttributes} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{biography && <RichText content={biography} />}
|
||||||
|
</div>
|
||||||
|
</AsideLayout>
|
||||||
|
</AppLayout>
|
||||||
|
|
||||||
|
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
img {
|
||||||
|
max-width: min(35rem, 100%);
|
||||||
|
max-height: 50vh;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
|
||||||
|
@media (max-width: 1280.5px) {
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#info {
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -262,6 +262,27 @@ export interface Recorder {
|
||||||
username: string;
|
username: string;
|
||||||
avatar?: string | Image | null;
|
avatar?: string | Image | null;
|
||||||
languages?: (string | Language)[] | null;
|
languages?: (string | Language)[] | null;
|
||||||
|
translations?:
|
||||||
|
| {
|
||||||
|
language: string | Language;
|
||||||
|
biography: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ("ltr" | "rtl") | null;
|
||||||
|
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
};
|
||||||
|
id?: string | null;
|
||||||
|
}[]
|
||||||
|
| null;
|
||||||
role?: ("Admin" | "Recorder" | "Api")[] | null;
|
role?: ("Admin" | "Recorder" | "Api")[] | null;
|
||||||
anonymize: boolean;
|
anonymize: boolean;
|
||||||
email: string;
|
email: string;
|
||||||
|
@ -1471,6 +1492,10 @@ export type EndpointRecorder = {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
avatar?: EndpointImage;
|
avatar?: EndpointImage;
|
||||||
|
translations: {
|
||||||
|
language: string;
|
||||||
|
biography: RichTextContent;
|
||||||
|
}[];
|
||||||
languages: string[];
|
languages: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1854,8 +1879,6 @@ export const payload = {
|
||||||
await (await request(payloadApiUrl(Collections.Currencies, `all`))).json(),
|
await (await request(payloadApiUrl(Collections.Currencies, `all`))).json(),
|
||||||
getWordings: async (): Promise<EndpointWording[]> =>
|
getWordings: async (): Promise<EndpointWording[]> =>
|
||||||
await (await request(payloadApiUrl(Collections.Wordings, `all`))).json(),
|
await (await request(payloadApiUrl(Collections.Wordings, `all`))).json(),
|
||||||
getRecorders: async (): Promise<EndpointRecorder[]> =>
|
|
||||||
await (await request(payloadApiUrl(Collections.Recorders, `all`))).json(),
|
|
||||||
getPage: async (slug: string): Promise<EndpointPage> =>
|
getPage: async (slug: string): Promise<EndpointPage> =>
|
||||||
await (await request(payloadApiUrl(Collections.Pages, `slug/${slug}`))).json(),
|
await (await request(payloadApiUrl(Collections.Pages, `slug/${slug}`))).json(),
|
||||||
getCollectible: async (slug: string): Promise<EndpointCollectible> =>
|
getCollectible: async (slug: string): Promise<EndpointCollectible> =>
|
||||||
|
@ -1888,4 +1911,6 @@ export const payload = {
|
||||||
await (await request(payloadApiUrl(Collections.Audios, `id/${id}`))).json(),
|
await (await request(payloadApiUrl(Collections.Audios, `id/${id}`))).json(),
|
||||||
getVideoByID: async (id: string): Promise<EndpointVideo> =>
|
getVideoByID: async (id: string): Promise<EndpointVideo> =>
|
||||||
await (await request(payloadApiUrl(Collections.Videos, `id/${id}`))).json(),
|
await (await request(payloadApiUrl(Collections.Videos, `id/${id}`))).json(),
|
||||||
|
getRecorderByID: async (id: string): Promise<EndpointRecorder> =>
|
||||||
|
await (await request(payloadApiUrl(Collections.Recorders, `id/${id}`))).json(),
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue