Added files support in folders and collectibles

This commit is contained in:
DrMint 2024-06-22 21:21:56 +02:00
parent 66ac5bd519
commit e854d88d89
21 changed files with 682 additions and 202 deletions

View File

@ -8,6 +8,8 @@ PAYLOAD_USER=myemail@domain.com
PAYLOAD_PASSWORD=somepassword123
WEB_HOOK_TOKEN=webhookd5e6ea45ef4e66eaa151612bdcb599df
ENABLE_PRECACHING=true
## OPEN EXCHANGE RATE
OER_APP_ID=oerappid5e6ea45ef4e66eaa151612bdcb599df

View File

@ -9,6 +9,7 @@
## Short term
- [Bugs] On android Chrome, the setting button in the header flashes for a few ms when the page is loading
- [Feat] [caching] Use getURLs for precaching + precache everything
- [Bugs] Make sure uploads name are slug-like and with an extension.
- [Bugs] Nyupun can't upload subtitles files
@ -42,6 +43,8 @@
## Long term
- [Feat] Invalidate Back/Forward Cache when changing language/theme/currency
- [Feat] Hovering on a preview card could give a more detailed summary/preview (with all attributes)
- [Feat] Explore posibilities for View Transitions
- [Feat] Revemp theme system using light-dark https://caniuse.com/mdn-css_types_color_light-dark
- [Feat] Add reduce motion to element that zoom when hovering

View File

@ -1,6 +1,6 @@
{
"name": "v3.accords-library.com",
"version": "3.0.0-beta.5",
"version": "3.0.0-beta.6",
"scripts": {
"dev": "astro dev",
"start": "astro dev",

View File

@ -5,16 +5,21 @@ import Button from "./Button.astro";
interface Props {
href: string;
filename: string;
useBlob?: boolean;
}
const { href, filename } = Astro.props;
const { href, filename, useBlob = false } = Astro.props;
const { t } = await getI18n(Astro.locals.currentLocale);
---
{/* ------------------------------------------- HTML ------------------------------------------- */}
<download-button href={href} filename={filename} class="when-js when-no-print">
<download-button
href={href}
filename={filename}
class="when-js when-no-print"
data-use-blob={useBlob}>
<Button title={t("global.downloadButton")} icon="material-symbols:download" />
</download-button>
@ -26,17 +31,23 @@ const { t } = await getI18n(Astro.locals.currentLocale);
customElement("download-button", (elem) => {
const href = elem.getAttribute("href");
const filename = elem.getAttribute("filename");
const useBlob = elem.hasAttribute("data-use-blob");
if (!href || !filename) return;
elem.addEventListener("click", async () => {
const res = await fetch(href);
const blob = await res.blob();
const blobURL = window.URL.createObjectURL(blob);
let url;
if (useBlob) {
const res = await fetch(href);
const blob = await res.blob();
url = window.URL.createObjectURL(blob);
} else {
url = href;
}
var link = document.createElement("a");
link.download = filename;
link.href = blobURL;
link.href = url;
link.click();
link.remove();
});

View File

@ -108,7 +108,7 @@ const hasNavigation = previousImageHref || nextImageHref;
{attributes.length > 0 && <Attributes attributes={attributes} />}
{credits.length > 0 && <Credits credits={credits} />}
{metaAttributes.length > 0 && <Attributes attributes={metaAttributes} />}
{filename && <DownloadButton href={url} filename={filename} />}
{filename && <DownloadButton href={url} filename={filename} useBlob />}
</div>
</div>

View File

@ -0,0 +1,71 @@
---
import GenericPreview from "components/Previews/GenericPreview.astro";
import { getI18n } from "src/i18n/i18n";
import type { EndpointFilePreview } from "src/shared/payload/payload-sdk";
import { getFileIcon } from "src/utils/attributes";
interface Props {
file: EndpointFilePreview;
}
const { getLocalizedMatch, getLocalizedUrl, t, formatFilesize } = await getI18n(
Astro.locals.currentLocale
);
const {
file: { id, translations, attributes, filename, thumbnail, mimeType, filesize },
} = Astro.props;
const { pretitle, title, subtitle, language } =
translations.length > 0
? getLocalizedMatch(translations)
: { pretitle: undefined, title: filename, subtitle: undefined, language: undefined };
const hasTitle = title !== filename;
const attributesWithMeta = [
...attributes,
...(hasTitle
? [
{
title: t("global.media.attributes.filename"),
icon: "material-symbols:unknown-document",
values: [{ name: filename }],
},
]
: []),
{
title: t("global.media.attributes.filesize"),
icon: "material-symbols:hard-drive",
values: [{ name: formatFilesize(filesize) }],
},
];
const getFileTypeLabel = (): string => {
switch (mimeType) {
case "application/zip":
return t("global.previewTypes.zip");
case "application/pdf":
return t("global.previewTypes.pdf");
default:
return mimeType;
}
};
---
{/* ------------------------------------------- HTML ------------------------------------------- */}
<GenericPreview
pretitle={pretitle}
title={title}
subtitle={subtitle}
lang={language}
thumbnail={thumbnail}
href={getLocalizedUrl(`/files/${id}`)}
attributes={attributesWithMeta}
icon={getFileIcon(mimeType)}
iconHoverLabel={getFileTypeLabel()}
smallTitle={!hasTitle}
/>

View File

@ -147,4 +147,7 @@ export type WordingKey =
| "collectibles.nature"
| "collectibles.languages"
| "collectibles.nature.physical"
| "collectibles.nature.digital";
| "collectibles.nature.digital"
| "global.previewTypes.zip"
| "global.previewTypes.pdf"
| "collectibles.files";

8
src/icons/image-file.svg Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="32" height="32" viewBox="0 0 24 24" version="1.1" id="svg1" xml:space="preserve"
xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs1" />
<path fill="currentColor"
d="M 6,22 C 5.45,22 4.979,21.804 4.587,21.412 4.195,21.02 3.9993333,20.549333 4,20 V 4 C 4,3.45 4.196,2.979 4.588,2.587 4.98,2.195 5.4506667,1.9993333 6,2 h 8 l 6,6 v 12 c 0,0.55 -0.196,1.021 -0.588,1.413 C 19.02,21.805 18.549333,22.000667 18,22 Z M 13,9 h 5 L 13,4 Z m -5.8880933,9.669723 h 9.5724213 l -2.991381,-3.98851 -2.393106,3.190808 -1.7948288,-2.393105 z M 9.1061613,13.08581 c 0.332376,0 0.6150281,-0.116465 0.8479576,-0.349393 0.2329281,-0.23293 0.3491271,-0.515317 0.3485951,-0.84716 0,-0.332376 -0.116464,-0.615029 -0.3493937,-0.847957 -0.2329286,-0.232929 -0.5153151,-0.349128 -0.847159,-0.348596 -0.3323761,0 -0.6150282,0.116464 -0.8479571,0.349393 -0.232929,0.23293 -0.3491278,0.515316 -0.3485957,0.84716 0,0.332376 0.1164644,0.615028 0.3493934,0.847957 0.2329289,0.232929 0.5153154,0.349128 0.8471594,0.348596 z"
id="path1" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

8
src/icons/pdf-file.svg Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="32" height="32" viewBox="0 0 24 24" version="1.1" id="svg1" xml:space="preserve"
xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs1" />
<path fill="currentColor"
d="M 6,22 C 5.45,22 4.979,21.804 4.587,21.412 4.195,21.02 3.9993333,20.549333 4,20 V 4 C 4,3.45 4.196,2.979 4.588,2.587 4.98,2.195 5.4506667,1.9993333 6,2 h 8 l 6,6 v 12 c 0,0.55 -0.196,1.021 -0.588,1.413 C 19.02,21.805 18.549333,22.000667 18,22 Z M 13,9 h 5 L 13,4 Z m -6.299094,9.065324 h 1 v -2 h 1 c 0.283333,0 0.521,-0.096 0.713,-0.288 0.192,-0.192 0.287667,-0.429333 0.287,-0.712 v -1 c 0,-0.283334 -0.096,-0.521 -0.288,-0.713 -0.192,-0.192 -0.429333,-0.287667 -0.712,-0.287 h -2 z m 1,-3 v -1 h 1 v 1 z m 3,3 h 2 c 0.283333,0 0.520999,-0.096 0.712999,-0.288 0.192,-0.192 0.287667,-0.429333 0.287,-0.712 v -3 c 0,-0.283334 -0.096,-0.521 -0.288,-0.713 -0.192,-0.192 -0.429332,-0.287667 -0.711999,-0.287 h -2 z m 1,-1 v -3 h 1 v 3 z m 2.999999,1 h 1 v -2 h 1 v -1 h -1 v -1 h 1 v -1 h -2 z"
id="path1" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -10,6 +10,7 @@ import { getI18n } from "src/i18n/i18n";
import { payload } from "src/utils/payload";
import { formatInlineTitle, formatRichTextToString } from "src/utils/format";
import { fetchOr404 } from "src/utils/responses";
import { getFileIcon } from "src/utils/attributes";
const id = Astro.params.id!;
const audio = await fetchOr404(() => payload.getAudioByID(id));
@ -30,6 +31,7 @@ const {
createdAt,
updatedAt,
thumbnail,
mimeType,
} = audio;
const { pretitle, title, subtitle, description, language } = getLocalizedMatch(translations);
@ -39,7 +41,7 @@ const metaAttributes = [
? [
{
title: t("global.media.attributes.filename"),
icon: "material-symbols:audio-file",
icon: getFileIcon(mimeType),
values: [{ name: filename }],
withBorder: false,
},

View File

@ -0,0 +1,52 @@
---
import FilePreview from "components/Previews/FilePreview.astro";
import TitleIcon from "components/TitleIcon.astro";
import { getI18n } from "src/i18n/i18n";
import type { EndpointCollectible } from "src/shared/payload/payload-sdk";
interface Props {
files: EndpointCollectible["files"];
}
const { files } = Astro.props;
const { t } = await getI18n(Astro.locals.currentLocale);
---
{/* ------------------------------------------- HTML ------------------------------------------- */}
<div id="container">
<TitleIcon title={t("collectibles.files")} icon="material-symbols:file-save" />
<div id="values">
{files.map((file) => <FilePreview file={file} />)}
</div>
</div>
{/* ------------------------------------------- CSS -------------------------------------------- */}
<style>
#container {
margin-top: 6em;
display: flex;
flex-direction: column;
gap: 2em;
& > #values {
display: grid;
gap: clamp(6px, 2vmin, 16px);
grid-template-columns: repeat(auto-fill, 270px);
@media (max-width: 600.5px) {
grid-template-columns: 1fr 1fr;
row-gap: 12px;
}
@media (max-width: 24rem) {
grid-template-columns: 1fr;
}
align-items: start;
}
}
</style>

View File

@ -5,6 +5,7 @@ import { getI18n } from "src/i18n/i18n";
import { payload } from "src/utils/payload";
import { formatInlineTitle, formatRichTextToString } from "src/utils/format";
import { fetchOr404 } from "src/utils/responses";
import { getFileIcon } from "src/utils/attributes";
const slug = Astro.params.slug!;
const index = Astro.params.index!;
@ -18,7 +19,7 @@ if (galleryImage instanceof Response) {
}
const { parentPages, previousIndex, nextIndex, image } = galleryImage;
const { filename, translations, createdAt, updatedAt, credits, attributes } = image;
const { filename, translations, createdAt, updatedAt, credits, attributes, mimeType } = image;
const { pretitle, title, subtitle, description, language } =
translations.length > 0
@ -36,7 +37,7 @@ const metaAttributes = [
? [
{
title: t("global.media.attributes.filename"),
icon: "material-symbols:unknown-document",
icon: getFileIcon(mimeType),
values: [{ name: filename }],
withBorder: false,
},

View File

@ -19,6 +19,7 @@ import type { Attribute } from "src/utils/attributes";
import { payload } from "src/utils/payload";
import { sizesToSrcset } from "src/utils/img";
import RichText from "components/RichText/RichText.astro";
import SubFilesSection from "./_components/SubFilesSection.astro";
const slug = Astro.params.slug!;
const { getLocalizedMatch, getLocalizedUrl, t, formatDate, formatPrice } = await getI18n(
@ -43,6 +44,7 @@ const {
gallery,
scans,
subitems,
files,
parentPages,
attributes,
contents,
@ -221,7 +223,7 @@ if (price) {
</Fragment>
{subitems.length > 0 && <SubitemSection subitems={subitems} />}
{files.length > 0 && <SubFilesSection files={files} />}
{contents.length > 0 && <ContentsSection contents={contents} />}
</AsideLayout>
</AppLayout>

View File

@ -0,0 +1,200 @@
---
import AppLayout from "components/AppLayout/AppLayout.astro";
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
import Attributes from "components/Attributes.astro";
import Credits from "components/Credits.astro";
import DownloadButton from "components/DownloadButton.astro";
import RichText from "components/RichText/RichText.astro";
import { getI18n } from "src/i18n/i18n";
import { payload } from "src/utils/payload";
import { formatInlineTitle, formatRichTextToString } from "src/utils/format";
import { fetchOr404 } from "src/utils/responses";
import { getFileIcon } from "src/utils/attributes";
import { Icon } from "astro-icon/components";
import { sizesToSrcset } from "src/utils/img";
const id = Astro.params.id!;
const video = await fetchOr404(() => payload.getFileByID(id));
if (video instanceof Response) {
return video;
}
const { getLocalizedMatch, t, formatFilesize, formatDate } = await getI18n(
Astro.locals.currentLocale
);
const {
translations,
attributes,
filename,
url,
credits,
mimeType,
filesize,
updatedAt,
createdAt,
thumbnail,
} = video;
const { pretitle, title, subtitle, description, language } =
translations.length > 0
? getLocalizedMatch(translations)
: {
pretitle: undefined,
title: filename,
subtitle: undefined,
description: undefined,
language: undefined,
};
const metaAttributes = [
...(filename && title !== filename && thumbnail
? [
{
title: t("global.media.attributes.filename"),
icon: getFileIcon(mimeType),
values: [{ name: filename }],
withBorder: false,
},
]
: []),
{
title: t("global.media.attributes.filesize"),
icon: "material-symbols:hard-drive",
values: [{ name: formatFilesize(filesize) }],
withBorder: false,
},
{
title: t("global.media.attributes.createdAt"),
icon: "material-symbols:calendar-add-on",
values: [{ name: formatDate(new Date(createdAt)) }],
withBorder: false,
},
{
title: t("global.media.attributes.updatedAt"),
icon: "material-symbols:edit-calendar",
values: [{ name: formatDate(new Date(updatedAt)) }],
withBorder: false,
},
];
const smallTitle = title === filename;
---
{/* ------------------------------------------- HTML ------------------------------------------- */}
<AppLayout
openGraph={{
title: formatInlineTitle({ pretitle, title, subtitle }),
description: description && formatRichTextToString(description),
thumbnail,
}}>
<div id="container">
{
thumbnail ? (
<a
id="image-anchor"
href={url}
target="_blank"
style={`aspect-ratio:${thumbnail.width}/${thumbnail.height};`}>
<img
src={url}
srcset={sizesToSrcset(thumbnail.sizes)}
sizes={`(max-aspect-ratio: ${thumbnail.width / 0.9}/${thumbnail.height / 0.7}) 90vw, ${(thumbnail.width / thumbnail.height) * 70}vh`}
width={thumbnail.width}
height={thumbnail.height}
/>
</a>
) : (
<a href={url} id="icon-container">
<Icon name={getFileIcon(mimeType)} width={32} height={32} />
<p>{filename}</p>
</a>
)
}
<div id="info">
{
smallTitle ? (
<h1 class="font-4xl" lang={language}>
{title}
</h1>
) : (
<AppLayoutTitle pretitle={pretitle} title={title} subtitle={subtitle} lang={language} />
)
}
{description && <RichText content={description} context={{ lang: language }} />}
{attributes.length > 0 && <Attributes attributes={attributes} />}
{credits.length > 0 && <Credits credits={credits} />}
{metaAttributes.length > 0 && <Attributes attributes={metaAttributes} />}
{filename && <DownloadButton href={url} filename={filename} />}
</div>
</div>
</AppLayout>
{/* ------------------------------------------- CSS -------------------------------------------- */}
<style>
#container {
display: flex;
flex-direction: column;
gap: 6em;
align-items: center;
h1 {
max-width: 35em;
}
& > #image-anchor {
transition: 100ms scale;
box-shadow: 0 5px 20px -10px var(--color-shadow-0);
&:hover,
&:focus-visible {
scale: 102%;
}
max-height: 70vh;
}
& > #icon-container {
display: grid;
place-content: center;
place-items: center;
gap: 0.5em;
border-radius: 0.7em;
padding: 3em;
box-sizing: border-box;
margin: 0.4em;
max-width: 35em;
width: 100%;
aspect-ratio: 3/2;
background-color: var(--color-elevation-2);
color: var(--color-base-400);
text-align: center;
& > svg {
width: clamp(16px, 25vw, 96px);
height: clamp(16px, 25vw, 96px);
}
transition: 100ms scale;
&:hover,
&:focus-visible {
scale: 102%;
}
}
& > #info {
display: flex;
flex-direction: column;
gap: 4em;
align-items: start;
@media (max-width: 35rem) {
gap: 6em;
}
}
}
</style>

View File

@ -14,6 +14,7 @@ import AppLayout from "components/AppLayout/AppLayout.astro";
import AppLayoutTitle from "components/AppLayout/components/AppLayoutTitle.astro";
import { payload } from "src/utils/payload";
import RichText from "components/RichText/RichText.astro";
import FilePreview from "components/Previews/FilePreview.astro";
const slug = Astro.params.slug!;
@ -74,6 +75,9 @@ const meta = getLocalizedMatch(folder.translations);
case Collections.Videos:
return <VideoPreview video={value} />;
case Collections.Files:
return <FilePreview file={value} />;
default:
return (
<ErrorMessage

View File

@ -5,6 +5,7 @@ import { getI18n } from "src/i18n/i18n";
import { payload } from "src/utils/payload";
import { formatInlineTitle, formatRichTextToString } from "src/utils/format";
import { fetchOr404 } from "src/utils/responses";
import { getFileIcon } from "src/utils/attributes";
const id = Astro.params.id!;
const image = await fetchOr404(() => payload.getImageByID(id));
@ -13,7 +14,7 @@ if (image instanceof Response) {
}
const { getLocalizedMatch, formatDate, t } = await getI18n(Astro.locals.currentLocale);
const { filename, translations, attributes, credits, createdAt, updatedAt } = image;
const { filename, translations, attributes, credits, createdAt, updatedAt, mimeType } = image;
const { pretitle, title, subtitle, description, language } =
translations.length > 0
@ -31,7 +32,7 @@ const metaAttributes = [
? [
{
title: t("global.media.attributes.filename"),
icon: "material-symbols:unknown-document",
icon: getFileIcon(mimeType),
values: [{ name: filename }],
withBorder: false,
},

View File

@ -10,6 +10,7 @@ import { getI18n } from "src/i18n/i18n";
import { payload } from "src/utils/payload";
import { formatInlineTitle, formatRichTextToString } from "src/utils/format";
import { fetchOr404 } from "src/utils/responses";
import { getFileIcon } from "src/utils/attributes";
const id = Astro.params.id!;
const video = await fetchOr404(() => payload.getVideoByID(id));
@ -30,6 +31,7 @@ const {
updatedAt,
createdAt,
thumbnail,
mimeType,
} = video;
const { pretitle, title, subtitle, description, language } = getLocalizedMatch(translations);
@ -39,7 +41,7 @@ const metaAttributes = [
? [
{
title: t("global.media.attributes.filename"),
icon: "material-symbols:video-file",
icon: getFileIcon(mimeType),
values: [{ name: filename }],
withBorder: false,
},

View File

@ -1,177 +1,177 @@
{
"disclaimer": "Usage subject to terms: https://openexchangerates.org/terms",
"license": "https://openexchangerates.org/license",
"timestamp": 1718769601,
"timestamp": 1719061206,
"base": "USD",
"rates": {
"AED": 3.673,
"AFN": 70.163141,
"ALL": 93.552377,
"AMD": 388.086509,
"ANG": 1.802556,
"AOA": 854.494667,
"ARS": 905.759291,
"AUD": 1.501458,
"AFN": 70.649788,
"ALL": 93.642272,
"AMD": 387.350187,
"ANG": 1.798477,
"AOA": 854.5,
"ARS": 904.605803,
"AUD": 1.50355,
"AWG": 1.8,
"AZN": 1.7,
"BAM": 1.821501,
"BAM": 1.827253,
"BBD": 2,
"BDT": 117.292047,
"BGN": 1.8212,
"BHD": 0.376891,
"BIF": 2869.980338,
"BDT": 117.251367,
"BGN": 1.827253,
"BHD": 0.376512,
"BIF": 2869.433797,
"BMD": 1,
"BND": 1.35155,
"BOB": 6.897451,
"BRL": 5.4413,
"BND": 1.351988,
"BOB": 6.894941,
"BRL": 5.4321,
"BSD": 1,
"BTC": 0.000015288954,
"BTN": 83.247452,
"BWP": 13.517364,
"BYN": 3.26637,
"BZD": 2.012145,
"CAD": 1.372048,
"CDF": 2835.224044,
"CHF": 0.884319,
"CLF": 0.033902,
"CLP": 935.45,
"CNH": 7.273883,
"CNY": 7.2559,
"COP": 4122.284393,
"CRC": 525.542692,
"BTC": 0.000015559783,
"BTN": 83.329274,
"BWP": 13.475058,
"BYN": 3.265661,
"BZD": 2.011398,
"CAD": 1.36985,
"CDF": 2821.505656,
"CHF": 0.893684,
"CLF": 0.033937,
"CLP": 940.97,
"CNH": 7.2877,
"CNY": 7.2613,
"COP": 4163.3111,
"CRC": 521.605154,
"CUC": 1,
"CUP": 25.75,
"CVE": 102.693429,
"CZK": 23.131,
"DJF": 177.73558,
"DKK": 6.947223,
"DOP": 59.183449,
"DZD": 134.598577,
"EGP": 47.7088,
"CVE": 103.017701,
"CZK": 23.3152,
"DJF": 177.670831,
"DKK": 6.9744,
"DOP": 58.896627,
"DZD": 134.486,
"EGP": 47.71,
"ERN": 15,
"ETB": 57.520491,
"EUR": 0.931299,
"FJD": 2.25895,
"FKP": 0.786911,
"GBP": 0.786911,
"GEL": 2.84,
"GGP": 0.786911,
"GHS": 15.02333,
"GIP": 0.786911,
"ETB": 57.579015,
"EUR": 0.935235,
"FJD": 2.24275,
"FKP": 0.790451,
"GBP": 0.790451,
"GEL": 2.805,
"GGP": 0.790451,
"GHS": 15.118419,
"GIP": 0.790451,
"GMD": 67.75,
"GNF": 8593.158878,
"GTQ": 7.745895,
"GYD": 208.758212,
"HKD": 7.808162,
"HNL": 25.078995,
"HRK": 7.016854,
"HTG": 132.267879,
"HUF": 366.849287,
"IDR": 16349.509717,
"ILS": 3.716955,
"IMP": 0.786911,
"INR": 83.381051,
"IQD": 1306.660001,
"GNF": 8590.530533,
"GTQ": 7.743261,
"GYD": 208.651413,
"HKD": 7.80515,
"HNL": 24.683515,
"HRK": 7.0464,
"HTG": 132.26513,
"HUF": 370.73,
"IDR": 16477.55,
"ILS": 3.7596,
"IMP": 0.790451,
"INR": 83.565401,
"IQD": 1307.235516,
"IRR": 42087.5,
"ISK": 139.04,
"JEP": 0.786911,
"JMD": 155.440387,
"JOD": 0.7089,
"JPY": 157.859,
"KES": 129.27,
"KGS": 87.6016,
"KHR": 4112.767316,
"KMF": 458.849846,
"ISK": 139.4,
"JEP": 0.790451,
"JMD": 155.407057,
"JOD": 0.7087,
"JPY": 159.68497365,
"KES": 129.228764,
"KGS": 86.7587,
"KHR": 4113.574874,
"KMF": 460.549891,
"KPW": 900,
"KRW": 1382.002686,
"KWD": 0.306682,
"KYD": 0.831866,
"KZT": 459.016771,
"LAK": 21889.130162,
"LBP": 89643.929605,
"LKR": 304.461947,
"LRD": 193.81036,
"LSL": 18.097846,
"LYD": 4.840137,
"MAD": 9.975878,
"MDL": 17.828505,
"MGA": 4470.377749,
"MKD": 57.384069,
"KRW": 1389.39,
"KWD": 0.306432,
"KYD": 0.831597,
"KZT": 464.455765,
"LAK": 21935.263396,
"LBP": 89360.676,
"LKR": 304.713417,
"LRD": 193.69365,
"LSL": 17.898504,
"LYD": 4.83767,
"MAD": 9.938151,
"MDL": 17.800024,
"MGA": 4518.181995,
"MKD": 57.490542,
"MMK": 2096.43,
"MNT": 3450,
"MOP": 8.027251,
"MRU": 39.448914,
"MUR": 46.666698,
"MVR": 15.395,
"MWK": 1730.787269,
"MXN": 18.4099,
"MYR": 4.708,
"MZN": 63.885,
"NAD": 18.097846,
"NGN": 1484.33,
"NIO": 36.741079,
"NOK": 10.573511,
"NPR": 133.44253,
"NZD": 1.63038,
"MOP": 8.035984,
"MRU": 39.296492,
"MUR": 46.840006,
"MVR": 15.4,
"MWK": 1730.176733,
"MXN": 18.1108,
"MYR": 4.713,
"MZN": 63.899991,
"NAD": 17.97,
"NGN": 1488,
"NIO": 36.735646,
"NOK": 10.5565,
"NPR": 133.326482,
"NZD": 1.633854,
"OMR": 0.384941,
"PAB": 1,
"PEN": 3.789035,
"PGK": 3.890178,
"PHP": 58.671002,
"PKR": 278.046054,
"PLN": 4.041156,
"PYG": 7507.477573,
"QAR": 3.640673,
"RON": 4.6337,
"RSD": 109.022,
"RUB": 85.747634,
"RWF": 1305.577777,
"SAR": 3.751877,
"SBD": 8.471937,
"SCR": 13.937,
"PEN": 3.797543,
"PGK": 3.892185,
"PHP": 58.831495,
"PKR": 277.9113,
"PLN": 4.044728,
"PYG": 7511.504736,
"QAR": 3.639557,
"RON": 4.6525,
"RSD": 109.468038,
"RUB": 88.982683,
"RWF": 1310.225337,
"SAR": 3.751791,
"SBD": 8.454445,
"SCR": 14.149709,
"SDG": 601,
"SEK": 10.433372,
"SGD": 1.350881,
"SHP": 0.786911,
"SEK": 10.5075,
"SGD": 1.3552,
"SHP": 0.790451,
"SLL": 20969.5,
"SOS": 571.352636,
"SRD": 31.231,
"SOS": 570.280724,
"SRD": 30.797,
"SSP": 130.26,
"STD": 22281.8,
"STN": 22.817654,
"SVC": 8.734971,
"STN": 22.889709,
"SVC": 8.731723,
"SYP": 2512.53,
"SZL": 18.092071,
"THB": 36.730254,
"TJS": 10.681083,
"TMT": 3.5,
"TND": 3.125274,
"TOP": 2.361474,
"TRY": 32.617701,
"TTD": 6.781034,
"TWD": 32.385,
"TZS": 2615,
"UAH": 40.591421,
"UGX": 3711.377309,
"SZL": 17.88917,
"THB": 36.666511,
"TJS": 10.607558,
"TMT": 3.51,
"TND": 3.12907,
"TOP": 2.359788,
"TRY": 32.83,
"TTD": 6.784802,
"TWD": 32.37465,
"TZS": 2619.42356,
"UAH": 40.439198,
"UGX": 3741.782229,
"USD": 1,
"UYU": 39.353278,
"UZS": 12604.759961,
"VES": 36.348426,
"VND": 25451.768898,
"UYU": 39.304902,
"UZS": 12612.753681,
"VES": 36.327958,
"VND": 25458.250123,
"VUV": 118.722,
"WST": 2.8,
"XAF": 610.891881,
"XAG": 0.03397836,
"XAU": 0.00042947,
"XAF": 613.473942,
"XAG": 0.03384954,
"XAU": 0.00043086,
"XCD": 2.70255,
"XDR": 0.757639,
"XOF": 610.891881,
"XPD": 0.00113672,
"XPF": 111.133493,
"XPT": 0.00102682,
"YER": 250.350066,
"ZAR": 18.039756,
"ZMW": 25.779293,
"XDR": 0.757322,
"XOF": 613.473942,
"XPD": 0.00109708,
"XPF": 111.603222,
"XPT": 0.00101001,
"YER": 250.349961,
"ZAR": 17.96396,
"ZMW": 25.421591,
"ZWL": 322
}
}

View File

@ -30,6 +30,7 @@ export interface Config {
videos: Video;
"videos-subtitles": VideoSubtitle;
"videos-channels": VideosChannel;
files: File;
scans: Scan;
tags: Tag;
attributes: Attribute;
@ -414,6 +415,10 @@ export interface Folder {
relationTo: "audios";
value: string | Audio;
}
| {
relationTo: "files";
value: string | File;
}
)[]
| null;
updatedAt: string;
@ -536,9 +541,8 @@ export interface Collectible {
bindingType?: ("Paperback" | "Hardcover") | null;
pageOrder?: ("Left to right" | "Right to left") | null;
};
folders?: (string | Folder)[] | null;
parentItems?: (string | Collectible)[] | null;
subitems?: (string | Collectible)[] | null;
files?: (string | File)[] | null;
contents?:
| {
content:
@ -603,6 +607,8 @@ export interface Collectible {
id?: string | null;
}[]
| null;
folders?: (string | Folder)[] | null;
parentItems?: (string | Collectible)[] | null;
updatedBy: string | Recorder;
updatedAt: string;
createdAt: string;
@ -676,49 +682,35 @@ export interface Currency {
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "generic-contents".
* via the `definition` "files".
*/
export interface GenericContent {
export interface File {
id: string;
name: string;
translations: {
language: string | Language;
name: string;
id?: string | null;
}[];
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "audios".
*/
export interface Audio {
id: string;
duration: number;
thumbnail?: string | MediaThumbnail | null;
translations: {
language: string | Language;
pretitle?: string | null;
title: string;
subtitle?: string | null;
description?: {
root: {
type: string;
children: {
type: string;
version: number;
translations?:
| {
language: string | Language;
pretitle?: string | null;
title: string;
subtitle?: string | null;
description?: {
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;
}[];
direction: ("ltr" | "rtl") | null;
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
id?: string | null;
}[];
} | null;
id?: string | null;
}[]
| null;
attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null;
credits?: Credits;
updatedAt: string;
@ -823,6 +815,64 @@ export interface MediaThumbnail {
};
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "generic-contents".
*/
export interface GenericContent {
id: string;
name: string;
translations: {
language: string | Language;
name: string;
id?: string | null;
}[];
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "audios".
*/
export interface Audio {
id: string;
duration: number;
thumbnail?: string | MediaThumbnail | null;
translations: {
language: string | Language;
pretitle?: string | null;
title: string;
subtitle?: string | null;
description?: {
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;
} | null;
id?: string | null;
}[];
attributes?: (TagsBlock | NumberBlock | TextBlock)[] | null;
credits?: Credits;
updatedAt: string;
createdAt: string;
url?: string | null;
filename?: string | null;
mimeType?: string | null;
filesize?: number | null;
width?: number | null;
height?: number | null;
focalX?: number | null;
focalY?: number | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "videos".
@ -1200,6 +1250,7 @@ export enum Collections {
Collectibles = "collectibles",
CreditsRole = "credits-roles",
Currencies = "currencies",
Files = "files",
Folders = "folders",
GenericContents = "generic-contents",
Images = "images",
@ -1548,6 +1599,10 @@ export type EndpointFolder = EndpointFolderPreview & {
relationTo: Collections.Videos;
value: EndpointVideoPreview;
}
| {
relationTo: Collections.Files;
value: EndpointFilePreview;
}
)[];
parentPages: EndpointSource[];
};
@ -1717,6 +1772,7 @@ export type EndpointCollectible = EndpointCollectiblePreview & {
pageOrder?: CollectiblePageOrders;
};
subitems: EndpointCollectiblePreview[];
files: EndpointFilePreview[];
contents: {
content:
| {
@ -1992,6 +2048,16 @@ export type EndpointVideo = EndpointMedia & {
duration: number;
};
export type EndpointFilePreview = EndpointMediaPreview & {
filesize: number;
thumbnail?: EndpointPayloadImage;
};
export type EndpointFile = EndpointMedia & {
filesize: number;
thumbnail?: EndpointPayloadImage;
};
export type EndpointPayloadImage = PayloadImage & {
sizes: PayloadImage[];
openGraph?: PayloadImage;
@ -2017,6 +2083,7 @@ export type EndpointAllPaths = {
videos: string[];
audios: string[];
images: string[];
files: string[];
recorders: string[];
chronologyEvents: string[];
};
@ -2059,6 +2126,7 @@ export const getSDKEndpoint = {
getImageByIDEndpoint: (id: string) => `/${Collections.Images}/id/${id}`,
getAudioByIDEndpoint: (id: string) => `/${Collections.Audios}/id/${id}`,
getVideoByIDEndpoint: (id: string) => `/${Collections.Videos}/id/${id}`,
getFileByIDEndpoint: (id: string) => `/${Collections.Files}/id/${id}`,
getRecorderByIDEndpoint: (id: string) => `/${Collections.Recorders}/id/${id}`,
getAllPathsEndpoint: () => `/all-paths`,
getLoginEndpoint: () => `/${Collections.Recorders}/login`,
@ -2153,6 +2221,8 @@ export const getPayloadSDK = ({
await request(getSDKEndpoint.getAudioByIDEndpoint(id)),
getVideoByID: async (id: string): Promise<EndpointVideo> =>
await request(getSDKEndpoint.getVideoByIDEndpoint(id)),
getFileByID: async (id: string): Promise<EndpointFile> =>
await request(getSDKEndpoint.getFileByIDEndpoint(id)),
getRecorderByID: async (id: string): Promise<EndpointRecorder> =>
await request(getSDKEndpoint.getRecorderByIDEndpoint(id)),
getAllPaths: async (): Promise<EndpointAllPaths> =>

View File

@ -5,3 +5,28 @@ export type Attribute = {
values: { name: string; href?: string | undefined; lang?: string | undefined }[];
withBorder?: boolean | undefined;
};
export const getFileIcon = (mimeType: string): string => {
const firstPart = mimeType.split("/")[0];
switch (firstPart) {
case "video":
return "material-symbols:video-file";
case "image":
return "image-file";
case "audio":
return "material-symbols:audio-file";
}
switch (mimeType) {
case "application/zip":
return "material-symbols:folder-zip";
case "application/pdf":
return "pdf-file";
}
return "material-symbols:unknown-document";
};

View File

@ -11,6 +11,8 @@ let expiration: number | undefined = undefined;
const responseCache = new Map<string, any>();
const idsCacheMap = new Map<string, Set<string>>();
const isPrecachingEnabled = import.meta.env.ENABLE_PRECACHING === "true";
export const payload = getPayloadSDK({
apiURL: import.meta.env.PAYLOAD_API_URL,
email: import.meta.env.PAYLOAD_USER,
@ -36,7 +38,7 @@ export const payload = getPayloadSDK({
const cachedResponse = responseCache.get(url);
if (cachedResponse) {
console.log("[ResponseCaching] Retrieved cache response for", url);
return cachedResponse;
return structuredClone(cachedResponse);
}
},
set: (url, response) => {
@ -118,6 +120,11 @@ export const refreshWebsiteConfig = async () => {
let payloadInitialized = false;
export const initPayload = async () => {
if (!payloadInitialized) {
if (!isPrecachingEnabled) {
payloadInitialized = true;
return;
}
const result = await payload.getAllPaths();
for (const slug of result.pages) {
@ -182,6 +189,14 @@ export const initPayload = async () => {
}
}
for (const id of result.files) {
try {
await payload.getFileByID(id);
} catch (e) {
console.warn("[Precaching] Couldn't precache file", id, e);
}
}
try {
await payload.getChronologyEvents();
} catch (e) {