Handle collectible pages
This commit is contained in:
parent
6c1956ce5c
commit
3c7ce915f1
9
TODO.md
Normal file
9
TODO.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Accord's Library v3.0
|
||||
|
||||
## Short term
|
||||
|
||||
- Translate new wording keys
|
||||
|
||||
## Long term
|
||||
|
||||
- Anonymous comments
|
18
package.json
18
package.json
@ -21,27 +21,27 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.5.6",
|
||||
"@astrojs/node": "^8.2.1",
|
||||
"@fontsource-variable/murecho": "^5.0.17",
|
||||
"@fontsource-variable/vollkorn": "^5.0.19",
|
||||
"@astrojs/node": "^8.2.3",
|
||||
"@fontsource-variable/murecho": "^5.0.18",
|
||||
"@fontsource-variable/vollkorn": "^5.0.20",
|
||||
"accept-language": "^3.0.18",
|
||||
"astro": "4.4.6",
|
||||
"astro": "4.4.15",
|
||||
"astro-icon": "^1.1.0",
|
||||
"node-cache": "^5.1.2",
|
||||
"tippy.js": "^6.3.7",
|
||||
"ua-parser-js": "^1.0.37"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/material-symbols": "^1.1.73",
|
||||
"@iconify-json/material-symbols": "^1.1.74",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
"astro-meta-tags": "^0.2.1",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"bun-types": "^1.0.29",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"bun-types": "^1.0.30",
|
||||
"npm-check-updates": "^16.14.15",
|
||||
"postcss-preset-env": "^9.4.0",
|
||||
"postcss-preset-env": "^9.5.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-astro": "^0.13.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.4.2"
|
||||
}
|
||||
}
|
||||
|
BIN
public/img/background-image.webp
Normal file
BIN
public/img/background-image.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 668 KiB |
Binary file not shown.
Before Width: | Height: | Size: 181 KiB |
Binary file not shown.
Before Width: | Height: | Size: 199 KiB |
@ -10,37 +10,48 @@ interface Props {
|
||||
|
||||
const { src, alt } = Astro.props;
|
||||
const uniqueId = getRandomId();
|
||||
|
||||
const styleNoScript = `
|
||||
<style>
|
||||
#${uniqueId} {
|
||||
opacity: 1;
|
||||
transition: unset;
|
||||
}
|
||||
</style>`;
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<img id={uniqueId} src={src} alt={alt} class="when-no-print" />
|
||||
<noscript set:html={styleNoScript} />
|
||||
<img id={uniqueId} src={src} alt={alt} class="when-no-print when-js" />
|
||||
<img src={src} alt={alt} class="when-no-print when-no-js" />
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
img {
|
||||
opacity: 0;
|
||||
transition: 3s opacity;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: -1;
|
||||
bottom: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
object-fit: cover;
|
||||
object-position: 50% 0;
|
||||
width: 100%;
|
||||
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
|
||||
mask-image: linear-gradient(to bottom, rgba(0 0 0 / 30%) 0%, transparent 100%);
|
||||
|
||||
@media (min-width: 110vh) {
|
||||
mask-image: linear-gradient(
|
||||
to bottom,
|
||||
rgba(0 0 0 / 30%) 0%,
|
||||
rgba(0 0 0 / 5%) 100vh,
|
||||
transparent 100%
|
||||
);
|
||||
height: 100%;
|
||||
max-height: 100vw;
|
||||
}
|
||||
|
||||
user-select: none;
|
||||
|
||||
&.when-js {
|
||||
opacity: 0;
|
||||
transition: 3s opacity;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@ -38,19 +38,12 @@ const { currentTheme } = Astro.locals;
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#27231e" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<style is:global>
|
||||
.when-no-js {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<noscript>
|
||||
<style is:global>
|
||||
.when-js {
|
||||
display: none !important;
|
||||
}
|
||||
.when-no-js {
|
||||
display: initial !important;
|
||||
visibility: none !important;
|
||||
opacity: 0 !important;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
@ -224,10 +217,17 @@ const { currentTheme } = Astro.locals;
|
||||
.high-contrast-text {
|
||||
text-shadow: 0 0 0.6em var(--color-elevation-0);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: clamp(12px, 3vmin, 24px) clamp(24px, 4vw, 64px);
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
position: relative;
|
||||
color: var(--color-base-1000);
|
||||
min-height: 100vb;
|
||||
display: flex;
|
||||
|
||||
@media screen {
|
||||
background-color: var(--color-base-150);
|
||||
@ -235,8 +235,7 @@ const { currentTheme } = Astro.locals;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: clamp(12px, 3vmin, 24px) clamp(24px, 4vw, 64px);
|
||||
min-height: 100vb;
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -327,6 +326,24 @@ const { currentTheme } = Astro.locals;
|
||||
}
|
||||
}
|
||||
|
||||
.pressable-link {
|
||||
text-decoration: underline dotted 0.1em;
|
||||
text-decoration-color: transparent;
|
||||
|
||||
transition-duration: 150ms;
|
||||
transition-property: text-decoration-color, color;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-base-750);
|
||||
text-decoration-color: var(--color-base-650);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--color-base-650);
|
||||
text-decoration-color: var(--color-base-550);
|
||||
}
|
||||
}
|
||||
|
||||
.pressable-label {
|
||||
text-decoration: none;
|
||||
flex-shrink: 0;
|
||||
@ -448,3 +465,13 @@ const { currentTheme } = Astro.locals;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
{/* ------------------------------------------- JS --------------------------------------------- */}
|
||||
|
||||
<script is:inline>
|
||||
Array.from(document.querySelectorAll(".when-no-js")).forEach((node) => node.remove());
|
||||
|
||||
document.addEventListener("astro:before-swap", ({ newDocument }) => {
|
||||
Array.from(newDocument.querySelectorAll(".when-no-js")).forEach((node) => node.remove());
|
||||
});
|
||||
</script>
|
||||
|
@ -44,7 +44,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
<a href="/settings">
|
||||
<Button
|
||||
icon="material-symbols:settings-outline"
|
||||
ariaLabel={t("header.topbar.search.tooltip")}
|
||||
ariaLabel={t("header.topbar.settings.tooltip")}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -17,13 +17,20 @@ switch (parentPage.collection) {
|
||||
href = getLocalizedUrl(`/folders/${parentPage.slug}`);
|
||||
break;
|
||||
|
||||
case Collections.Collectibles:
|
||||
href = getLocalizedUrl(`/collectibles/${parentPage.slug}`);
|
||||
break;
|
||||
|
||||
default:
|
||||
href = "/404";
|
||||
break;
|
||||
}
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
{/* TODO: Not use the tag but actual translation */}
|
||||
|
||||
<a href={href}><span>{parentPage.tag}</span>{translation.name}</a>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
@ -18,6 +18,7 @@ const { t } = await getI18n(Astro.locals.currentLocale);
|
||||
|
||||
<Tooltip trigger="click">
|
||||
<div id="tooltip-content" slot="tooltip-content">
|
||||
{/* TODO: Translate */}
|
||||
<p>This content is part of these pages:</p>
|
||||
{parentPages.map((parentPage) => <ParentPageLink parentPage={parentPage} />)}
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
import type { SpacerBlock } from "src/shared/payload/payload-sdk";
|
||||
import { SpacerSizes, type SpacerBlock } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
block: SpacerBlock;
|
||||
@ -7,11 +7,11 @@ interface Props {
|
||||
|
||||
const { block } = Astro.props;
|
||||
|
||||
const spaceSizeToRem: Record<SpacerBlock["size"], number> = {
|
||||
Small: 1,
|
||||
Medium: 2,
|
||||
Large: 4,
|
||||
XLarge: 8,
|
||||
const spaceSizeToRem: Record<SpacerSizes, number> = {
|
||||
[SpacerSizes.Small]: 1,
|
||||
[SpacerSizes.Medium]: 2,
|
||||
[SpacerSizes.Large]: 4,
|
||||
[SpacerSizes.XLarge]: 8,
|
||||
};
|
||||
---
|
||||
|
||||
|
@ -20,7 +20,7 @@ if (values.length === 0) return;
|
||||
<p>{title}</p>
|
||||
</div>
|
||||
<div id="values">
|
||||
{values.map((value) => <div class="pill">{value}</div>)}
|
||||
{values.map((value) => <div>{value}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -54,12 +54,13 @@ if (values.length === 0) return;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
|
||||
& > .pill {
|
||||
& > div {
|
||||
border: 1px solid var(--color-base-1000);
|
||||
border-radius: 9999px;
|
||||
padding-top: 0.15em;
|
||||
padding-bottom: 0.25em;
|
||||
padding-inline: 0.6em;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
73
src/components/Previews/CollectiblePreview.astro
Normal file
73
src/components/Previews/CollectiblePreview.astro
Normal file
@ -0,0 +1,73 @@
|
||||
---
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import type { EndpointCollectiblePreview } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
collectible: EndpointCollectiblePreview;
|
||||
}
|
||||
|
||||
const { getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
|
||||
const {
|
||||
collectible: { slug, translations, thumbnail },
|
||||
} = Astro.props;
|
||||
|
||||
const { title, pretitle, subtitle } = getLocalizedMatch(translations);
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<a href={getLocalizedUrl(`/collectibles/${slug}`)} class="pressable">
|
||||
{
|
||||
thumbnail && (
|
||||
<img src={thumbnail.url} width={thumbnail.width} height={thumbnail.height} alt="" />
|
||||
)
|
||||
}
|
||||
|
||||
<p>
|
||||
{pretitle && <span id="pretitle">{pretitle} </span>}
|
||||
<span id="title">{title} </span>
|
||||
{subtitle && <span id="subtitle">{subtitle}</span>}
|
||||
</p>
|
||||
</a>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
a {
|
||||
padding: 1em;
|
||||
border-radius: 1em;
|
||||
color: var(--color-base-1000);
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 0.8;
|
||||
display: grid;
|
||||
overflow-wrap: anywhere;
|
||||
font-size: clamp(0.5em, 0.35em + 0.75vw, 1em);
|
||||
font-weight: 800;
|
||||
|
||||
& > #pretitle {
|
||||
font-family: var(--font-sans-serifs);
|
||||
font-weight: 400;
|
||||
margin-bottom: 0.8em;
|
||||
}
|
||||
|
||||
& > #title {
|
||||
font-family: var(--font-serif);
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
& > #subtitle {
|
||||
font-family: var(--font-serif);
|
||||
font-weight: 600;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
73
src/components/Previews/PagePreview.astro
Normal file
73
src/components/Previews/PagePreview.astro
Normal file
@ -0,0 +1,73 @@
|
||||
---
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import type { EndpointPagePreview } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
page: EndpointPagePreview;
|
||||
}
|
||||
|
||||
const { getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
|
||||
const {
|
||||
page: { slug, translations, thumbnail },
|
||||
} = Astro.props;
|
||||
|
||||
const { title, pretitle, subtitle } = getLocalizedMatch(translations);
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<a href={getLocalizedUrl(`/pages/${slug}`)} class="pressable">
|
||||
{
|
||||
thumbnail && (
|
||||
<img src={thumbnail.url} width={thumbnail.width} height={thumbnail.height} alt="" />
|
||||
)
|
||||
}
|
||||
|
||||
<p>
|
||||
{pretitle && <span id="pretitle">{pretitle} </span>}
|
||||
<span id="title">{title} </span>
|
||||
{subtitle && <span id="subtitle">{subtitle}</span>}
|
||||
</p>
|
||||
</a>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
a {
|
||||
padding: 1em;
|
||||
border-radius: 1em;
|
||||
color: var(--color-base-1000);
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 0.8;
|
||||
display: grid;
|
||||
overflow-wrap: anywhere;
|
||||
font-size: clamp(0.5em, 0.35em + 0.75vw, 1em);
|
||||
font-weight: 800;
|
||||
|
||||
& > #pretitle {
|
||||
font-family: var(--font-sans-serifs);
|
||||
font-weight: 400;
|
||||
margin-bottom: 0.8em;
|
||||
}
|
||||
|
||||
& > #title {
|
||||
font-family: var(--font-serif);
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
& > #subtitle {
|
||||
font-family: var(--font-serif);
|
||||
font-weight: 600;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -11,7 +11,7 @@ const { entry } = Astro.props;
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<li data-prefix={entry.prefix}>
|
||||
<a href={`#${entry.prefix}`}>{entry.title}</a>
|
||||
<a href={`#${entry.prefix}`} class="pressable-link">{entry.title}</a>
|
||||
{
|
||||
entry.children.length > 0 && (
|
||||
<ol>
|
||||
@ -28,21 +28,6 @@ const { entry } = Astro.props;
|
||||
<style>
|
||||
a {
|
||||
font-weight: 500;
|
||||
text-decoration: underline dotted 0.1em;
|
||||
text-decoration-color: transparent;
|
||||
|
||||
transition-duration: 150ms;
|
||||
transition-property: text-decoration-color, color;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-base-750);
|
||||
text-decoration-color: var(--color-base-650);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--color-base-650);
|
||||
text-decoration-color: var(--color-base-550);
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
|
@ -10,19 +10,22 @@ const { tagGroups } = Astro.props;
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<div>{tagGroups.map((tag) => <TagGroup {...tag} />)}</div>
|
||||
<div>
|
||||
{tagGroups.map((tag) => <TagGroup {...tag} />)}
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
div {
|
||||
@media (max-width: 35rem) {
|
||||
margin-block: 5em;
|
||||
gap: 2em;
|
||||
}
|
||||
|
||||
margin-block: 2em;
|
||||
display: grid;
|
||||
gap: 1em;
|
||||
gap: 2em;
|
||||
margin-block: 2em;
|
||||
|
||||
@media (max-width: 35rem) {
|
||||
gap: 3.5em;
|
||||
margin-block: 3.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -138,5 +138,48 @@ export const getI18n = async (locale: string) => {
|
||||
return getLocalizedMatch(tag.translations).name;
|
||||
};
|
||||
|
||||
return { t, getLocalizedMatch, getLocalizedUrl, formatTag, formatTagsGroup };
|
||||
const formatPrice = (price: { amount: number; currency: string }): string =>
|
||||
price.amount.toLocaleString(locale, { style: "currency", currency: price.currency });
|
||||
|
||||
const formatDate = (date: Date): string =>
|
||||
date.toLocaleDateString(locale, { dateStyle: "medium" });
|
||||
|
||||
const formatInches = (sizeInMm: number): string => {
|
||||
return (
|
||||
(sizeInMm * 0.039370078740157).toLocaleString(locale, { maximumFractionDigits: 2 }) + " in"
|
||||
);
|
||||
};
|
||||
|
||||
const formatMillimeters = (sizeInMm: number): string => {
|
||||
return sizeInMm.toLocaleString(locale, { maximumFractionDigits: 0 }) + " mm";
|
||||
};
|
||||
|
||||
const formatPounds = (weightInGrams: number): string => {
|
||||
return (
|
||||
(weightInGrams * 0.002204623).toLocaleString(locale, { maximumFractionDigits: 2 }) + " lb"
|
||||
);
|
||||
};
|
||||
|
||||
const formatGrams = (weightInGrams: number): string => {
|
||||
return weightInGrams.toLocaleString(locale, { maximumFractionDigits: 0 }) + " g";
|
||||
};
|
||||
|
||||
const formatNumber = (number: number, options?: Intl.NumberFormatOptions): string => {
|
||||
return number.toLocaleString(locale, options);
|
||||
};
|
||||
|
||||
return {
|
||||
t,
|
||||
getLocalizedMatch,
|
||||
getLocalizedUrl,
|
||||
formatTag,
|
||||
formatTagsGroup,
|
||||
formatPrice,
|
||||
formatDate,
|
||||
formatInches,
|
||||
formatPounds,
|
||||
formatGrams,
|
||||
formatMillimeters,
|
||||
formatNumber,
|
||||
};
|
||||
};
|
||||
|
@ -50,4 +50,28 @@ export type WordingKey =
|
||||
| "footer.license.description"
|
||||
| "footer.license.icons.tooltip"
|
||||
| "footer.disclaimer"
|
||||
| "header.nav.parentPages.label";
|
||||
| "header.nav.parentPages.label"
|
||||
| "collectibles.releaseDate"
|
||||
| "collectibles.size"
|
||||
| "collectibles.size.width"
|
||||
| "collectibles.size.height"
|
||||
| "collectibles.size.thickness"
|
||||
| "collectibles.availability.available"
|
||||
| "collectibles.availability.notAvailable.future"
|
||||
| "collectibles.availability.notAvailable.past"
|
||||
| "collectibles.availability.notAvailable.noPrice"
|
||||
| "collectibles.availability.notAvailable"
|
||||
| "collectibles.price"
|
||||
| "collectibles.price.free"
|
||||
| "collectibles.bookFormat"
|
||||
| "collectibles.bookFormat.pageCount"
|
||||
| "collectibles.bookFormat.binding.paperback"
|
||||
| "collectibles.bookFormat.binding.hardcover"
|
||||
| "collectibles.bookFormat.binding.readingDirection.leftToRight"
|
||||
| "collectibles.bookFormat.binding.readingDirection.rightToLeft"
|
||||
| "collectibles.gallery"
|
||||
| "collectibles.scans"
|
||||
| "collectibles.imageCount"
|
||||
| "header.topbar.settings.tooltip"
|
||||
| "collectibles.contents"
|
||||
| "collectibles.weight";
|
||||
|
@ -76,9 +76,13 @@ const translation = getLocalizedMatch(page.translations);
|
||||
<Credits translators={translation.translators} proofreaders={translation.proofreaders} />
|
||||
</div>
|
||||
|
||||
{
|
||||
translation.toc.length > 0 && (
|
||||
<div class="when-not-large meta-container">
|
||||
<TableOfContent toc={translation.toc} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<hr />
|
||||
<div id="text">
|
||||
@ -110,10 +114,14 @@ const translation = getLocalizedMatch(page.translations);
|
||||
/>
|
||||
)
|
||||
}
|
||||
<Credits translators={translation.translators} proofreaders={translation.proofreaders} />
|
||||
<Credits
|
||||
translators={translation.translators}
|
||||
transcribers={translation.transcribers}
|
||||
proofreaders={translation.proofreaders}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TableOfContent toc={translation.toc} />
|
||||
{translation.toc.length > 0 && <TableOfContent toc={translation.toc} />}
|
||||
</div>
|
||||
</div>
|
||||
</MasoTarget>
|
||||
|
269
src/pages/[locale]/collectibles/[slug].astro
Normal file
269
src/pages/[locale]/collectibles/[slug].astro
Normal file
@ -0,0 +1,269 @@
|
||||
---
|
||||
import AppEmptyLayout from "components/AppLayout/AppEmptyLayout.astro";
|
||||
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
|
||||
import RichText from "components/RichText/RichText.astro";
|
||||
import TagGroups from "components/TagGroups.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import { payload } from "src/shared/payload/payload-sdk";
|
||||
import { fetchOr404 } from "src/utils/responses";
|
||||
import ImageTile from "./_components/ImageTile.astro";
|
||||
import PriceInfo from "./_components/PriceInfo.astro";
|
||||
import SizeInfo from "./_components/SizeInfo.astro";
|
||||
import ReleaseDateInfo from "./_components/ReleaseDateInfo.astro";
|
||||
import PageInfo from "./_components/PageInfo.astro";
|
||||
import AvailabilityInfo from "./_components/AvailabilityInfo.astro";
|
||||
import WeightInfo from "./_components/WeightInfo.astro";
|
||||
import SubitemSection from "./_components/SubitemSection.astro";
|
||||
import ContentsSection from "./_components/ContentsSection/ContentsSection.astro";
|
||||
|
||||
const { slug } = Astro.params;
|
||||
const { getLocalizedMatch, t } = await getI18n(Astro.locals.currentLocale);
|
||||
|
||||
const collectible = await fetchOr404(() => payload.getCollectible(slug!));
|
||||
if (collectible instanceof Response) {
|
||||
return collectible;
|
||||
}
|
||||
|
||||
const {
|
||||
translations,
|
||||
thumbnail,
|
||||
size,
|
||||
price,
|
||||
releaseDate,
|
||||
pageInfo,
|
||||
urls,
|
||||
weight,
|
||||
backgroundImage,
|
||||
gallery,
|
||||
scans,
|
||||
subitems,
|
||||
parentPages,
|
||||
tagGroups,
|
||||
contents,
|
||||
} = collectible;
|
||||
|
||||
const translation = getLocalizedMatch(translations);
|
||||
|
||||
const galleryFirstImage = gallery[0];
|
||||
const scansFirstImage = scans[0];
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<AppEmptyLayout
|
||||
parentPages={parentPages}
|
||||
backgroundIllustration={backgroundImage?.url ?? thumbnail?.url}>
|
||||
<div id="layout">
|
||||
<div id="left">
|
||||
<AppLayoutTitle
|
||||
title={translation.title}
|
||||
pretitle={translation.pretitle}
|
||||
subtitle={translation.subtitle}
|
||||
/>
|
||||
|
||||
<div id="images" class="when-not-large">
|
||||
{
|
||||
thumbnail && (
|
||||
<img
|
||||
id="thumbnail"
|
||||
src={thumbnail.url}
|
||||
width={thumbnail.width}
|
||||
height={thumbnail.height}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
<div id="gallery-scans" class="when-no-print">
|
||||
{
|
||||
galleryFirstImage && (
|
||||
<ImageTile
|
||||
image={galleryFirstImage.url}
|
||||
title="Gallery"
|
||||
subtitle={`${gallery.length} images`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
scansFirstImage && (
|
||||
<ImageTile
|
||||
image={scansFirstImage.url}
|
||||
title="Scans"
|
||||
subtitle={`${scans.length} images`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
translation.description && (
|
||||
<div id="summary" class="high-contrast-text">
|
||||
<RichText content={translation.description} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<TagGroups tagGroups={tagGroups}>
|
||||
{releaseDate && <ReleaseDateInfo releaseDate={releaseDate} />}
|
||||
|
||||
{price && <PriceInfo price={price} />}
|
||||
|
||||
<AvailabilityInfo urls={urls} price={price !== undefined} releaseDate={releaseDate} />
|
||||
|
||||
{size && <SizeInfo size={size} />}
|
||||
|
||||
{weight && <WeightInfo weight={weight} />}
|
||||
|
||||
{pageInfo && <PageInfo pageInfo={pageInfo} />}
|
||||
</TagGroups>
|
||||
|
||||
{subitems.length > 0 && <SubitemSection subitems={subitems} />}
|
||||
|
||||
{contents.length > 0 && <ContentsSection contents={contents} />}
|
||||
</div>
|
||||
|
||||
<div id="right" class="when-large">
|
||||
<div id="images">
|
||||
<div id="gallery-scans" class="when-no-print">
|
||||
{
|
||||
galleryFirstImage && (
|
||||
<ImageTile
|
||||
image={galleryFirstImage.url}
|
||||
title={t("collectibles.gallery")}
|
||||
subtitle={t("collectibles.imageCount", { count: gallery.length })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
scansFirstImage && (
|
||||
<ImageTile
|
||||
image={scansFirstImage.url}
|
||||
title={t("collectibles.scans")}
|
||||
subtitle={t("collectibles.imageCount", { count: scans.length })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
thumbnail && (
|
||||
<img
|
||||
id="thumbnail"
|
||||
src={thumbnail.url}
|
||||
width={thumbnail.width}
|
||||
height={thumbnail.height}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppEmptyLayout>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
#layout {
|
||||
display: grid;
|
||||
justify-content: space-between;
|
||||
container-type: inline-size;
|
||||
|
||||
@media (min-width: 80rem) {
|
||||
grid-template-columns: 35rem 35rem;
|
||||
}
|
||||
|
||||
& > #left {
|
||||
& > #images {
|
||||
display: grid;
|
||||
place-content: start;
|
||||
place-items: start;
|
||||
margin-block: 2em;
|
||||
gap: clamp(1em, 0.5em + 3vw, 2em);
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@media (max-width: 23rem) {
|
||||
gap: 2.5em;
|
||||
}
|
||||
|
||||
@media (min-width: 52rem) {
|
||||
grid-template-columns: 35rem 10rem;
|
||||
}
|
||||
|
||||
& > #thumbnail {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
box-shadow: 0 5px 20px -10px var(--color-shadow);
|
||||
max-width: 35rem;
|
||||
}
|
||||
|
||||
& > #gallery-scans {
|
||||
display: flex;
|
||||
max-width: 35rem;
|
||||
flex-direction: column;
|
||||
gap: 2.5em;
|
||||
width: 100%;
|
||||
|
||||
> :global(div) {
|
||||
aspect-ratio: 2 / 1;
|
||||
}
|
||||
|
||||
@media (min-width: 23rem) {
|
||||
gap: clamp(1em, 0.5em + 3vw, 2em);
|
||||
flex-direction: row;
|
||||
|
||||
> :global(div) {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
@media (min-width: 52rem) {
|
||||
max-width: 15rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > #summary {
|
||||
backdrop-filter: blur(5px);
|
||||
padding: 1.5em;
|
||||
margin: -1.5em;
|
||||
margin-block: 1em;
|
||||
border-radius: 3em;
|
||||
}
|
||||
}
|
||||
|
||||
& > #right {
|
||||
& > #images {
|
||||
display: grid;
|
||||
grid-template-columns: 10rem 1fr;
|
||||
gap: 1em;
|
||||
|
||||
& > #gallery-scans {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
& > #thumbnail {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
box-shadow: 0 5px 20px -10px var(--color-shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.when-large {
|
||||
@media (max-width: 80rem) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.when-not-large {
|
||||
@media (min-width: 80rem) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,106 @@
|
||||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import type { EndpointCollectible } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
urls: EndpointCollectible["urls"];
|
||||
releaseDate?: string | undefined;
|
||||
price?: boolean;
|
||||
}
|
||||
|
||||
const { price = false, urls, releaseDate } = Astro.props;
|
||||
const { t } = await getI18n(Astro.locals.currentLocale);
|
||||
|
||||
const title = (() => {
|
||||
if (urls.length > 0) return t("collectibles.availability.available");
|
||||
|
||||
if (price) {
|
||||
if (!releaseDate) return t("collectibles.availability.notAvailable");
|
||||
const release = new Date(releaseDate);
|
||||
if (release > new Date()) {
|
||||
return t("collectibles.availability.notAvailable.future");
|
||||
} else {
|
||||
return t("collectibles.availability.notAvailable.past");
|
||||
}
|
||||
}
|
||||
|
||||
return t("collectibles.availability.notAvailable.noPrice");
|
||||
})();
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<div id="container">
|
||||
<div id="title">
|
||||
<Icon name="material-symbols:shopping-cart-outline" width={24} height={24} />
|
||||
<p>{title}</p>
|
||||
</div>
|
||||
|
||||
{
|
||||
urls.length > 0 && (
|
||||
<div id="values">
|
||||
{urls.map(({ label, url }) => (
|
||||
<a target="_blank" rel="noopener noreferrer" href={url}>
|
||||
{label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
#container {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.5em 1em;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 35em) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
& > #title {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 8px;
|
||||
|
||||
& > p {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
& > #values {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 0.35em;
|
||||
|
||||
& > a {
|
||||
border: 1px solid var(--color-base-1000);
|
||||
border-radius: 9999px;
|
||||
padding-top: 0.15em;
|
||||
padding-bottom: 0.25em;
|
||||
padding-inline: 0.6em;
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
transition-duration: 150ms;
|
||||
transition-property: border-color, color;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-base-750);
|
||||
border-color: var(--color-base-750);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: var(--color-base-650);
|
||||
border-color: var(--color-base-650);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,93 @@
|
||||
---
|
||||
import ErrorMessage from "components/ErrorMessage.astro";
|
||||
import RichText from "components/RichText/RichText.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import type { EndpointCollectible } from "src/shared/payload/payload-sdk";
|
||||
import { formatInlineTitle } from "src/utils/format";
|
||||
|
||||
interface Props {
|
||||
content: EndpointCollectible["contents"][number];
|
||||
}
|
||||
|
||||
const { getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
const {
|
||||
content: { content, range },
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<div id="row">
|
||||
<div id="title">
|
||||
{
|
||||
content.relationTo === "generic-contents" ? (
|
||||
<p>{getLocalizedMatch(content.value.translations).name}</p>
|
||||
) : content.relationTo === "pages" ? (
|
||||
<a href={getLocalizedUrl(`/pages/${content.value.slug}`)} class="pressable-link">
|
||||
{formatInlineTitle(getLocalizedMatch(content.value.translations))}
|
||||
</a>
|
||||
) : (
|
||||
<ErrorMessage
|
||||
title="Unknown content type"
|
||||
description="Please contact website technical administrator."
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<div id="dots"></div>
|
||||
|
||||
<div id="range">
|
||||
{
|
||||
range && (
|
||||
<>
|
||||
{range.type === "pageRange" ? (
|
||||
range.start
|
||||
) : range.type === "timeRange" ? (
|
||||
range.start
|
||||
) : range.type === "other" ? (
|
||||
<RichText content={getLocalizedMatch(range.translations).note} />
|
||||
) : (
|
||||
<ErrorMessage
|
||||
title="Unknown range type"
|
||||
description="Please contact website technical administrator."
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
#row {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
gap: 1em;
|
||||
align-items: center;
|
||||
padding: 1em;
|
||||
border-radius: 1em;
|
||||
box-shadow: 0 1px 2px 0 var(--color-shadow-2);
|
||||
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: color-mix(in srgb, var(--color-elevation-2) 50%, transparent);
|
||||
|
||||
border-top: 1px solid var(--color-elevation-2);
|
||||
|
||||
& > #title {
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
|
||||
& > #dots {
|
||||
width: 100%;
|
||||
min-width: clamp(1em, 0.5em + 5vw, 5em);
|
||||
border-bottom: 0.15em dotted var(--color-base-500);
|
||||
height: 0.6em;
|
||||
}
|
||||
|
||||
# > #range {
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,47 @@
|
||||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import type { EndpointCollectible } from "src/shared/payload/payload-sdk";
|
||||
import ContentRow from "./ContentRow.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
|
||||
interface Props {
|
||||
contents: EndpointCollectible["contents"];
|
||||
}
|
||||
|
||||
const { contents } = Astro.props;
|
||||
const { t } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<div id="title">
|
||||
<Icon name="material-symbols:list-alt-outline" width={24} height={24} />
|
||||
<p>{t("collectibles.contents")}</p>
|
||||
</div>
|
||||
|
||||
<div id="contents">
|
||||
{contents.map((content) => <ContentRow content={content} />)}
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
#title {
|
||||
margin-top: 6em;
|
||||
margin-bottom: 2em;
|
||||
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 8px;
|
||||
|
||||
& > p {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
#contents {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
58
src/pages/[locale]/collectibles/_components/ImageTile.astro
Normal file
58
src/pages/[locale]/collectibles/_components/ImageTile.astro
Normal file
@ -0,0 +1,58 @@
|
||||
---
|
||||
interface Props {
|
||||
image: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
}
|
||||
|
||||
const { image, title, subtitle } = Astro.props;
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<div id="tile">
|
||||
<img src={image} />
|
||||
|
||||
<div class="high-contrast-text">
|
||||
<p class="title">{title}</p>
|
||||
<p>{subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
#tile {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
display: grid;
|
||||
background-color: var(--color-elevation-0);
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 5px 20px -10px var(--color-shadow);
|
||||
|
||||
& > div {
|
||||
text-align: center;
|
||||
backdrop-filter: blur(5px);
|
||||
padding: 1em;
|
||||
border-radius: 1em;
|
||||
|
||||
& > .title {
|
||||
font-size: 130%;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
& > img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0.5;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
</style>
|
87
src/pages/[locale]/collectibles/_components/PageInfo.astro
Normal file
87
src/pages/[locale]/collectibles/_components/PageInfo.astro
Normal file
@ -0,0 +1,87 @@
|
||||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import {
|
||||
CollectibleBindingTypes,
|
||||
CollectiblePageOrders,
|
||||
type EndpointCollectible,
|
||||
} from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
pageInfo: NonNullable<EndpointCollectible["pageInfo"]>;
|
||||
}
|
||||
|
||||
const {
|
||||
pageInfo: { pageCount, bindingType, pageOrder },
|
||||
} = Astro.props;
|
||||
|
||||
const { t } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<div id="container">
|
||||
<div id="title">
|
||||
<Icon name="material-symbols:note-stack-outline" width={24} height={24} />
|
||||
<p>{t("collectibles.bookFormat")}</p>
|
||||
</div>
|
||||
|
||||
<div id="values">
|
||||
<p>{t("collectibles.bookFormat.pageCount", { count: pageCount })}</p>
|
||||
{
|
||||
bindingType && (
|
||||
<p>
|
||||
{t(
|
||||
bindingType === CollectibleBindingTypes.Hardcover
|
||||
? "collectibles.bookFormat.binding.hardcover"
|
||||
: "collectibles.bookFormat.binding.paperback"
|
||||
)}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
{
|
||||
pageOrder && (
|
||||
<p>
|
||||
{t(
|
||||
pageOrder === CollectiblePageOrders.LeftToRight
|
||||
? "collectibles.bookFormat.binding.readingDirection.leftToRight"
|
||||
: "collectibles.bookFormat.binding.readingDirection.rightToLeft"
|
||||
)}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
#container {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.5em 1em;
|
||||
align-items: start;
|
||||
|
||||
@media (max-width: 35em) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
& > #title {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 8px;
|
||||
|
||||
& > p {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
& > #values {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
70
src/pages/[locale]/collectibles/_components/PriceInfo.astro
Normal file
70
src/pages/[locale]/collectibles/_components/PriceInfo.astro
Normal file
@ -0,0 +1,70 @@
|
||||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import { convert } from "src/utils/currencies";
|
||||
|
||||
interface Props {
|
||||
price: {
|
||||
amount: number;
|
||||
currency: string;
|
||||
};
|
||||
}
|
||||
|
||||
const { price } = Astro.props;
|
||||
|
||||
const { formatPrice, t } = await getI18n(Astro.locals.currentLocale);
|
||||
|
||||
const preferredCurrency = Astro.locals.currentCurrency;
|
||||
|
||||
const convertedPrice: Props["price"] = {
|
||||
amount: convert(price.currency, preferredCurrency, price.amount),
|
||||
currency: preferredCurrency,
|
||||
};
|
||||
|
||||
let priceText = price.amount === 0 ? t("collectibles.price.free") : formatPrice(price);
|
||||
|
||||
if (price.amount > 0 && price.currency !== convertedPrice.currency) {
|
||||
priceText += ` (${formatPrice(convertedPrice)})`;
|
||||
}
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<div id="container">
|
||||
<div id="title">
|
||||
<Icon name="material-symbols:sell-outline" width={24} height={24} />
|
||||
<p>{t("collectibles.price")}</p>
|
||||
</div>
|
||||
|
||||
{(<p>{priceText}</p>)}
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
#container {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.5em 1em;
|
||||
align-items: start;
|
||||
|
||||
@media (max-width: 35em) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
& > #title {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 8px;
|
||||
|
||||
& > p {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
& > p {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,53 @@
|
||||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
|
||||
interface Props {
|
||||
releaseDate: string;
|
||||
}
|
||||
|
||||
const { releaseDate } = Astro.props;
|
||||
|
||||
const { formatDate, t } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<div id="container">
|
||||
<div id="title">
|
||||
<Icon name="material-symbols:calendar-month-outline" width={24} height={24} />
|
||||
<p>{t("collectibles.releaseDate")}</p>
|
||||
</div>
|
||||
|
||||
<p>{formatDate(new Date(releaseDate))}</p>
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
#container {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.5em 1em;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 35em) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
& > #title {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 8px;
|
||||
|
||||
& > p {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
& > p {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
91
src/pages/[locale]/collectibles/_components/SizeInfo.astro
Normal file
91
src/pages/[locale]/collectibles/_components/SizeInfo.astro
Normal file
@ -0,0 +1,91 @@
|
||||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
|
||||
interface Props {
|
||||
size: {
|
||||
width: number;
|
||||
height: number;
|
||||
thickness?: number;
|
||||
};
|
||||
}
|
||||
|
||||
const { size } = Astro.props;
|
||||
|
||||
const { formatInches, formatMillimeters, t } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<div id="size">
|
||||
<div id="title">
|
||||
<Icon name="material-symbols:measuring-tape-outline" width={24} height={24} />
|
||||
<p>{t("collectibles.size")}</p>
|
||||
</div>
|
||||
|
||||
<div id="values">
|
||||
<div>
|
||||
<p>{t("collectibles.size.width")}</p>
|
||||
<p>{formatMillimeters(size.width)}</p>
|
||||
<p>{formatInches(size.width)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>{t("collectibles.size.height")}</p>
|
||||
<p>{formatMillimeters(size.height)}</p>
|
||||
<p>{formatInches(size.height)}</p>
|
||||
</div>
|
||||
{
|
||||
size.thickness && (
|
||||
<div>
|
||||
<p>{t("collectibles.size.thickness")}</p>
|
||||
<p>{formatMillimeters(size.thickness)}</p>
|
||||
<p>{formatInches(size.thickness)}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
#size {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 1em 2em;
|
||||
align-items: start;
|
||||
|
||||
@media (max-width: 35em) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
& > #title {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 8px;
|
||||
|
||||
& > p {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
translate: 0px -0.1em;
|
||||
}
|
||||
}
|
||||
|
||||
& > #values {
|
||||
display: flex;
|
||||
gap: 1em 1.5em;
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6em;
|
||||
|
||||
& > p:first-child {
|
||||
font-size: 120%;
|
||||
font-weight: 500;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,48 @@
|
||||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import CollectiblePreview from "components/Previews/CollectiblePreview.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import type { EndpointCollectible } from "src/shared/payload/payload-sdk";
|
||||
|
||||
interface Props {
|
||||
subitems: EndpointCollectible["subitems"];
|
||||
}
|
||||
|
||||
const { subitems } = Astro.props;
|
||||
const { t } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<div id="title">
|
||||
<Icon name="material-symbols:box-outline" width={24} height={24} />
|
||||
<p>{t("collectibles.contents")}</p>
|
||||
</div>
|
||||
|
||||
<div id="values">
|
||||
{subitems.map((subitem) => <CollectiblePreview collectible={subitem} />)}
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
#title {
|
||||
margin-top: 6em;
|
||||
margin-bottom: 2em;
|
||||
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 8px;
|
||||
|
||||
& > p {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
#values {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: clamp(6px, 2vmin, 16px);
|
||||
}
|
||||
</style>
|
52
src/pages/[locale]/collectibles/_components/WeightInfo.astro
Normal file
52
src/pages/[locale]/collectibles/_components/WeightInfo.astro
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
|
||||
interface Props {
|
||||
weight: number;
|
||||
}
|
||||
|
||||
const { weight } = Astro.props;
|
||||
const { formatPounds, formatGrams, t } = await getI18n(Astro.locals.currentLocale);
|
||||
---
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<div id="container">
|
||||
<div id="title">
|
||||
<Icon name="material-symbols:scale-outline" width={24} height={24} />
|
||||
<p>{t("collectibles.weight")}</p>
|
||||
</div>
|
||||
|
||||
<p>{formatGrams(weight)}{" "}({formatPounds(weight)})</p>
|
||||
</div>
|
||||
|
||||
{/* ------------------------------------------- CSS -------------------------------------------- */}
|
||||
|
||||
<style>
|
||||
#container {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.5em 1em;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: 35em) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
& > #title {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
gap: 8px;
|
||||
|
||||
& > p {
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
& > p {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -6,9 +6,11 @@ import FoldersSection from "./_components/FoldersSection.astro";
|
||||
import { fetchOr404 } from "src/utils/responses";
|
||||
import ErrorMessage from "components/ErrorMessage.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import CollectiblePreview from "components/Previews/CollectiblePreview.astro";
|
||||
import PagePreview from "components/Previews/PagePreview.astro";
|
||||
|
||||
const { slug } = Astro.params;
|
||||
const { getLocalizedMatch, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
const { getLocalizedMatch } = await getI18n(Astro.locals.currentLocale);
|
||||
|
||||
const folder = await fetchOr404(() => payload.getFolder(slug!));
|
||||
if (folder instanceof Response) {
|
||||
@ -50,19 +52,15 @@ const meta = getLocalizedMatch(folder.translations);
|
||||
)
|
||||
}
|
||||
|
||||
<div>
|
||||
<div id="files">
|
||||
{
|
||||
folder.files.map(({ relationTo, value }) => {
|
||||
switch (relationTo) {
|
||||
case "library-items":
|
||||
return <p>Library item not supported yet! {value.slug}</p>;
|
||||
case "collectibles":
|
||||
return <CollectiblePreview collectible={value} />;
|
||||
|
||||
case "pages":
|
||||
return (
|
||||
<a class="pressable" href={getLocalizedUrl(`/pages/${value.slug}`)}>
|
||||
{value.slug}
|
||||
</a>
|
||||
);
|
||||
return <PagePreview page={value} />;
|
||||
|
||||
default:
|
||||
return (
|
||||
@ -85,9 +83,16 @@ const meta = getLocalizedMatch(folder.translations);
|
||||
display: grid;
|
||||
gap: 4em;
|
||||
|
||||
#sections {
|
||||
& > #sections {
|
||||
display: grid;
|
||||
gap: 2.5em;
|
||||
}
|
||||
|
||||
& > #files {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: clamp(6px, 2vmin, 16px);
|
||||
place-items: start;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -14,7 +14,7 @@ const { t, getLocalizedUrl } = await getI18n(Astro.locals.currentLocale);
|
||||
|
||||
<AppLayout
|
||||
title="Accord’s Library"
|
||||
backgroundIllustration="/img/bg-home2.webp"
|
||||
backgroundIllustration="/img/background-image.webp"
|
||||
hideFooterLinks
|
||||
hideHomeButton>
|
||||
<div id="title" slot="header-title">
|
||||
|
@ -14,6 +14,8 @@ if (page instanceof Response) {
|
||||
|
||||
{/* ------------------------------------------- HTML ------------------------------------------- */}
|
||||
|
||||
<AppEmptyLayout parentPages={page.parentPages} backgroundIllustration={page.thumbnail?.url}>
|
||||
<AppEmptyLayout
|
||||
parentPages={page.parentPages}
|
||||
backgroundIllustration={page.backgroundImage?.url ?? page.thumbnail?.url}>
|
||||
<Page slug={page.slug} lang={Astro.locals.currentLocale} page={page} />
|
||||
</AppEmptyLayout>
|
||||
|
@ -2,6 +2,8 @@
|
||||
import AppLayout from "components/AppLayout/AppLayout.astro";
|
||||
import { getI18n } from "src/i18n/i18n";
|
||||
import { cache } from "src/utils/cachedPayload";
|
||||
import { formatCurrency } from "src/utils/currencies";
|
||||
import { formatLocale } from "src/utils/format";
|
||||
|
||||
const { currentLocale, currentTheme, currentCurrency } = Astro.locals;
|
||||
const { t } = await getI18n(currentLocale);
|
||||
@ -20,7 +22,7 @@ const { t } = await getI18n(currentLocale);
|
||||
class:list={{ current: currentLocale === id }}
|
||||
href={`?action-lang=${id}`}
|
||||
data-astro-prefetch="tap">
|
||||
{id}
|
||||
{formatLocale(id)}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
@ -50,15 +52,15 @@ const { t } = await getI18n(currentLocale);
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>{t("settings.theme.title")}</h2>
|
||||
<p>{t("settings.theme.description")}</p><br />
|
||||
<h2>{t("settings.currency.title")}</h2>
|
||||
<p>{t("settings.currency.description")}</p><br />
|
||||
{
|
||||
cache.currencies.map((id) => (
|
||||
<a
|
||||
class:list={{ current: currentCurrency === id }}
|
||||
href={`?action-currency=${id}`}
|
||||
data-astro-prefetch="tap">
|
||||
{id}
|
||||
{`${id} (${formatCurrency(id)})`}
|
||||
</a>
|
||||
))
|
||||
}
|
||||
@ -72,6 +74,7 @@ const { t } = await getI18n(currentLocale);
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
gap: 0.5em;
|
||||
|
||||
& > .current {
|
||||
|
@ -35,41 +35,35 @@ export type RecorderBiographies =
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "CategoryTranslations".
|
||||
*/
|
||||
export type CategoryTranslations =
|
||||
| {
|
||||
export type CategoryTranslations = {
|
||||
language: string | Language;
|
||||
name: string;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
}[];
|
||||
|
||||
export interface Config {
|
||||
collections: {
|
||||
folders: Folder;
|
||||
'folders-thumbnails': FoldersThumbnail;
|
||||
'library-items': LibraryItem;
|
||||
pages: Page;
|
||||
'chronology-items': ChronologyItem;
|
||||
'chronology-eras': ChronologyEra;
|
||||
weapons: Weapon;
|
||||
'weapons-groups': WeaponsGroup;
|
||||
'weapons-thumbnails': WeaponsThumbnail;
|
||||
'library-items-thumbnails': LibraryItemThumbnail;
|
||||
'library-items-scans': LibraryItemScans;
|
||||
'library-items-gallery': LibraryItemGallery;
|
||||
'recorders-thumbnails': RecordersThumbnail;
|
||||
files: File;
|
||||
notes: Note;
|
||||
videos: Video;
|
||||
'videos-channels': VideosChannel;
|
||||
languages: Language;
|
||||
currencies: Currency;
|
||||
recorders: Recorder;
|
||||
keys: Key;
|
||||
tags: Tag;
|
||||
'tags-groups': TagsGroup;
|
||||
images: Image;
|
||||
wordings: Wording;
|
||||
collectibles: Collectible;
|
||||
'generic-contents': GenericContent;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
@ -123,8 +117,8 @@ export interface Folder {
|
||||
files?:
|
||||
| (
|
||||
| {
|
||||
relationTo: 'library-items';
|
||||
value: string | LibraryItem;
|
||||
relationTo: 'collectibles';
|
||||
value: string | Collectible;
|
||||
}
|
||||
| {
|
||||
relationTo: 'pages';
|
||||
@ -170,152 +164,21 @@ export interface Language {
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "library-items".
|
||||
* via the `definition` "collectibles".
|
||||
*/
|
||||
export interface LibraryItem {
|
||||
export interface Collectible {
|
||||
id: string;
|
||||
itemType?: ('Textual' | 'Audio' | 'Video' | 'Game' | 'Other') | null;
|
||||
language: string | Language;
|
||||
slug: string;
|
||||
thumbnail?: string | LibraryItemThumbnail | null;
|
||||
thumbnail?: string | Image | null;
|
||||
nature: 'Physical' | 'Digital';
|
||||
languages?: (string | Language)[] | null;
|
||||
tags?: (string | Tag)[] | null;
|
||||
translations: {
|
||||
language: string | Language;
|
||||
pretitle?: string | null;
|
||||
title: string;
|
||||
subtitle?: string | null;
|
||||
digital: boolean;
|
||||
gallery?:
|
||||
| {
|
||||
image?: string | LibraryItemGallery | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
scansEnabled?: boolean | null;
|
||||
scans?: {
|
||||
scanners: (string | Recorder)[];
|
||||
cleaners: (string | Recorder)[];
|
||||
typesetters?: (string | Recorder)[] | null;
|
||||
coverEnabled?: boolean | null;
|
||||
cover?: {
|
||||
front?: string | LibraryItemScans | null;
|
||||
spine?: string | LibraryItemScans | null;
|
||||
back?: string | LibraryItemScans | null;
|
||||
insideFront?: string | LibraryItemScans | null;
|
||||
insideBack?: string | LibraryItemScans | null;
|
||||
flapFront?: string | LibraryItemScans | null;
|
||||
flapBack?: string | LibraryItemScans | null;
|
||||
insideFlapFront?: string | LibraryItemScans | null;
|
||||
insideFlapBack?: string | LibraryItemScans | null;
|
||||
};
|
||||
dustjacketEnabled?: boolean | null;
|
||||
dustjacket?: {
|
||||
front?: string | LibraryItemScans | null;
|
||||
spine?: string | LibraryItemScans | null;
|
||||
back?: string | LibraryItemScans | null;
|
||||
insideFront?: string | LibraryItemScans | null;
|
||||
insideSpine?: string | LibraryItemScans | null;
|
||||
insideBack?: string | LibraryItemScans | null;
|
||||
flapFront?: string | LibraryItemScans | null;
|
||||
flapBack?: string | LibraryItemScans | null;
|
||||
insideFlapFront?: string | LibraryItemScans | null;
|
||||
insideFlapBack?: string | LibraryItemScans | null;
|
||||
};
|
||||
obiEnabled?: boolean | null;
|
||||
obi?: {
|
||||
front?: string | LibraryItemScans | null;
|
||||
spine?: string | LibraryItemScans | null;
|
||||
back?: string | LibraryItemScans | null;
|
||||
insideFront?: string | LibraryItemScans | null;
|
||||
insideSpine?: string | LibraryItemScans | null;
|
||||
insideBack?: string | LibraryItemScans | null;
|
||||
flapFront?: string | LibraryItemScans | null;
|
||||
flapBack?: string | LibraryItemScans | null;
|
||||
insideFlapFront?: string | LibraryItemScans | null;
|
||||
insideFlapBack?: string | LibraryItemScans | null;
|
||||
};
|
||||
pages?:
|
||||
| {
|
||||
page: number;
|
||||
image: string | LibraryItemScans;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
archiveFile?: (string | null) | File;
|
||||
};
|
||||
textual?: {
|
||||
subtype?: (string | null) | Key;
|
||||
pageCount?: number | null;
|
||||
bindingType?: ('Paperback' | 'Hardcover') | null;
|
||||
pageOrder?: ('LeftToRight' | 'RightToLeft') | null;
|
||||
};
|
||||
audio?: {
|
||||
audioSubtype?: (string | null) | Key;
|
||||
tracks?:
|
||||
| {
|
||||
title: string;
|
||||
file: string | File;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
};
|
||||
video?: {
|
||||
subtype?: (string | null) | Key;
|
||||
};
|
||||
game?: {
|
||||
demo?: boolean | null;
|
||||
platform?: (string | null) | Key;
|
||||
audioLanguages?: (string | Language)[] | null;
|
||||
subtitleLanguages?: (string | Language)[] | null;
|
||||
interfacesLanguages?: (string | Language)[] | null;
|
||||
};
|
||||
releaseDate?: string | null;
|
||||
categories?: (string | Key)[] | null;
|
||||
sizeEnabled?: boolean | null;
|
||||
size?: {
|
||||
width: number;
|
||||
height: number;
|
||||
thickness?: number | null;
|
||||
};
|
||||
priceEnabled?: boolean | null;
|
||||
price?: {
|
||||
amount: number;
|
||||
currency: string | Currency;
|
||||
};
|
||||
translations?:
|
||||
| {
|
||||
language: string | Language;
|
||||
description: {
|
||||
root: {
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
urls?:
|
||||
| {
|
||||
url: string;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
parentItems?: (string | LibraryItem)[] | null;
|
||||
subitems?: (string | LibraryItem)[] | null;
|
||||
contents?:
|
||||
| {
|
||||
content: string | Page;
|
||||
pageStart?: number | null;
|
||||
pageEnd?: number | null;
|
||||
timeStart?: number | null;
|
||||
timeEnd?: number | null;
|
||||
note?: {
|
||||
description?: {
|
||||
root: {
|
||||
children: {
|
||||
type: string;
|
||||
@ -331,6 +194,152 @@ export interface LibraryItem {
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
id?: string | null;
|
||||
}[];
|
||||
backgroundImage?: string | Image | null;
|
||||
gallery?:
|
||||
| {
|
||||
image: string | Image;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
scansEnabled?: boolean | null;
|
||||
scans?: {
|
||||
scanners: (string | Recorder)[];
|
||||
cleaners: (string | Recorder)[];
|
||||
typesetters?: (string | Recorder)[] | null;
|
||||
coverEnabled?: boolean | null;
|
||||
cover?: {
|
||||
front?: string | Image | null;
|
||||
spine?: string | Image | null;
|
||||
back?: string | Image | null;
|
||||
insideFront?: string | Image | null;
|
||||
insideBack?: string | Image | null;
|
||||
flapFront?: string | Image | null;
|
||||
flapBack?: string | Image | null;
|
||||
insideFlapFront?: string | Image | null;
|
||||
insideFlapBack?: string | Image | null;
|
||||
};
|
||||
dustjacketEnabled?: boolean | null;
|
||||
dustjacket?: {
|
||||
front?: string | Image | null;
|
||||
spine?: string | Image | null;
|
||||
back?: string | Image | null;
|
||||
insideFront?: string | Image | null;
|
||||
insideSpine?: string | Image | null;
|
||||
insideBack?: string | Image | null;
|
||||
flapFront?: string | Image | null;
|
||||
flapBack?: string | Image | null;
|
||||
insideFlapFront?: string | Image | null;
|
||||
insideFlapBack?: string | Image | null;
|
||||
};
|
||||
obiEnabled?: boolean | null;
|
||||
obi?: {
|
||||
front?: string | Image | null;
|
||||
spine?: string | Image | null;
|
||||
back?: string | Image | null;
|
||||
insideFront?: string | Image | null;
|
||||
insideSpine?: string | Image | null;
|
||||
insideBack?: string | Image | null;
|
||||
flapFront?: string | Image | null;
|
||||
flapBack?: string | Image | null;
|
||||
insideFlapFront?: string | Image | null;
|
||||
insideFlapBack?: string | Image | null;
|
||||
};
|
||||
pages?:
|
||||
| {
|
||||
page: number;
|
||||
image: string | Image;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
};
|
||||
urls?:
|
||||
| {
|
||||
url: string;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
releaseDate?: string | null;
|
||||
priceEnabled?: boolean | null;
|
||||
price?: {
|
||||
amount: number;
|
||||
currency: string | Currency;
|
||||
};
|
||||
sizeEnabled?: boolean | null;
|
||||
size?: {
|
||||
width: number;
|
||||
height: number;
|
||||
thickness?: number | null;
|
||||
};
|
||||
weightEnabled?: boolean | null;
|
||||
weight?: {
|
||||
amount: number;
|
||||
};
|
||||
pageInfoEnabled?: boolean | null;
|
||||
pageInfo?: {
|
||||
pageCount: number;
|
||||
bindingType?: ('Paperback' | 'Hardcover') | null;
|
||||
pageOrder?: ('Left to right' | 'Right to left') | null;
|
||||
};
|
||||
folders?: (string | Folder)[] | null;
|
||||
parentItems?: (string | Collectible)[] | null;
|
||||
subitems?: (string | Collectible)[] | null;
|
||||
contents?:
|
||||
| {
|
||||
content:
|
||||
| {
|
||||
relationTo: 'pages';
|
||||
value: string | Page;
|
||||
}
|
||||
| {
|
||||
relationTo: 'generic-contents';
|
||||
value: string | GenericContent;
|
||||
};
|
||||
range?:
|
||||
| (
|
||||
| {
|
||||
start: number;
|
||||
end: number;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'pageRange';
|
||||
}
|
||||
| {
|
||||
start: string;
|
||||
end: string;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'timeRange';
|
||||
}
|
||||
| {
|
||||
translations?:
|
||||
| {
|
||||
language: string | Language;
|
||||
note: {
|
||||
root: {
|
||||
children: {
|
||||
type: string;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
type: string;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'other';
|
||||
}
|
||||
)[]
|
||||
| null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
updatedBy: string | Recorder;
|
||||
@ -340,11 +349,10 @@ export interface LibraryItem {
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "library-items-thumbnails".
|
||||
* via the `definition` "images".
|
||||
*/
|
||||
export interface LibraryItemThumbnail {
|
||||
export interface Image {
|
||||
id: string;
|
||||
libraryItem?: (string | LibraryItem)[] | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
@ -370,48 +378,40 @@ export interface LibraryItemThumbnail {
|
||||
filesize?: number | null;
|
||||
filename?: string | null;
|
||||
};
|
||||
square?: {
|
||||
url?: string | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
filename?: string | null;
|
||||
};
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "library-items-gallery".
|
||||
* via the `definition` "tags".
|
||||
*/
|
||||
export interface LibraryItemGallery {
|
||||
export interface Tag {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
slug: string;
|
||||
translations: {
|
||||
language: string | Language;
|
||||
name: string;
|
||||
id?: string | null;
|
||||
}[];
|
||||
group: string | TagsGroup;
|
||||
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: {
|
||||
language: string | Language;
|
||||
name: string;
|
||||
id?: string | null;
|
||||
}[];
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
sizes?: {
|
||||
thumb?: {
|
||||
url?: string | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
filename?: string | null;
|
||||
};
|
||||
small?: {
|
||||
url?: string | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
filename?: string | null;
|
||||
};
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
@ -468,86 +468,6 @@ export interface RecordersThumbnail {
|
||||
};
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "library-items-scans".
|
||||
*/
|
||||
export interface LibraryItemScans {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
sizes?: {
|
||||
thumb?: {
|
||||
url?: string | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
filename?: string | null;
|
||||
};
|
||||
og?: {
|
||||
url?: string | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
filename?: string | null;
|
||||
};
|
||||
medium?: {
|
||||
url?: string | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
filename?: string | null;
|
||||
};
|
||||
large?: {
|
||||
url?: string | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
filename?: string | null;
|
||||
};
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "files".
|
||||
*/
|
||||
export interface File {
|
||||
id: string;
|
||||
filename: string;
|
||||
type: 'LibraryScans' | 'LibrarySoundtracks' | 'ContentVideo' | 'ContentAudio';
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "keys".
|
||||
*/
|
||||
export interface Key {
|
||||
id: string;
|
||||
name: string;
|
||||
type:
|
||||
| 'Contents'
|
||||
| 'LibraryAudio'
|
||||
| 'LibraryVideo'
|
||||
| 'LibraryTextual'
|
||||
| 'LibraryGroup'
|
||||
| 'Library'
|
||||
| 'Weapons'
|
||||
| 'GamePlatforms'
|
||||
| 'Categories'
|
||||
| 'Wordings';
|
||||
translations?: CategoryTranslations;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "currencies".
|
||||
@ -561,9 +481,10 @@ export interface Currency {
|
||||
*/
|
||||
export interface Page {
|
||||
id: string;
|
||||
type: 'Content' | 'Article' | 'Generic';
|
||||
slug: string;
|
||||
type: 'Content' | 'Post' | 'Generic';
|
||||
thumbnail?: string | Image | null;
|
||||
backgroundImage?: string | Image | null;
|
||||
tags?: (string | Tag)[] | null;
|
||||
authors?: (string | Recorder)[] | null;
|
||||
translations: {
|
||||
@ -608,7 +529,7 @@ export interface Page {
|
||||
id?: string | null;
|
||||
}[];
|
||||
folders?: (string | Folder)[] | null;
|
||||
collectibles?: (string | LibraryItem)[] | null;
|
||||
collectibles?: (string | Collectible)[] | null;
|
||||
updatedBy: string | Recorder;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@ -616,71 +537,16 @@ export interface Page {
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "images".
|
||||
* via the `definition` "generic-contents".
|
||||
*/
|
||||
export interface Image {
|
||||
export interface GenericContent {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
url?: string | null;
|
||||
filename?: string | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
sizes?: {
|
||||
thumb?: {
|
||||
url?: string | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
filename?: string | null;
|
||||
};
|
||||
og?: {
|
||||
url?: string | null;
|
||||
width?: number | null;
|
||||
height?: number | null;
|
||||
mimeType?: string | null;
|
||||
filesize?: number | null;
|
||||
filename?: string | null;
|
||||
};
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "tags".
|
||||
*/
|
||||
export interface Tag {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
slug: string;
|
||||
translations?:
|
||||
| {
|
||||
name: string;
|
||||
translations: {
|
||||
language: string | Language;
|
||||
name: string;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
group: string | TagsGroup;
|
||||
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?:
|
||||
| {
|
||||
language: string | Language;
|
||||
name: string;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
}[];
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@ -786,10 +652,8 @@ export interface Weapon {
|
||||
id: string;
|
||||
slug: string;
|
||||
thumbnail?: string | WeaponsThumbnail | null;
|
||||
type: string | Key;
|
||||
group?: (string | null) | WeaponsGroup;
|
||||
appearances: {
|
||||
categories: (string | Key)[];
|
||||
translations: {
|
||||
language: string | Language;
|
||||
sourceLanguage: string | Language;
|
||||
@ -1004,7 +868,7 @@ export interface VideosChannel {
|
||||
export interface Wording {
|
||||
id: string;
|
||||
name: string;
|
||||
translations?: CategoryTranslations;
|
||||
translations: CategoryTranslations;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@ -1047,7 +911,7 @@ export interface PayloadMigration {
|
||||
* via the `definition` "SpacerBlock".
|
||||
*/
|
||||
export interface SpacerBlock {
|
||||
size: 'Small' | 'Medium' | 'Large' | 'XLarge';
|
||||
size: 'Small' | 'Medium' | 'Large' | 'Extra Large';
|
||||
blockType: 'spacerBlock';
|
||||
}
|
||||
/**
|
||||
@ -1139,12 +1003,7 @@ export enum Collections {
|
||||
ChronologyItems = "chronology-items",
|
||||
Currencies = "currencies",
|
||||
Files = "files",
|
||||
Keys = "keys",
|
||||
Languages = "languages",
|
||||
LibraryItems = "library-items",
|
||||
LibraryItemsThumbnails = "library-items-thumbnails",
|
||||
LibraryItemsScans = "library-items-scans",
|
||||
LibraryItemsGallery = "library-items-gallery",
|
||||
Notes = "notes",
|
||||
Pages = "pages",
|
||||
PagesThumbnails = "pages-thumbnails",
|
||||
@ -1160,7 +1019,9 @@ export enum Collections {
|
||||
Tags = "tags",
|
||||
TagsGroups = "tags-groups",
|
||||
Images = "images",
|
||||
Wordings = "wordings"
|
||||
Wordings = "wordings",
|
||||
Collectibles = "collectibles",
|
||||
GenericContents = "generic-contents",
|
||||
}
|
||||
|
||||
export enum CollectionGroups {
|
||||
@ -1169,19 +1030,6 @@ export enum CollectionGroups {
|
||||
Meta = "Meta",
|
||||
}
|
||||
|
||||
export enum KeysTypes {
|
||||
Contents = "Contents",
|
||||
LibraryAudio = "Library / Audio",
|
||||
LibraryVideo = "Library / Video",
|
||||
LibraryTextual = "Library / Textual",
|
||||
LibraryGroup = "Library / Group",
|
||||
Library = "Library",
|
||||
Weapons = "Weapons",
|
||||
GamePlatforms = "Game Platforms",
|
||||
Categories = "Categories",
|
||||
Wordings = "Wordings",
|
||||
}
|
||||
|
||||
export enum LanguageCodes {
|
||||
en = "English",
|
||||
fr = "French",
|
||||
@ -1191,31 +1039,27 @@ export enum LanguageCodes {
|
||||
"zh" = "Chinese",
|
||||
}
|
||||
|
||||
export enum FileTypes {
|
||||
LibraryScans = "Library / Scans",
|
||||
LibrarySoundtracks = "Library / Soundtracks",
|
||||
ContentVideo = "Content / Video",
|
||||
ContentAudio = "Content / Audio",
|
||||
}
|
||||
|
||||
export enum LibraryItemsTypes {
|
||||
Textual = "Textual",
|
||||
Audio = "Audio",
|
||||
Video = "Video",
|
||||
Game = "Game",
|
||||
Other = "Other",
|
||||
}
|
||||
|
||||
export enum LibraryItemsTextualBindingTypes {
|
||||
export enum CollectibleBindingTypes {
|
||||
Paperback = "Paperback",
|
||||
Hardcover = "Hardcover",
|
||||
}
|
||||
|
||||
export enum LibraryItemsTextualPageOrders {
|
||||
export enum CollectiblePageOrders {
|
||||
LeftToRight = "Left to right",
|
||||
RightToLeft = "Right to left",
|
||||
}
|
||||
|
||||
export enum CollectibleNature {
|
||||
Physical = "Physical",
|
||||
Digital = "Digital",
|
||||
}
|
||||
|
||||
export enum CollectibleContentType {
|
||||
None = "None",
|
||||
Indexes = "Index-based",
|
||||
Pages = "Page-based",
|
||||
}
|
||||
|
||||
export enum RecordersRoles {
|
||||
Admin = "Admin",
|
||||
Recorder = "Recorder",
|
||||
@ -1235,7 +1079,7 @@ export enum VideoSources {
|
||||
|
||||
export enum PageType {
|
||||
Content = "Content",
|
||||
Article = "Article",
|
||||
Post = "Post",
|
||||
Generic = "Generic",
|
||||
}
|
||||
|
||||
@ -1583,12 +1427,12 @@ export type EndpointFolder = EndpointFolderPreview & {
|
||||
};
|
||||
files: (
|
||||
| {
|
||||
relationTo: "library-items";
|
||||
value: LibraryItem;
|
||||
relationTo: "collectibles";
|
||||
value: EndpointCollectiblePreview;
|
||||
}
|
||||
| {
|
||||
relationTo: "pages";
|
||||
value: Page;
|
||||
value: EndpointPagePreview;
|
||||
}
|
||||
)[];
|
||||
};
|
||||
@ -1616,17 +1460,6 @@ export type EndpointRecorder = {
|
||||
}[];
|
||||
};
|
||||
|
||||
export type EndpointKey = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: Key["type"];
|
||||
translations: {
|
||||
language: string;
|
||||
name: string;
|
||||
short: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type EndpointWording = {
|
||||
name: string;
|
||||
translations: {
|
||||
@ -1654,7 +1487,7 @@ export type EndpointTagsGroup = {
|
||||
tags: EndpointTag[];
|
||||
};
|
||||
|
||||
export type EndpointPage = {
|
||||
export type EndpointPagePreview = {
|
||||
slug: string;
|
||||
type: PageType;
|
||||
thumbnail?: PayloadImage;
|
||||
@ -1662,18 +1495,24 @@ export type EndpointPage = {
|
||||
tagGroups: TagGroup[];
|
||||
translations: {
|
||||
language: string;
|
||||
sourceLanguage: string;
|
||||
pretitle?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
}[];
|
||||
status: "draft" | "published";
|
||||
};
|
||||
|
||||
export type EndpointPage = EndpointPagePreview & {
|
||||
backgroundImage?: PayloadImage;
|
||||
translations: (EndpointPagePreview["translations"][number] & {
|
||||
sourceLanguage: string;
|
||||
summary?: RichTextContent;
|
||||
content: RichTextContent;
|
||||
transcribers: string[];
|
||||
translators: string[];
|
||||
proofreaders: string[];
|
||||
toc: TableOfContentEntry[];
|
||||
}[];
|
||||
status: "draft" | "published";
|
||||
})[];
|
||||
parentPages: ParentPage[];
|
||||
};
|
||||
|
||||
@ -1684,6 +1523,82 @@ export type ParentPage = {
|
||||
tag: string;
|
||||
};
|
||||
|
||||
export type EndpointCollectiblePreview = {
|
||||
slug: string;
|
||||
thumbnail?: PayloadImage;
|
||||
translations: {
|
||||
language: string;
|
||||
pretitle?: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
description?: RichTextContent;
|
||||
}[];
|
||||
tagGroups: TagGroup[];
|
||||
status: "draft" | "published";
|
||||
releaseDate?: string;
|
||||
languages: string[];
|
||||
};
|
||||
|
||||
export type EndpointCollectible = EndpointCollectiblePreview & {
|
||||
backgroundImage?: PayloadImage;
|
||||
nature: CollectibleNature;
|
||||
gallery: PayloadImage[];
|
||||
scans: PayloadImage[];
|
||||
urls: { url: string; label: string }[];
|
||||
price?: {
|
||||
amount: number;
|
||||
currency: string;
|
||||
};
|
||||
size?: {
|
||||
width: number;
|
||||
height: number;
|
||||
thickness?: number;
|
||||
};
|
||||
weight?: number;
|
||||
pageInfo?: {
|
||||
pageCount: number;
|
||||
bindingType?: CollectibleBindingTypes;
|
||||
pageOrder?: CollectiblePageOrders;
|
||||
};
|
||||
subitems: EndpointCollectiblePreview[];
|
||||
contents: {
|
||||
content:
|
||||
| {
|
||||
relationTo: "pages";
|
||||
value: EndpointPagePreview;
|
||||
}
|
||||
| {
|
||||
relationTo: "generic-contents";
|
||||
value: {
|
||||
translations: {
|
||||
language: string;
|
||||
name: string;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
||||
range?:
|
||||
| {
|
||||
type: "pageRange";
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
| {
|
||||
type: "timeRange";
|
||||
start: string;
|
||||
end: string;
|
||||
}
|
||||
| {
|
||||
type: "other";
|
||||
translations: {
|
||||
language: string;
|
||||
note: RichTextContent;
|
||||
}[];
|
||||
};
|
||||
}[];
|
||||
parentPages: ParentPage[];
|
||||
};
|
||||
|
||||
export type TagGroup = { slug: string; icon: string; values: string[] };
|
||||
|
||||
export type TableOfContentEntry = {
|
||||
@ -1723,4 +1638,6 @@ export const payload = {
|
||||
await (await request(payloadApiUrl(Collections.TagsGroups, `all`))).json(),
|
||||
getPage: async (slug: string): Promise<EndpointPage> =>
|
||||
await (await request(payloadApiUrl(Collections.Pages, `slug/${slug}`))).json(),
|
||||
getCollectible: async (slug: string): Promise<EndpointCollectible> =>
|
||||
await (await request(payloadApiUrl(Collections.Collectibles, `slug/${slug}`))).json(),
|
||||
};
|
||||
|
@ -12,3 +12,23 @@ export const formatRecorder = (recorderId: string): string => {
|
||||
|
||||
return result.username;
|
||||
};
|
||||
|
||||
export const formatInlineTitle = ({
|
||||
pretitle,
|
||||
title,
|
||||
subtitle,
|
||||
}: {
|
||||
pretitle?: string | undefined;
|
||||
title: string;
|
||||
subtitle?: string | undefined;
|
||||
}): string => {
|
||||
let result = "";
|
||||
if (pretitle) {
|
||||
result += `${pretitle}: `;
|
||||
}
|
||||
result += title;
|
||||
if (subtitle) {
|
||||
result += ` — ${subtitle}`;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user