Added collectibles

This commit is contained in:
DrMint 2024-03-03 18:18:42 +01:00
parent 6cb23eb82e
commit e9fbb0a3b7
10 changed files with 835 additions and 20 deletions

View File

@ -0,0 +1,701 @@
import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types";
import { Where } from "payload/types";
import {
CollectibleBindingTypes,
CollectibleNature,
CollectiblePageOrders,
CollectionGroups,
Collections,
PageType,
} from "../../constants";
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
import { componentField } from "../../fields/componentField/componentField";
import { imageField } from "../../fields/imageField/imageField";
import { rowField } from "../../fields/rowField/rowField";
import { slugField } from "../../fields/slugField/slugField";
import { tagsField } from "../../fields/tagsField/tagsField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
import { beforeDuplicatePiping } from "../../hooks/beforeDuplicatePiping";
import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish";
import { createEditor } from "../../utils/editor";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
import { RowLabel } from "./components/RowLabel";
const fields = {
status: "_status",
slug: "slug",
thumbnail: "thumbnail",
nature: "nature",
tags: "tags",
languages: "languages",
translations: "translations",
pretitle: "pretitle",
title: "title",
subtitle: "subtitle",
description: "description",
price: "price",
priceAmount: "amount",
priceCurrency: "currency",
size: "size",
sizeWidth: "width",
sizeHeight: "height",
sizeThickness: "thickness",
weight: "weight",
weightAmount: "amount",
urls: "urls",
urlsUrl: "url",
scans: "scans",
scansScanners: "scanners",
scansCleaners: "cleaners",
scansTypesetters: "typesetters",
scansCover: "cover",
scansCoverFlapFront: "flapFront",
scansCoverFront: "front",
scansCoverSpine: "spine",
scansCoverBack: "back",
scansCoverFlapBack: "flapBack",
scansCoverInsideFlapFront: "insideFlapFront",
scansCoverInsideFront: "insideFront",
scansCoverInsideBack: "insideBack",
scansCoverInsideFlapBack: "insideFlapBack",
scansDustjacket: "dustjacket",
scansDustjacketFlapFront: "flapFront",
scansDustjacketFront: "front",
scansDustjacketSpine: "spine",
scansDustjacketBack: "back",
scansDustjacketFlapBack: "flapBack",
scansDustjacketInsideFlapFront: "insideFlapFront",
scansDustjacketInsideFront: "insideFront",
scansDustjacketInsideSpine: "insideSpine",
scansDustjacketInsideBack: "insideBack",
scansDustjacketInsideFlapBack: "insideFlapBack",
scansObi: "obi",
scansObiFlapFront: "flapFront",
scansObiFront: "front",
scansObiSpine: "spine",
scansObiBack: "back",
scansObiFlapBack: "flapBack",
scansObiInsideFlapFront: "insideFlapFront",
scansObiInsideFront: "insideFront",
scansObiInsideSpine: "insideSpine",
scansObiInsideBack: "insideBack",
scansObiInsideFlapBack: "insideFlapBack",
scansPages: "pages",
scansPagesPage: "page",
scansPagesImage: "image",
gallery: "gallery",
galleryImage: "image",
releaseDate: "releaseDate",
parentItems: "parentItems",
subitems: "subitems",
folders: "folders",
contents: "contents",
contentsContent: "content",
pageInfo: "pageInfo",
pageInfoBindingType: "bindingType",
pageInfoPageCount: "pageCount",
pageInfoPageOrder: "pageOrder",
} as const satisfies Record<string, string>;
export const Collectibles = buildVersionedCollectionConfig({
slug: Collections.Collectibles,
labels: {
singular: "Collectible",
plural: "Collectibles",
},
defaultSort: fields.slug,
admin: {
useAsTitle: fields.slug,
defaultColumns: [fields.slug, fields.status],
group: CollectionGroups.Collections,
hooks: {
beforeDuplicate: beforeDuplicatePiping([
beforeDuplicateUnpublish,
beforeDuplicateAddCopyTo(fields.slug),
]),
},
},
fields: [
{
type: "tabs",
tabs: [
{
label: "Overview",
fields: [
rowField([
slugField({ name: fields.slug }),
imageField({
name: fields.thumbnail,
relationTo: Collections.Images,
}),
]),
rowField([
{
name: fields.nature,
type: "radio",
required: true,
defaultValue: CollectibleNature.Physical,
options: Object.entries(CollectibleNature).map(([value, label]) => ({
label,
value,
})),
},
{
name: fields.languages,
type: "relationship",
relationTo: Collections.Languages,
hasMany: true,
admin: {
allowCreate: false,
description:
"Languages used by this collectible: language(s) available for a game,\
in a video, a book is written in... Leave empty if inapplicable",
},
},
]),
tagsField({ name: fields.tags }),
translatedFields({
name: fields.translations,
admin: { useAsTitle: fields.title },
required: true,
minRows: 1,
fields: [
rowField([
{ name: fields.pretitle, type: "text" },
{ name: fields.title, type: "text", required: true },
{ name: fields.subtitle, type: "text" },
]),
{
name: fields.description,
type: "richText",
editor: createEditor({ inlines: true, lists: true, links: true }),
},
],
}),
],
},
{
label: "Images",
fields: [
{
name: fields.gallery,
type: "array",
admin: {
description:
"Additional images of the item (unboxing, on shelf, promotional images...)",
},
labels: { singular: "Image", plural: "Images" },
fields: [
imageField({
name: fields.galleryImage,
relationTo: Collections.LibraryItemsGallery,
}),
],
},
componentField({
name: fields.scans,
fields: [
rowField([
{
name: fields.scansScanners,
type: "relationship",
relationTo: Collections.Recorders,
hasMany: true,
required: true,
},
{
name: fields.scansCleaners,
type: "relationship",
relationTo: Collections.Recorders,
hasMany: true,
required: true,
},
{
name: fields.scansTypesetters,
type: "relationship",
relationTo: Collections.Recorders,
hasMany: true,
},
]),
componentField({
name: fields.scansCover,
fields: [
rowField([
imageField({
name: fields.scansCoverFront,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansCoverSpine,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansCoverBack,
relationTo: Collections.LibraryItemsScans,
}),
]),
rowField([
imageField({
name: fields.scansCoverInsideFront,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansCoverInsideBack,
relationTo: Collections.LibraryItemsScans,
}),
]),
rowField([
imageField({
name: fields.scansCoverFlapFront,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansCoverFlapBack,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansCoverInsideFlapFront,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansCoverInsideFlapBack,
relationTo: Collections.LibraryItemsScans,
}),
]),
],
}),
componentField({
name: fields.scansDustjacket,
label: "Dust Jacket",
admin: {
description:
"The dust jacket of a book is the detachable outer cover with folded \
flaps that hold it to the front and back book covers",
},
fields: [
rowField([
imageField({
name: fields.scansDustjacketFront,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansDustjacketSpine,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansDustjacketBack,
relationTo: Collections.LibraryItemsScans,
}),
]),
rowField([
imageField({
name: fields.scansDustjacketInsideFront,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansDustjacketInsideSpine,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansDustjacketInsideBack,
relationTo: Collections.LibraryItemsScans,
}),
]),
rowField([
imageField({
name: fields.scansDustjacketFlapFront,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansDustjacketFlapBack,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansDustjacketInsideFlapFront,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansDustjacketInsideFlapBack,
relationTo: Collections.LibraryItemsScans,
}),
]),
],
}),
componentField({
name: fields.scansObi,
label: "Obi",
admin: {
description:
"An obi is a strip of paper looped around a book or other product. \
it typically add marketing claims, or other relevant information about the product.",
},
fields: [
rowField([
imageField({
name: fields.scansObiFront,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansObiSpine,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansObiBack,
relationTo: Collections.LibraryItemsScans,
}),
]),
rowField([
imageField({
name: fields.scansObiInsideFront,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansObiInsideSpine,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansObiInsideBack,
relationTo: Collections.LibraryItemsScans,
}),
]),
rowField([
imageField({
name: fields.scansObiFlapFront,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansObiFlapBack,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansObiInsideFlapFront,
relationTo: Collections.LibraryItemsScans,
}),
imageField({
name: fields.scansObiInsideFlapBack,
relationTo: Collections.LibraryItemsScans,
}),
]),
],
}),
{
name: fields.scansPages,
type: "array",
admin: {
initCollapsed: true,
description:
"Make sure the page number corresponds to the page number written on \
the scan. You can use negative page numbers if necessary.",
components: {
RowLabel: ({ data }: RowLabelArgs) => RowLabel(data),
},
},
fields: [
rowField([
{
name: fields.scansPagesPage,
type: "number",
required: true,
},
imageField({
name: fields.scansPagesImage,
relationTo: Collections.LibraryItemsScans,
required: true,
}),
]),
],
},
],
}),
],
},
{
label: "Details",
fields: [
{
name: fields.urls,
label: "URLs",
type: "array",
admin: {
description: "Links to official websites where to get/buy the item.",
},
fields: [{ name: fields.urlsUrl, type: "text", required: true }],
},
{
name: fields.releaseDate,
type: "date",
admin: {
date: { pickerAppearance: "dayOnly", displayFormat: "yyyy-MM-dd" },
},
},
componentField({
name: fields.price,
admin: {
description:
"Add pricing information about this collectible. Leave this unchecked if the collectible is not for sale or set the amount to 0 if it is free.",
},
fields: [
rowField([
{
name: fields.priceAmount,
type: "number",
required: true,
min: 0,
},
{
name: fields.priceCurrency,
type: "relationship",
relationTo: Collections.Currencies,
required: true,
admin: { allowCreate: false },
},
]),
],
}),
componentField({
name: fields.size,
admin: {
condition: ({ nature }) => nature === CollectibleNature.Physical,
description: "Add physical size information about this collectible",
},
fields: [
rowField([
{
name: fields.sizeWidth,
type: "number",
required: true,
admin: { step: 1, description: "in mm." },
},
{
name: fields.sizeHeight,
type: "number",
required: true,
admin: { step: 1, description: "in mm." },
},
{
name: fields.sizeThickness,
type: "number",
admin: { step: 1, description: "in mm." },
},
]),
],
}),
componentField({
name: fields.weight,
admin: {
condition: ({ nature }) => nature === CollectibleNature.Physical,
description: "Add physical weight information about this collectible",
},
fields: [
{
name: fields.weightAmount,
type: "number",
required: true,
admin: { step: 1, description: "in g." },
min: 0,
},
],
}),
componentField({
name: fields.pageInfo,
label: "Page information",
admin: {
description:
"If the collectible is book-like (has pages),\
use this to add additional information about its format.",
},
fields: [
{
name: fields.pageInfoPageCount,
type: "number",
required: true,
min: 1,
},
rowField([
{
name: fields.pageInfoBindingType,
type: "radio",
options: Object.entries(CollectibleBindingTypes).map(([value, label]) => ({
label,
value,
})),
admin: {
condition: ({ nature }) => nature === CollectibleNature.Physical,
},
},
{
name: fields.pageInfoPageOrder,
type: "radio",
options: Object.entries(CollectiblePageOrders).map(([value, label]) => ({
label,
value,
})),
},
]),
],
}),
],
},
{
label: "Contents",
fields: [
rowField([
backPropagationField({
name: fields.folders,
relationTo: Collections.Folders,
hasMany: true,
where: ({ id }) => ({
and: [
{ "files.value": { equals: id } },
{ "files.relationTo": { equals: Collections.Collectibles } },
] as Where[],
}),
admin: {
description: `You can go to the "Folders" collection to include this collectible in a folder.`,
},
}),
backPropagationField({
name: fields.parentItems,
relationTo: Collections.Collectibles,
hasMany: true,
where: ({ id }) => ({ [fields.subitems]: { equals: id } }),
}),
{
name: fields.subitems,
type: "relationship",
hasMany: true,
relationTo: Collections.Collectibles,
},
]),
{
name: fields.contents,
type: "array",
fields: [
{
name: fields.contentsContent,
type: "relationship",
// TODO: Add audio and video files
relationTo: [Collections.Pages, Collections.GenericContents],
admin: {
allowCreate: false,
},
required: true,
filterOptions: ({ relationTo }) => {
switch (relationTo) {
case Collections.Pages:
return { type: { equals: PageType.Content } };
default:
return true;
}
},
},
{
name: "range",
type: "blocks",
maxRows: 1,
admin: { className: "no-label" },
blocks: [
{
slug: "pageRange",
labels: { singular: "Page Range", plural: "Page Ranges" },
fields: [
rowField([
{
name: "start",
type: "number",
required: true,
admin: {
description:
"Make sure the page range corresponds to the pages as written\
on the collectible. You can use negative page numbers if necessary.",
},
},
{ name: "end", type: "number", required: true },
]),
],
},
{
slug: "timeRange",
labels: { singular: "Time Range", plural: "Time Ranges" },
fields: [
rowField([
{
name: "start",
type: "text",
required: true,
defaultValue: "00:00:00",
validate: (value) =>
/\d{2}:\d{2}:\d{2}/g.test(value)
? true
: "The format should be hh:mm:ss\
(e.g: 01:23:45 for 1 hour, 23 minutes, and 45 seconds)",
admin: {
description:
"hh:mm:ss (e.g: 01:23:45 for 1 hour, 23 minutes, and 45 seconds)",
},
},
{
name: "end",
type: "text",
required: true,
defaultValue: "00:00:00",
validate: (value) =>
/\d{2}:\d{2}:\d{2}/g.test(value)
? true
: "The format should be hh:mm:ss\
(e.g: 01:23:45 for 1 hour, 23 minutes, and 45 seconds)",
admin: {
description:
"hh:mm:ss (e.g: 01:23:45 for 1 hour, 23 minutes, and 45 seconds)",
},
},
]),
],
},
{
slug: "other",
labels: { singular: "Other", plural: "Others" },
fields: [
translatedFields({
admin: { className: "no-label" },
name: "translations",
fields: [
{
name: "note",
type: "richText",
editor: createEditor({ inlines: true, lists: true, links: true }),
},
],
}),
],
},
],
},
],
},
],
},
],
},
],
});

View File

@ -0,0 +1,26 @@
import React from "react";
import styled from "styled-components";
import { isDefined } from "../../../utils/asserts";
interface Props {
page?: number;
image?: string;
}
const Container = styled.div`
display: flex;
place-items: center;
gap: 10px;
`;
const Title = styled.div`
font-weight: 600;
font-size: 1.2rem;
`;
export const RowLabel = ({ page, image }: Props): JSX.Element => (
<Container>
{isDefined(page) && <div className="pill pill--style-white">{`Page ${page}`}</div>}
{isDefined(image) && <Title>{image}</Title>}
</Container>
);

View File

@ -83,7 +83,7 @@ export const Folders = buildCollectionConfig({
{
type: "relationship",
name: fields.files,
relationTo: [Collections.LibraryItems, Collections.Pages],
relationTo: [Collections.Collectibles, Collections.Pages],
hasMany: true,
},
],

View File

@ -0,0 +1,70 @@
import { CollectionConfig } from "payload/types";
import { QuickFilters } from "../../components/QuickFilters";
import { CollectionGroups, Collections, LanguageCodes } from "../../constants";
import { rowField } from "../../fields/rowField/rowField";
import { translatedFields } from "../../fields/translatedFields/translatedFields";
import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo";
import { buildCollectionConfig } from "../../utils/collectionConfig";
const fields = {
name: "name",
translations: "translations",
translationsName: "name",
} as const satisfies Record<string, string>;
export const GenericContents: CollectionConfig = buildCollectionConfig({
slug: Collections.GenericContents,
labels: {
singular: "Generic Content",
plural: "Generic Contents",
},
defaultSort: fields.name,
admin: {
useAsTitle: fields.name,
defaultColumns: [fields.name, fields.translations],
group: CollectionGroups.Meta,
hooks: {
beforeDuplicate: beforeDuplicateAddCopyTo(fields.name),
},
components: {
BeforeListTable: [
() =>
QuickFilters({
slug: Collections.GenericContents,
filterGroups: [
Object.entries(LanguageCodes).map(([key, value]) => ({
label: `${value}`,
filter: { where: { "translations.language": { not_equals: key } } },
})),
],
}),
],
},
},
fields: [
{
name: fields.name,
type: "text",
required: true,
unique: true,
},
translatedFields({
name: fields.translations,
minRows: 1,
required: true,
interfaceName: "CategoryTranslations",
admin: {
useAsTitle: fields.translationsName,
},
fields: [
rowField([
{
name: fields.translationsName,
type: "textarea",
required: true,
},
]),
],
}),
],
});

View File

@ -1,11 +1,11 @@
import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types";
import {
CollectibleBindingTypes,
CollectiblePageOrders,
CollectionGroups,
Collections,
FileTypes,
KeysTypes,
LibraryItemsTextualBindingTypes,
LibraryItemsTextualPageOrders,
LibraryItemsTypes,
PageType,
} from "../../constants";
@ -493,7 +493,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
{
name: fields.textualBindingType,
type: "radio",
options: Object.entries(LibraryItemsTextualBindingTypes).map(
options: Object.entries(CollectibleBindingTypes).map(
([value, label]) => ({
label,
value,
@ -507,7 +507,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
{
name: fields.textualPageOrder,
type: "radio",
options: Object.entries(LibraryItemsTextualPageOrders).map(
options: Object.entries(CollectiblePageOrders).map(
([value, label]) => ({
label,
value,

View File

@ -49,6 +49,8 @@ export const Tags: CollectionConfig = buildCollectionConfig({
translatedFields({
name: fields.translations,
admin: { useAsTitle: fields.translationsName },
required: true,
minRows: 1,
fields: [{ name: fields.translationsName, type: "text", required: true }],
}),
{ name: fields.group, type: "relationship", required: true, relationTo: Collections.TagsGroups },

View File

@ -28,6 +28,8 @@ export const TagsGroups: CollectionConfig = buildCollectionConfig({
translatedFields({
name: fields.translations,
admin: { useAsTitle: fields.translationsName },
required: true,
minRows: 1,
fields: [{ name: fields.translationsName, type: "text", required: true }],
}),
],

View File

@ -34,7 +34,9 @@ export enum Collections {
Tags = "tags",
TagsGroups = "tags-groups",
Images = "images",
Wordings = "wordings"
Wordings = "wordings",
Collectibles = "collectibles",
GenericContents = "generic-contents",
}
export enum CollectionGroups {
@ -80,12 +82,12 @@ export enum LibraryItemsTypes {
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",
}
@ -109,7 +111,7 @@ export enum VideoSources {
export enum PageType {
Content = "Content",
Article = "Article",
Post = "Post",
Generic = "Generic",
}
@ -120,6 +122,17 @@ export enum SpacerSizes {
XLarge = "Extra Large",
}
export enum CollectibleNature {
Physical = "Physical",
Digital = "Digital",
}
export enum CollectibleContentType {
None = "None",
Indexes = "Index-based",
Pages = "Page-based",
}
/* RICH TEXT */
export type RichTextContent = {

View File

@ -4,10 +4,12 @@ import path from "path";
import { buildConfig } from "payload/config";
import { ChronologyEras } from "./collections/ChronologyEras/ChronologyEras";
import { ChronologyItems } from "./collections/ChronologyItems/ChronologyItems";
import { Collectibles } from "./collections/Collectibles/Collectibles";
import { Currencies } from "./collections/Currencies/Currencies";
import { Files } from "./collections/Files/Files";
import { Folders } from "./collections/Folders/Folders";
import { FoldersThumbnails } from "./collections/FoldersThumbnails/FoldersThumbnails";
import { GenericContents } from "./collections/GenericContents/GenericContents";
import { Images } from "./collections/Images/Images";
import { Keys } from "./collections/Keys/Keys";
import { Languages } from "./collections/Languages/Languages";
@ -71,7 +73,9 @@ export default buildConfig({
Tags,
TagsGroups,
Images,
Wordings
Wordings,
Collectibles,
GenericContents,
],
db: mongooseAdapter({
url: process.env.MONGODB_URI ?? "mongodb://mongo:27017/payload",

View File

@ -64,25 +64,22 @@ html[data-theme="light"] {
}
}
// Hide header on blocks with classname "no-label"
// Hide header on fields with classname "no-label"
.field-type.no-label > header {
display: none;
}
.lexical-block__block-pill-transcriptBlock + .section-title {
display: none;
}
.lexical-block__block-pill-transcriptBlock + .section-title,
.lexical-block__block-pill-spacerBlock + .section-title {
display: none;
}
.blocks-field__block-pill-cueBlock + .section-title {
display: none;
}
.blocks-field__block-pill-spacerBlock + .section-title {
.blocks-field__block-pill-cueBlock + .section-title,
.blocks-field__block-pill-spacerBlock + .section-title,
.blocks-field__block-pill-pageRange + .section-title,
.blocks-field__block-pill-timeRange + .section-title,
.blocks-field__block-pill-other + .section-title {
display: none;
}