From cbd0251ad50b95dc4807ca8d2512a462b9e3423e Mon Sep 17 00:00:00 2001 From: DrMint <29893320+DrMint@users.noreply.github.com> Date: Mon, 30 Oct 2023 00:10:11 +0100 Subject: [PATCH] Spent the weekend improving stuff --- .gitignore | 4 +- bun.lockb | Bin 541699 -> 541699 bytes docker-compose.yml | 1 + package-lock.json | 37 +- package.json | 6 +- .../ChronologyEras/ChronologyEras.ts | 36 +- .../ChronologyItems/ChronologyItems.ts | 47 +- src/collections/Contents/Contents.ts | 219 ++--- .../Contents/endpoints/importFromStrapi.ts | 92 +- .../endpoints/importRelationsFromStrapi.ts | 38 + .../ContentsFolders/ContentsFolders.ts | 34 +- .../endpoints/importFromStrapi.ts | 24 +- src/collections/Keys/Keys.ts | 34 +- .../LibraryFolders/LibraryFolders.ts | 42 +- src/collections/LibraryItems/LibraryItems.ts | 881 +++++++++--------- src/collections/Posts/Posts.ts | 157 ++-- src/collections/Recorders/Recorders.ts | 31 +- .../Recorders/endpoints/importFromStrapi.ts | 2 +- src/collections/Videos/Videos.ts | 54 +- .../VideosChannels/VideosChannels.ts | 12 +- src/collections/Weapons/Weapons.ts | 144 ++- src/endpoints/createStrapiImportEndpoint.ts | 47 +- .../backPropagationField.ts | 31 +- src/fields/componentField/componentField.ts | 55 ++ src/fields/keysField/keysField.ts | 5 + .../optionalGroupField/optionalGroupField.ts | 14 - src/fields/rowField/rowField.ts | 9 + .../translatedFields/translatedFields.ts | 7 +- src/styles.scss | 72 +- src/types/collections.ts | 148 +-- src/utils/localApi.ts | 22 +- src/utils/string.ts | 2 +- src/utils/versionedCollectionConfig.ts | 2 +- 33 files changed, 1189 insertions(+), 1120 deletions(-) create mode 100644 src/collections/Contents/endpoints/importRelationsFromStrapi.ts create mode 100644 src/fields/componentField/componentField.ts delete mode 100644 src/fields/optionalGroupField/optionalGroupField.ts create mode 100644 src/fields/rowField/rowField.ts diff --git a/.gitignore b/.gitignore index 5efb643..9c1b124 100644 --- a/.gitignore +++ b/.gitignore @@ -167,4 +167,6 @@ dist # Ignore Data mongo/ -uploads/ \ No newline at end of file +uploads/ +build +core \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index c39386064616cc869b15cc2d62e4f7c6aaa52826..12d04d9484fb740a8163ad97629a4b204c52d8e6 100755 GIT binary patch delta 165 zcmV;W09yZph#`ZBA&@R0XLuX2Mfsc%&T2sT=rJy~?^pgupsCG#A{qGdj3D(Ru}(Tz zlZag~gSc3?xL5(@oItEUXK_*d3RC+^m_y2G>$O|-7z_t{AXm56^}!?o7hXfh`!7?P z!_?sev@_rlM9>16@<Qx42jV<(xn>e0^h`?vf|vWG)g;xWC_lvEf+EljS40w>Rv-Sq?qgaI;#<#`3S U<#`4-GyyV)9j*qq9j*uFl_lCw5C8xG diff --git a/docker-compose.yml b/docker-compose.yml index d128255..bdd27ce 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: - node_modules:/home/node/app/node_modules working_dir: /home/node/app/ command: sh -c "npm install && npm run generate:types && npm run dev" + # command: sh -c "npm install && npm run generate:types && npm run build:payload && npm run serve" depends_on: - mongo environment: diff --git a/package-lock.json b/package-lock.json index a4472c3..673154c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,11 +13,10 @@ "@payloadcms/bundler-webpack": "^1.0.4", "@payloadcms/db-mongodb": "^1.0.4", "@payloadcms/richtext-lexical": "^0.1.15", - "clean-deep": "^3.4.0", "cross-env": "^7.0.3", "language-tags": "^1.0.9", "luxon": "^3.4.3", - "payload": "^2.0.12", + "payload": "^2.0.13", "styled-components": "^6.1.0" }, "devDependencies": { @@ -5413,19 +5412,6 @@ "node": ">= 10.0" } }, - "node_modules/clean-deep": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/clean-deep/-/clean-deep-3.4.0.tgz", - "integrity": "sha512-Lo78NV5ItJL/jl+B5w0BycAisaieJGXK1qYi/9m4SjR8zbqmrUtO7Yhro40wEShGmmxs/aJLI/A+jNhdkXK8mw==", - "dependencies": { - "lodash.isempty": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.transform": "^4.6.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -9321,26 +9307,11 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, - "node_modules/lodash.isempty": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" }, - "node_modules/lodash.transform": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz", - "integrity": "sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ==" - }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -11140,9 +11111,9 @@ "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, "node_modules/payload": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/payload/-/payload-2.0.12.tgz", - "integrity": "sha512-M3x9Y53ukiflZC4STri/34Gx4VjMk+UjuLc38dQRiCLPFGXEuqPfFFdt+c1Lh4ZeTQmMlWzzJcRMQ00tK+kWYg==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/payload/-/payload-2.0.13.tgz", + "integrity": "sha512-rD9ncVH8ClP7SphDymnrtVv0GAwHeyBXt9b1wSQBF15Dx/svU5rD1OEDtDPgEUTQApnySBVsB4NDGM1xO32YjA==", "dependencies": { "@date-io/date-fns": "2.16.0", "@dnd-kit/core": "6.0.8", diff --git a/package.json b/package.json index 8f145f7..4b211e4 100644 --- a/package.json +++ b/package.json @@ -19,18 +19,18 @@ "precommit": "npm run generate:types && npm run prettier && npm run unused-exports && npm run tsc", "upgrade": "ncu", "clean": "sudo rm -r uploads mongo", - "start": "sudo docker compose up" + "start": "sudo docker compose up", + "stop": "sudo docker compose down" }, "dependencies": { "@fontsource/vollkorn": "^5.0.17", "@payloadcms/bundler-webpack": "^1.0.4", "@payloadcms/db-mongodb": "^1.0.4", "@payloadcms/richtext-lexical": "^0.1.15", - "clean-deep": "^3.4.0", "cross-env": "^7.0.3", "language-tags": "^1.0.9", "luxon": "^3.4.3", - "payload": "^2.0.12", + "payload": "^2.0.13", "styled-components": "^6.1.0" }, "devDependencies": { diff --git a/src/collections/ChronologyEras/ChronologyEras.ts b/src/collections/ChronologyEras/ChronologyEras.ts index e03d1f4..86897f0 100644 --- a/src/collections/ChronologyEras/ChronologyEras.ts +++ b/src/collections/ChronologyEras/ChronologyEras.ts @@ -2,6 +2,7 @@ import { CollectionConfig } from "payload/types"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { CollectionGroups, Collections } from "../../constants"; import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; +import { rowField } from "../../fields/rowField/rowField"; import { slugField } from "../../fields/slugField/slugField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { buildCollectionConfig } from "../../utils/collectionConfig"; @@ -43,25 +44,22 @@ export const ChronologyEras: CollectionConfig = buildCollectionConfig({ endpoints: [importFromStrapi, getAllEndpoint], fields: [ slugField({ name: fields.slug }), - { - type: "row", - fields: [ - { - name: fields.startingYear, - type: "number", - min: 0, - required: true, - admin: { width: "0%", description: "The year the era started (year included)" }, - }, - { - name: fields.endingYear, - type: "number", - min: 0, - required: true, - admin: { width: "0%", description: "The year the era ended (year included)" }, - }, - ], - }, + rowField([ + { + name: fields.startingYear, + type: "number", + min: 0, + required: true, + admin: { description: "The year the era started (year included)" }, + }, + { + name: fields.endingYear, + type: "number", + min: 0, + required: true, + admin: { description: "The year the era ended (year included)" }, + }, + ]), translatedFields({ name: fields.translations, admin: { useAsTitle: fields.translationsTitle }, diff --git a/src/collections/ChronologyItems/ChronologyItems.ts b/src/collections/ChronologyItems/ChronologyItems.ts index 671b052..2375e89 100644 --- a/src/collections/ChronologyItems/ChronologyItems.ts +++ b/src/collections/ChronologyItems/ChronologyItems.ts @@ -5,6 +5,7 @@ import { publishStatusFilters, } from "../../components/QuickFilters"; import { CollectionGroups, Collections } from "../../constants"; +import { rowField } from "../../fields/rowField/rowField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { createEditor } from "../../utils/editor"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; @@ -69,32 +70,26 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig( name: fields.date, validate: validateDate, fields: [ - { - type: "row", - fields: [ - { - name: fields.dateYear, - type: "number", - required: true, - min: 0, - admin: { width: "0%" }, - }, - { - name: fields.dateMonth, - type: "number", - min: 1, - max: 12, - admin: { width: "0%" }, - }, - { - name: fields.dateDay, - type: "number", - min: 1, - max: 31, - admin: { width: "0%" }, - }, - ], - }, + rowField([ + { + name: fields.dateYear, + type: "number", + required: true, + min: 0, + }, + { + name: fields.dateMonth, + type: "number", + min: 1, + max: 12, + }, + { + name: fields.dateDay, + type: "number", + min: 1, + max: 31, + }, + ]), ], }, { diff --git a/src/collections/Contents/Contents.ts b/src/collections/Contents/Contents.ts index 9e1c7dc..0cedf56 100644 --- a/src/collections/Contents/Contents.ts +++ b/src/collections/Contents/Contents.ts @@ -1,9 +1,11 @@ import { sectionBlock } from "../../blocks/sectionBlock"; import { transcriptBlock } from "../../blocks/transcriptBlock"; import { CollectionGroups, Collections, FileTypes, KeysTypes } from "../../constants"; +import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; import { fileField } from "../../fields/fileField/fileField"; import { imageField } from "../../fields/imageField/imageField"; import { keysField } from "../../fields/keysField/keysField"; +import { rowField } from "../../fields/rowField/rowField"; import { slugField } from "../../fields/slugField/slugField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; @@ -13,6 +15,7 @@ import { isDefined } from "../../utils/asserts"; import { createEditor } from "../../utils/editor"; import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig"; import { importFromStrapi } from "./endpoints/importFromStrapi"; +import { importRelationsFromStrapi } from "./endpoints/importRelationsFromStrapi"; const fields = { slug: "slug", @@ -35,6 +38,10 @@ const fields = { audioNotes: "videoNotes", status: "status", updatedBy: "updatedBy", + previousContents: "previousContents", + nextContents: "nextContents", + folders: "folders", + libraryItems: "libraryItems", } as const satisfies Record; export const Contents = buildVersionedCollectionConfig({ @@ -63,51 +70,44 @@ export const Contents = buildVersionedCollectionConfig({ beforeDuplicateAddCopyTo(fields.slug), ]), }, - preview: (doc) => `https://accords-library.com/contents/${doc.slug}`, }, - endpoints: [importFromStrapi], + endpoints: [importFromStrapi, importRelationsFromStrapi], fields: [ - { - type: "row", - fields: [ - slugField({ name: fields.slug, admin: { width: "0%" } }), - imageField({ - name: fields.thumbnail, - relationTo: Collections.ContentsThumbnails, - admin: { width: "0%" }, - }), - ], - }, - { - type: "row", - fields: [ - keysField({ - name: fields.categories, - relationTo: KeysTypes.Categories, - hasMany: true, - admin: { allowCreate: false, width: "0%" }, - }), - keysField({ - name: fields.type, - relationTo: KeysTypes.Contents, - admin: { allowCreate: false, width: "0%" }, - }), - ], - }, + rowField([ + slugField({ name: fields.slug }), + imageField({ + name: fields.thumbnail, + relationTo: Collections.ContentsThumbnails, + }), + ]), + rowField([ + keysField({ + name: fields.categories, + relationTo: KeysTypes.Categories, + hasMany: true, + }), + keysField({ + name: fields.type, + relationTo: KeysTypes.Contents, + }), + backPropagationField({ + name: fields.libraryItems, + hasMany: true, + relationTo: Collections.LibraryItems, + where: ({ id }) => ({ "contents.content": { equals: id } }), + }), + ]), translatedFields({ name: fields.translations, admin: { useAsTitle: fields.title, hasSourceLanguage: true }, required: true, minRows: 1, fields: [ - { - type: "row", - fields: [ - { name: fields.pretitle, type: "text" }, - { name: fields.title, type: "text", required: true }, - { name: fields.subtitle, type: "text" }, - ], - }, + rowField([ + { name: fields.pretitle, type: "text" }, + { name: fields.title, type: "text", required: true }, + { name: fields.subtitle, type: "text" }, + ]), { name: fields.summary, type: "richText", @@ -137,43 +137,37 @@ export const Contents = buildVersionedCollectionConfig({ alignment: true, }), }, - { - type: "row", - fields: [ - { - name: fields.textTranscribers, - label: "Transcribers", - type: "relationship", - relationTo: Collections.Recorders, - hasMany: true, - admin: { - condition: (_, siblingData) => - siblingData.language === siblingData.sourceLanguage, - width: "0%", - }, + rowField([ + { + name: fields.textTranscribers, + label: "Transcribers", + type: "relationship", + relationTo: Collections.Recorders, + hasMany: true, + admin: { + condition: (_, siblingData) => + siblingData.language === siblingData.sourceLanguage, }, - { - name: fields.textTranslators, - label: "Translators", - type: "relationship", - relationTo: Collections.Recorders, - hasMany: true, - admin: { - condition: (_, siblingData) => - siblingData.language !== siblingData.sourceLanguage, - width: "0%", - }, + }, + { + name: fields.textTranslators, + label: "Translators", + type: "relationship", + relationTo: Collections.Recorders, + hasMany: true, + admin: { + condition: (_, siblingData) => + siblingData.language !== siblingData.sourceLanguage, }, - { - name: fields.textProofreaders, - label: "Proofreaders", - type: "relationship", - relationTo: Collections.Recorders, - hasMany: true, - admin: { width: "0%" }, - }, - ], - }, + }, + { + name: fields.textProofreaders, + label: "Proofreaders", + type: "relationship", + relationTo: Collections.Recorders, + hasMany: true, + }, + ]), { name: fields.textNotes, label: "Notes", @@ -185,50 +179,63 @@ export const Contents = buildVersionedCollectionConfig({ { label: "Video", fields: [ - { - type: "row", - fields: [ - fileField({ - name: fields.video, - relationTo: FileTypes.ContentVideo, - admin: { width: "0%" }, - }), - { - name: fields.videoNotes, - label: "Notes", - type: "richText", - editor: createEditor({ inlines: true, lists: true, links: true }), - admin: { width: "0%" }, - }, - ], - }, + rowField([ + fileField({ + name: fields.video, + relationTo: FileTypes.ContentVideo, + }), + { + name: fields.videoNotes, + label: "Notes", + type: "richText", + editor: createEditor({ inlines: true, lists: true, links: true }), + }, + ]), ], }, { label: "Audio", fields: [ - { - type: "row", - fields: [ - fileField({ - name: fields.audio, - relationTo: FileTypes.ContentAudio, - admin: { width: "0%" }, - }), - { - name: fields.audioNotes, - label: "Notes", - type: "richText", - editor: createEditor({ inlines: true, lists: true, links: true }), - admin: { width: "0%" }, - }, - ], - }, + rowField([ + fileField({ + name: fields.audio, + relationTo: FileTypes.ContentAudio, + }), + { + name: fields.audioNotes, + label: "Notes", + type: "richText", + editor: createEditor({ inlines: true, lists: true, links: true }), + }, + ]), ], }, ], }, ], }), + rowField([ + backPropagationField({ + name: fields.folders, + hasMany: true, + relationTo: Collections.ContentsFolders, + where: ({ id }) => ({ contents: { equals: id } }), + admin: { + description: `You can set the folder(s) from the "Contents Folders" collection`, + }, + }), + backPropagationField({ + name: fields.previousContents, + relationTo: Collections.Contents, + hasMany: true, + where: ({ id }) => ({ [fields.nextContents]: { equals: id } }), + }), + { + name: fields.nextContents, + type: "relationship", + hasMany: true, + relationTo: Collections.Contents, + }, + ]), ], }); diff --git a/src/collections/Contents/endpoints/importFromStrapi.ts b/src/collections/Contents/endpoints/importFromStrapi.ts index c81f292..9c65792 100644 --- a/src/collections/Contents/endpoints/importFromStrapi.ts +++ b/src/collections/Contents/endpoints/importFromStrapi.ts @@ -60,6 +60,52 @@ export const importFromStrapi = createStrapiImportEndpoint({ image: thumbnail, }); + const handleTranslation = async ({ + language, + title, + description, + pre_title, + subtitle, + text_set, + }: StrapiContent["translations"][number]) => { + if (isUndefined(language.data)) + throw new Error("A language is required for a content translation"); + if (isUndefined(text_set)) throw new Error("Only content with text_set are supported"); + if (isUndefined(text_set.source_language.data)) + throw new Error("A language is required for a content translation text_set"); + return { + language: language.data.attributes.code, + sourceLanguage: text_set.source_language.data.attributes.code, + title, + pretitle: pre_title, + subtitle, + summary: isNotEmpty(description) ? plainTextToLexical(description) : undefined, + textContent: plainTextToLexical(text_set.text), + textNotes: isNotEmpty(text_set.notes) ? plainTextToLexical(text_set.notes) : undefined, + textTranscribers: + text_set.transcribers.data && + (await Promise.all( + text_set.transcribers.data?.map(async (recorder) => + findRecorder(recorder.attributes.username) + ) + )), + textTranslators: + text_set.translators.data && + (await Promise.all( + text_set.translators.data?.map(async (recorder) => + findRecorder(recorder.attributes.username) + ) + )), + textProofreaders: + text_set.proofreaders.data && + (await Promise.all( + text_set.proofreaders.data?.map(async (recorder) => + findRecorder(recorder.attributes.username) + ) + )), + }; + }; + const data: MarkOptional = { slug, categories: @@ -69,51 +115,7 @@ export const importFromStrapi = createStrapiImportEndpoint({ )), type: type.data && (await findContentType(type.data?.attributes.slug)), thumbnail: thumbnailId, - translations: await Promise.all( - translations.map( - async ({ language, title, description, pre_title, subtitle, text_set }) => { - if (isUndefined(language.data)) - throw new Error("A language is required for a content translation"); - if (isUndefined(text_set)) - throw new Error("Only content with text_set are supported"); - if (isUndefined(text_set.source_language.data)) - throw new Error("A language is required for a content translation text_set"); - return { - language: language.data.attributes.code, - sourceLanguage: text_set.source_language.data.attributes.code, - title, - pretitle: pre_title, - subtitle, - summary: isNotEmpty(description) ? plainTextToLexical(description) : undefined, - textContent: plainTextToLexical(text_set.text), - textNotes: isNotEmpty(text_set.notes) - ? plainTextToLexical(text_set.notes) - : undefined, - textTranscribers: - text_set.transcribers.data && - (await Promise.all( - text_set.transcribers.data?.map(async (recorder) => - findRecorder(recorder.attributes.username) - ) - )), - textTranslators: - text_set.translators.data && - (await Promise.all( - text_set.translators.data?.map(async (recorder) => - findRecorder(recorder.attributes.username) - ) - )), - textProofreaders: - text_set.proofreaders.data && - (await Promise.all( - text_set.proofreaders.data?.map(async (recorder) => - findRecorder(recorder.attributes.username) - ) - )), - }; - } - ) - ), + translations: await Promise.all(translations.map(handleTranslation)), }; return data; }, diff --git a/src/collections/Contents/endpoints/importRelationsFromStrapi.ts b/src/collections/Contents/endpoints/importRelationsFromStrapi.ts new file mode 100644 index 0000000..7eda8e7 --- /dev/null +++ b/src/collections/Contents/endpoints/importRelationsFromStrapi.ts @@ -0,0 +1,38 @@ +import payload from "payload"; +import { Collections } from "../../../constants"; +import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint"; +import { findContent } from "../../../utils/localApi"; + +type StrapiContent = { + slug: string; + next_contents: { data: { attributes: { slug: string } }[] }; +}; + +export const importRelationsFromStrapi = createStrapiImportEndpoint({ + strapi: { + collection: "contents", + params: { + populate: ["next_contents"], + }, + }, + payload: { + path: "/strapi/related-content", + collection: Collections.Contents, + import: async ({ slug, next_contents }, user) => { + if (next_contents.data.length === 0) return; + const currentContent = await findContent(slug); + const nextContents: string[] = []; + for (const nextContent of next_contents.data) { + const result = await findContent(nextContent.attributes.slug); + nextContents.push(result); + } + + payload.update({ + collection: Collections.Contents, + id: currentContent, + data: { nextContents }, + user, + }); + }, + }, +}); diff --git a/src/collections/ContentsFolders/ContentsFolders.ts b/src/collections/ContentsFolders/ContentsFolders.ts index fde3e71..895fb70 100644 --- a/src/collections/ContentsFolders/ContentsFolders.ts +++ b/src/collections/ContentsFolders/ContentsFolders.ts @@ -1,4 +1,5 @@ import { CollectionGroups, Collections } from "../../constants"; +import { rowField } from "../../fields/rowField/rowField"; import { slugField } from "../../fields/slugField/slugField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { buildCollectionConfig } from "../../utils/collectionConfig"; @@ -37,24 +38,19 @@ export const ContentsFolders = buildCollectionConfig({ }, fields: [{ name: fields.name, type: "text", required: true }], }), - { - type: "row", - fields: [ - { - type: "relationship", - name: fields.subfolders, - relationTo: Collections.ContentsFolders, - hasMany: true, - admin: { width: "0%" }, - }, - { - type: "relationship", - name: fields.contents, - relationTo: Collections.Contents, - hasMany: true, - admin: { width: "0%" }, - }, - ], - }, + rowField([ + { + type: "relationship", + name: fields.subfolders, + relationTo: Collections.ContentsFolders, + hasMany: true, + }, + { + type: "relationship", + name: fields.contents, + relationTo: Collections.Contents, + hasMany: true, + }, + ]), ], }); diff --git a/src/collections/ContentsFolders/endpoints/importFromStrapi.ts b/src/collections/ContentsFolders/endpoints/importFromStrapi.ts index ab80017..0240982 100644 --- a/src/collections/ContentsFolders/endpoints/importFromStrapi.ts +++ b/src/collections/ContentsFolders/endpoints/importFromStrapi.ts @@ -4,6 +4,7 @@ import { Collections } from "../../../constants"; import { CollectionEndpoint } from "../../../types/payload"; import { StrapiLanguage } from "../../../types/strapi"; import { isUndefined } from "../../../utils/asserts"; +import { findContent } from "../../../utils/localApi"; type StrapiContentsFolder = { id: string; @@ -11,13 +12,14 @@ type StrapiContentsFolder = { slug: string; titles?: { title: string; language: StrapiLanguage }[]; subfolders: { data: StrapiContentsFolder[] }; - contents: { data: { id: number }[] }; + contents: { data: { attributes: { slug: string } }[] }; }; }; const getStrapiContentFolder = async (id: number): Promise => { const paramsWithPagination = QueryString.stringify({ populate: [ + "contents", "subfolders", "subfolders.contents", "subfolders.titles", @@ -72,17 +74,31 @@ export const importFromStrapi: CollectionEndpoint = { } let foldersCreated = 0; + const errors: string[] = []; const createContentFolder = async (data: StrapiContentsFolder): Promise => { + const { slug, titles } = data.attributes; + const subfolders = await Promise.all( data.attributes.subfolders.data.map(createContentFolder) ); - const { slug, titles } = data.attributes; + + const contents: string[] = []; + for (const content of data.attributes.contents.data) { + try { + const result = await findContent(content.attributes.slug); + contents.push(result); + } catch (e) { + errors.push(`Couldn't add ${content.attributes.slug} to folder ${slug}`); + } + } + const result = await payload.create({ collection: Collections.ContentsFolders, data: { slug, subfolders, + contents, translations: titles?.map(({ title, language }) => { if (isUndefined(language.data)) throw new Error("A language is required for a content folder translation"); @@ -102,6 +118,8 @@ export const importFromStrapi: CollectionEndpoint = { res.status(500).json({ message: "Something went wrong", error: e }); } - res.status(200).json({ message: `${foldersCreated} entries have been added successfully.` }); + res + .status(200) + .json({ message: `${foldersCreated} entries have been added successfully.`, errors }); }, }; diff --git a/src/collections/Keys/Keys.ts b/src/collections/Keys/Keys.ts index 5c0b0b8..d77a375 100644 --- a/src/collections/Keys/Keys.ts +++ b/src/collections/Keys/Keys.ts @@ -2,6 +2,7 @@ import payload from "payload"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { QuickFilters } from "../../components/QuickFilters"; import { CollectionGroups, Collections, KeysTypes, LanguageCodes } from "../../constants"; +import { rowField } from "../../fields/rowField/rowField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; import { Key } from "../../types/collections"; @@ -95,26 +96,21 @@ export const Keys = buildCollectionConfig({ useAsTitle: fields.translationsName, }, fields: [ - { - type: "row", - fields: [ - { - name: fields.translationsName, - type: "text", - required: true, - admin: { width: "0%" }, + rowField([ + { + name: fields.translationsName, + type: "text", + required: true, + }, + { + name: fields.translationsShort, + type: "text", + admin: { + condition: (data: Partial) => + isDefined(data.type) && keysTypesWithShort.includes(data.type), }, - { - name: fields.translationsShort, - type: "text", - admin: { - condition: (data: Partial) => - isDefined(data.type) && keysTypesWithShort.includes(data.type), - width: "0%", - }, - }, - ], - }, + }, + ]), ], }), ], diff --git a/src/collections/LibraryFolders/LibraryFolders.ts b/src/collections/LibraryFolders/LibraryFolders.ts index 263026d..e04e910 100644 --- a/src/collections/LibraryFolders/LibraryFolders.ts +++ b/src/collections/LibraryFolders/LibraryFolders.ts @@ -1,4 +1,6 @@ import { CollectionGroups, Collections } from "../../constants"; +import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; +import { rowField } from "../../fields/rowField/rowField"; import { slugField } from "../../fields/slugField/slugField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { buildCollectionConfig } from "../../utils/collectionConfig"; @@ -11,6 +13,7 @@ const fields = { description: "description", subfolders: "subfolders", items: "items", + parentFolders: "parentFolders", } as const satisfies Record; export const LibraryFolders = buildCollectionConfig({ @@ -44,24 +47,25 @@ export const LibraryFolders = buildCollectionConfig({ }, ], }), - { - type: "row", - fields: [ - { - type: "relationship", - name: fields.subfolders, - relationTo: Collections.LibraryFolders, - hasMany: true, - admin: { width: "0%" }, - }, - { - type: "relationship", - name: fields.items, - relationTo: Collections.LibraryItems, - hasMany: true, - admin: { width: "0%" }, - }, - ], - }, + rowField([ + backPropagationField({ + name: fields.parentFolders, + relationTo: Collections.LibraryFolders, + hasMany: true, + where: ({ id }) => ({ [fields.subfolders]: { equals: id } }), + }), + { + type: "relationship", + name: fields.subfolders, + relationTo: Collections.LibraryFolders, + hasMany: true, + }, + { + type: "relationship", + name: fields.items, + relationTo: Collections.LibraryItems, + hasMany: true, + }, + ]), ], }); diff --git a/src/collections/LibraryItems/LibraryItems.ts b/src/collections/LibraryItems/LibraryItems.ts index 9eae41c..f97e58e 100644 --- a/src/collections/LibraryItems/LibraryItems.ts +++ b/src/collections/LibraryItems/LibraryItems.ts @@ -8,10 +8,12 @@ import { LibraryItemsTextualPageOrders, LibraryItemsTypes, } from "../../constants"; +import { backPropagationField } from "../../fields/backPropagationField/backPropagationField"; +import { componentField } from "../../fields/componentField/componentField"; import { fileField } from "../../fields/fileField/fileField"; import { imageField } from "../../fields/imageField/imageField"; import { keysField } from "../../fields/keysField/keysField"; -import { optionalGroupField } from "../../fields/optionalGroupField/optionalGroupField"; +import { rowField } from "../../fields/rowField/rowField"; import { slugField } from "../../fields/slugField/slugField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; @@ -25,6 +27,8 @@ import { RowLabel } from "./components/RowLabel"; const fields = { status: "_status", + itemType: "itemType", + language: "language", slug: "slug", thumbnail: "thumbnail", @@ -36,10 +40,7 @@ const fields = { translations: "translations", translationsDescription: "description", - rootItem: "rootItem", - primary: "primary", digital: "digital", - downloadable: "downloadable", size: "size", width: "width", @@ -59,13 +60,11 @@ const fields = { categories: "categories", - itemType: "itemType", textual: "textual", textualSubtype: "subtype", textualBindingType: "bindingType", textualPageCount: "pageCount", textualPageOrder: "pageOrder", - textualLanguages: "languages", audio: "audio", audioSubtype: "audioSubtype", @@ -73,7 +72,20 @@ const fields = { audioTracksFile: "file", audioTracksTitle: "title", + video: "video", + videoSubtype: "subtype", + + game: "game", + gameDemo: "demo", + gamePlatform: "platform", + gameAudioLanguages: "audioLanguages", + gameSubtitleLanguages: "subtitleLanguages", + gameInterfacesLanguages: "interfacesLanguages", + scans: "scans", + scansScanners: "scanners", + scansCleaners: "cleaners", + scansTypesetters: "typesetters", scansCover: "cover", scansCoverFlapFront: "flapFront", @@ -114,6 +126,8 @@ const fields = { scansPagesPage: "page", scansPagesImage: "image", + scanArchiveFile: "archiveFile", + contents: "contents", contentsContent: "content", contentsPageStart: "pageStart", @@ -121,6 +135,10 @@ const fields = { contentsTimeStart: "timeStart", contentsTimeEnd: "timeEnd", contentsNote: "note", + + parentFolders: "parentFolders", + parentItems: "parentItems", + subitems: "subitems", } as const satisfies Record; export const LibraryItems = buildVersionedCollectionConfig({ @@ -143,37 +161,32 @@ export const LibraryItems = buildVersionedCollectionConfig({ beforeDuplicateAddCopyTo(fields.slug), ]), }, - preview: (doc) => `https://accords-library.com/library/${doc.slug}`, }, fields: [ - { - type: "row", - fields: [ - { - name: fields.itemType, - type: "radio", - options: Object.entries(LibraryItemsTypes).map(([value, label]) => ({ - label, - value, - })), - admin: { - layout: "horizontal", - width: "0%", - }, + rowField([ + { + name: fields.itemType, + type: "radio", + options: Object.entries(LibraryItemsTypes).map(([value, label]) => ({ + label, + value, + })), + admin: { + layout: "horizontal", }, - { - name: fields.digital, - type: "checkbox", - required: true, - defaultValue: false, - admin: { - description: - "The item is the digital version of another item, or the item is sold only digitally.", - width: "0%", - }, + }, + { + name: fields.language, + type: "relationship", + relationTo: Collections.Languages, + required: true, + admin: { + allowCreate: false, + description: + "This item sole or primary language (most notably, the language used on the cover)", }, - ], - }, + }, + ]), { type: "tabs", admin: { @@ -183,51 +196,29 @@ export const LibraryItems = buildVersionedCollectionConfig({ { label: "Overview", fields: [ + rowField([ + slugField({ + name: fields.slug, + }), + imageField({ + name: fields.thumbnail, + relationTo: Collections.LibraryItemsThumbnails, + }), + ]), + rowField([ + { name: fields.pretitle, type: "text" }, + { name: fields.title, type: "text", required: true }, + { name: fields.subtitle, type: "text" }, + ]), { - type: "row", - fields: [ - slugField({ - name: fields.slug, - }), - imageField({ - name: fields.thumbnail, - relationTo: Collections.LibraryItemsThumbnails, - }), - ], - }, - { - type: "row", - fields: [ - { name: fields.pretitle, type: "text" }, - { name: fields.title, type: "text", required: true }, - { name: fields.subtitle, type: "text" }, - ], - }, - { - type: "row", - fields: [ - { - name: fields.rootItem, - type: "checkbox", - required: true, - defaultValue: true, - admin: { - description: "Only items that can be sold separetely should be root items.", - width: "0%", - }, - }, - { - name: fields.primary, - type: "checkbox", - required: true, - defaultValue: true, - admin: { - description: - "A primary item is an official item that focuses primarly on one or more of our Categories.", - width: "0%", - }, - }, - ], + name: fields.digital, + type: "checkbox", + required: true, + defaultValue: false, + admin: { + description: + "The item is the digital version of another item, or the item is sold only digitally.", + }, }, ], }, @@ -241,6 +232,7 @@ export const LibraryItems = buildVersionedCollectionConfig({ description: "Additional images of the item (unboxing, on shelf, promotional images...)", }, + labels: { singular: "Image", plural: "Images" }, fields: [ imageField({ name: fields.galleryImage, @@ -248,226 +240,190 @@ export const LibraryItems = buildVersionedCollectionConfig({ }), ], }, - optionalGroupField({ + componentField({ name: fields.scans, fields: [ - optionalGroupField({ + 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: [ - { - type: "row", - fields: [ - imageField({ - name: fields.scansCoverFront, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansCoverSpine, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansCoverBack, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - ], - }, - { - type: "row", - fields: [ - imageField({ - name: fields.scansCoverInsideFront, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansCoverBack, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - ], - }, - { - type: "row", - fields: [ - imageField({ - name: fields.scansCoverFlapFront, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansCoverFlapBack, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansCoverInsideFlapFront, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansCoverInsideFlapBack, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - ], - }, + 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.scansCoverBack, + 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, + }), + ]), ], }), - optionalGroupField({ + componentField({ name: fields.scansDustjacket, label: "Dust Jacket", - labels: { singular: "Dust Jacket", plural: "Dust Jackets" }, 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: [ - { - type: "row", - fields: [ - imageField({ - name: fields.scansDustjacketFront, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansDustjacketSpine, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansDustjacketBack, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - ], - }, - { - type: "row", - fields: [ - imageField({ - name: fields.scansDustjacketInsideFront, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansDustjacketInsideSpine, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansDustjacketInsideBack, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - ], - }, - { - type: "row", - fields: [ - imageField({ - name: fields.scansDustjacketFlapFront, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansDustjacketFlapBack, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansDustjacketInsideFlapFront, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansDustjacketInsideFlapBack, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - ], - }, + 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, + }), + ]), ], }), - optionalGroupField({ + componentField({ name: fields.scansObi, label: "Obi", - labels: { singular: "Obi Belt", plural: "Obi Belts" }, 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: [ - { - type: "row", - fields: [ - imageField({ - name: fields.scansObiFront, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansObiSpine, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansObiBack, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - ], - }, - { - type: "row", - fields: [ - imageField({ - name: fields.scansObiInsideFront, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansObiInsideSpine, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansObiInsideBack, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - ], - }, - { - type: "row", - fields: [ - imageField({ - name: fields.scansObiFlapFront, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansObiFlapBack, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansObiInsideFlapFront, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - imageField({ - name: fields.scansObiInsideFlapBack, - relationTo: Collections.LibraryItemsScans, - admin: { width: "0%" }, - }), - ], - }, + 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, + }), + ]), ], }), { @@ -483,35 +439,24 @@ export const LibraryItems = buildVersionedCollectionConfig({ }, }, fields: [ - { - type: "row", - fields: [ - { - name: fields.scansPagesPage, - type: "number", - required: true, - admin: { width: "0%" }, - }, - imageField({ - name: fields.scansPagesImage, - relationTo: Collections.LibraryItemsScans, - required: true, - admin: { width: "0%" }, - }), - ], - }, + rowField([ + { + name: fields.scansPagesPage, + type: "number", + required: true, + }, + imageField({ + name: fields.scansPagesImage, + relationTo: Collections.LibraryItemsScans, + required: true, + }), + ]), ], }, - { - name: fields.downloadable, - type: "checkbox", - required: true, - defaultValue: false, - admin: { - description: "Are the scans available for download?", - width: "0%", - }, - }, + fileField({ + name: fields.scanArchiveFile, + relationTo: FileTypes.LibraryScans, + }), ], }), ], @@ -535,58 +480,43 @@ export const LibraryItems = buildVersionedCollectionConfig({ keysField({ name: fields.textualSubtype, relationTo: KeysTypes.LibraryTextual, - hasMany: true, - admin: { allowCreate: false, width: "0%" }, }), - { - name: fields.textualLanguages, - type: "relationship", - relationTo: Collections.Languages, - hasMany: true, - admin: { allowCreate: false, width: "0%" }, - }, - ], - }, - { - type: "row", - fields: [ { name: fields.textualPageCount, type: "number", min: 1, - admin: { width: "0%" }, - }, - { - name: fields.textualBindingType, - type: "radio", - options: Object.entries(LibraryItemsTextualBindingTypes).map( - ([value, label]) => ({ - label, - value, - }) - ), - admin: { - condition: (data: Partial) => !data.digital, - layout: "horizontal", - width: "0%", - }, - }, - { - name: fields.textualPageOrder, - type: "radio", - options: Object.entries(LibraryItemsTextualPageOrders).map( - ([value, label]) => ({ - label, - value, - }) - ), - admin: { - layout: "horizontal", - width: "0%", - }, }, ], }, + rowField([ + { + name: fields.textualBindingType, + type: "radio", + options: Object.entries(LibraryItemsTextualBindingTypes).map( + ([value, label]) => ({ + label, + value, + }) + ), + admin: { + condition: (data: Partial) => !data.digital, + layout: "horizontal", + }, + }, + { + name: fields.textualPageOrder, + type: "radio", + options: Object.entries(LibraryItemsTextualPageOrders).map( + ([value, label]) => ({ + label, + value, + }) + ), + admin: { + layout: "horizontal", + }, + }, + ]), ], }, { @@ -598,66 +528,154 @@ export const LibraryItems = buildVersionedCollectionConfig({ data.itemType === LibraryItemsTypes.Audio, }, fields: [ - { - type: "row", - fields: [ - keysField({ - name: fields.audioSubtype, - relationTo: KeysTypes.LibraryAudio, - hasMany: true, - admin: { allowCreate: false, width: "0%" }, - }), - ], - }, + keysField({ + name: fields.audioSubtype, + relationTo: KeysTypes.LibraryAudio, + }), { name: fields.audioTracks, type: "array", fields: [ - { - type: "row", - fields: [ - { - name: fields.audioTracksTitle, - type: "text", - required: true, - admin: { width: "0%" }, - }, - fileField({ - name: fields.audioTracksFile, - relationTo: FileTypes.LibrarySoundtracks, - required: true, - admin: { width: "0%" }, - }), - ], - }, + rowField([ + { + name: fields.audioTracksTitle, + type: "text", + required: true, + }, + fileField({ + name: fields.audioTracksFile, + relationTo: FileTypes.LibrarySoundtracks, + required: true, + }), + ]), ], }, ], }, + { + name: fields.video, + type: "group", + label: false, + admin: { + condition: (data: Partial) => + data.itemType === LibraryItemsTypes.Video, + }, + fields: [ + keysField({ + name: fields.videoSubtype, + relationTo: KeysTypes.LibraryVideo, + }), + ], + }, + { + name: fields.game, + type: "group", + label: false, + admin: { + condition: (data: Partial) => data.itemType === LibraryItemsTypes.Game, + }, + fields: [ + rowField([ + { + name: fields.gameDemo, + type: "checkbox", + admin: { description: "Is this item a demo for the game" }, + }, + keysField({ + name: fields.gamePlatform, + relationTo: KeysTypes.GamePlatforms, + }), + ]), + rowField([ + { + name: fields.gameAudioLanguages, + type: "relationship", + relationTo: Collections.Languages, + hasMany: true, + }, + { + name: fields.gameSubtitleLanguages, + type: "relationship", + relationTo: Collections.Languages, + hasMany: true, + }, + { + name: fields.gameInterfacesLanguages, + type: "relationship", + relationTo: Collections.Languages, + hasMany: true, + }, + ]), + ], + }, ], }, { label: "Details", fields: [ - { - type: "row", - fields: [ - { - name: fields.releaseDate, - type: "date", - admin: { - date: { pickerAppearance: "dayOnly", displayFormat: "yyyy-MM-dd" }, - width: "0%", - }, + rowField([ + { + name: fields.releaseDate, + type: "date", + admin: { + date: { pickerAppearance: "dayOnly", displayFormat: "yyyy-MM-dd" }, }, - keysField({ - name: fields.categories, - relationTo: KeysTypes.Categories, - hasMany: true, - admin: { allowCreate: false, width: "0%" }, - }), + }, + keysField({ + name: fields.categories, + relationTo: KeysTypes.Categories, + hasMany: true, + }), + ]), + componentField({ + name: fields.size, + admin: { + condition: (data: Partial) => !data.digital, + description: "Add physical size information about the item", + }, + fields: [ + rowField([ + { + name: fields.width, + type: "number", + required: true, + admin: { step: 1, description: "in mm." }, + }, + { + name: fields.height, + type: "number", + required: true, + admin: { step: 1, description: "in mm." }, + }, + { + name: fields.thickness, + type: "number", + admin: { step: 1, description: "in mm." }, + }, + ]), ], - }, + }), + componentField({ + name: fields.price, + admin: { description: "Add pricing information about the item" }, + fields: [ + rowField([ + { + name: fields.priceAmount, + type: "number", + required: true, + min: 0, + }, + { + name: fields.priceCurrency, + type: "relationship", + relationTo: Collections.Currencies, + required: true, + admin: { allowCreate: false }, + }, + ]), + ], + }), translatedFields({ name: fields.translations, label: "Descriptions", @@ -671,66 +689,12 @@ export const LibraryItems = buildVersionedCollectionConfig({ }, ], }), - optionalGroupField({ - name: fields.size, - admin: { condition: (data: Partial) => !data.digital }, - fields: [ - { - type: "row", - fields: [ - { - name: fields.width, - type: "number", - required: true, - admin: { step: 1, width: "0%", description: "in mm." }, - }, - { - name: fields.height, - type: "number", - required: true, - admin: { step: 1, width: "0%", description: "in mm." }, - }, - { - name: fields.thickness, - type: "number", - admin: { step: 1, width: "0%", description: "in mm." }, - }, - ], - }, - ], - }), - optionalGroupField({ - name: fields.price, - admin: { className: "group-array", width: "0%" }, - fields: [ - { - type: "row", - fields: [ - { - name: fields.priceAmount, - type: "number", - required: true, - min: 0, - admin: { width: "0%" }, - }, - { - name: fields.priceCurrency, - type: "relationship", - relationTo: Collections.Currencies, - required: true, - admin: { allowCreate: false, width: "0%" }, - }, - ], - }, - ], - }), { name: fields.urls, label: "URLs", type: "array", admin: { description: "Links to official websites where to get/buy the item.", - width: "0%", }, fields: [{ name: fields.urlsUrl, type: "text", required: true }], }, @@ -739,6 +703,29 @@ export const LibraryItems = buildVersionedCollectionConfig({ { label: "Contents", fields: [ + rowField([ + backPropagationField({ + name: fields.parentFolders, + relationTo: Collections.LibraryFolders, + hasMany: true, + where: ({ id }) => ({ items: { equals: id } }), + admin: { + description: `You can set the folders from the "Library Folders" collection`, + }, + }), + backPropagationField({ + name: fields.parentItems, + relationTo: Collections.LibraryItems, + hasMany: true, + where: ({ id }) => ({ [fields.subitems]: { equals: id } }), + }), + { + name: fields.subitems, + type: "relationship", + hasMany: true, + relationTo: Collections.LibraryItems, + }, + ]), { name: fields.contents, type: "array", @@ -752,7 +739,9 @@ export const LibraryItems = buildVersionedCollectionConfig({ { type: "row", admin: { - condition: ({ itemType }) => itemType === LibraryItemsTypes.Textual, + // TODO: Check why those condition doesn't work + condition: ({ itemType }: Partial) => + itemType === LibraryItemsTypes.Textual, }, fields: [ { @@ -765,7 +754,7 @@ export const LibraryItems = buildVersionedCollectionConfig({ { type: "row", admin: { - condition: ({ itemType }) => + condition: ({ itemType }: Partial) => itemType === LibraryItemsTypes.Audio || itemType === LibraryItemsTypes.Video, }, fields: [ @@ -781,7 +770,7 @@ export const LibraryItems = buildVersionedCollectionConfig({ type: "richText", editor: createEditor({ inlines: true, lists: true, links: true }), admin: { - condition: ({ itemType }) => + condition: ({ itemType }: Partial) => itemType === LibraryItemsTypes.Game || itemType === LibraryItemsTypes.Other, }, }, diff --git a/src/collections/Posts/Posts.ts b/src/collections/Posts/Posts.ts index c41dd99..ac880b7 100644 --- a/src/collections/Posts/Posts.ts +++ b/src/collections/Posts/Posts.ts @@ -1,6 +1,9 @@ +import { sectionBlock } from "../../blocks/sectionBlock"; import { QuickFilters, publishStatusFilters } from "../../components/QuickFilters"; import { CollectionGroups, Collections, KeysTypes } from "../../constants"; import { imageField } from "../../fields/imageField/imageField"; +import { keysField } from "../../fields/keysField/keysField"; +import { rowField } from "../../fields/rowField/rowField"; import { slugField } from "../../fields/slugField/slugField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { beforeDuplicateAddCopyTo } from "../../hooks/beforeDuplicateAddCopyTo"; @@ -58,39 +61,24 @@ export const Posts = buildVersionedCollectionConfig({ preview: (doc) => `https://accords-library.com/news/${doc.slug}`, }, fields: [ - { - type: "row", - fields: [ - slugField({ name: fields.slug, admin: { width: "0%" } }), - imageField({ - name: fields.thumbnail, - relationTo: Collections.PostsThumbnails, - admin: { width: "0%" }, - }), - ], - }, - { - type: "row", - fields: [ - { - name: fields.authors, - type: "relationship", - relationTo: [Collections.Recorders], - required: true, - minRows: 1, - hasMany: true, - admin: { width: "0%" }, - }, - { - name: fields.categories, - type: "relationship", - relationTo: [Collections.Keys], - filterOptions: { type: { equals: KeysTypes.Categories } }, - hasMany: true, - admin: { allowCreate: false, width: "0%" }, - }, - ], - }, + rowField([ + slugField({ name: fields.slug }), + imageField({ + name: fields.thumbnail, + relationTo: Collections.PostsThumbnails, + }), + ]), + rowField([ + { + name: fields.authors, + type: "relationship", + relationTo: [Collections.Recorders], + required: true, + minRows: 1, + hasMany: true, + }, + keysField({ name: fields.categories, relationTo: KeysTypes.Categories, hasMany: true }), + ]), translatedFields({ name: fields.translations, admin: { useAsTitle: fields.title, hasSourceLanguage: true }, @@ -104,57 +92,60 @@ export const Posts = buildVersionedCollectionConfig({ editor: createEditor({ inlines: true, lists: true, links: true }), }, { - type: "row", - fields: [ - { - name: fields.translators, - type: "relationship", - relationTo: Collections.Recorders, - hasMany: true, - hooks: { - beforeChange: [ - ({ siblingData }) => { - if (siblingData.language === siblingData.sourceLanguage) { - delete siblingData.translators; - } - }, - ], - }, - admin: { - condition: (_, siblingData) => { - if ( - isUndefined(siblingData.language) || - isUndefined(siblingData.sourceLanguage) - ) { - return false; - } - return siblingData.language !== siblingData.sourceLanguage; - }, - width: "0%", - }, - validate: (translators, { siblingData }) => { - if (isUndefined(siblingData.language) || isUndefined(siblingData.sourceLanguage)) { - return true; - } - if (siblingData.language === siblingData.sourceLanguage) { - return true; - } - if (isDefined(translators) && translators.length > 0) { - return true; - } - return "This field is required when the language is different from the source language."; - }, - }, - { - name: fields.proofreaders, - type: "relationship", - relationTo: Collections.Recorders, - hasMany: true, - admin: { width: "0%" }, - }, - ], + name: fields.content, + type: "richText", + editor: createEditor({ + images: true, + inlines: true, + alignment: true, + blocks: [sectionBlock], + links: true, + lists: true, + }), }, - { name: fields.content, type: "richText" }, + rowField([ + { + name: fields.translators, + type: "relationship", + relationTo: Collections.Recorders, + hasMany: true, + hooks: { + beforeChange: [ + ({ siblingData }) => { + if (siblingData.language === siblingData.sourceLanguage) { + delete siblingData.translators; + } + }, + ], + }, + admin: { + condition: (_, siblingData) => { + if (isUndefined(siblingData.language) || isUndefined(siblingData.sourceLanguage)) { + return false; + } + return siblingData.language !== siblingData.sourceLanguage; + }, + }, + validate: (translators, { siblingData }) => { + if (isUndefined(siblingData.language) || isUndefined(siblingData.sourceLanguage)) { + return true; + } + if (siblingData.language === siblingData.sourceLanguage) { + return true; + } + if (isDefined(translators) && translators.length > 0) { + return true; + } + return "This field is required when the language is different from the source language."; + }, + }, + { + name: fields.proofreaders, + type: "relationship", + relationTo: Collections.Recorders, + hasMany: true, + }, + ]), ], }), { diff --git a/src/collections/Recorders/Recorders.ts b/src/collections/Recorders/Recorders.ts index 5a4b4b1..a17ed70 100644 --- a/src/collections/Recorders/Recorders.ts +++ b/src/collections/Recorders/Recorders.ts @@ -4,6 +4,7 @@ import { mustBeAdmin as mustBeAdminForFields } from "../../accesses/fields/mustB import { QuickFilters } from "../../components/QuickFilters"; import { CollectionGroups, Collections, RecordersRoles } from "../../constants"; import { imageField } from "../../fields/imageField/imageField"; +import { rowField } from "../../fields/rowField/rowField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { buildCollectionConfig } from "../../utils/collectionConfig"; import { createEditor } from "../../utils/editor"; @@ -77,23 +78,19 @@ export const Recorders = buildCollectionConfig({ endpoints: [importFromStrapi], timestamps: false, fields: [ - { - type: "row", - fields: [ - { - name: fields.username, - type: "text", - unique: true, - required: true, - admin: { description: "The username must be unique", width: "0%" }, - }, - imageField({ - name: fields.avatar, - relationTo: Collections.RecordersThumbnails, - admin: { width: "0%" }, - }), - ], - }, + rowField([ + { + name: fields.username, + type: "text", + unique: true, + required: true, + admin: { description: "The username must be unique" }, + }, + imageField({ + name: fields.avatar, + relationTo: Collections.RecordersThumbnails, + }), + ]), { name: fields.languages, type: "relationship", diff --git a/src/collections/Recorders/endpoints/importFromStrapi.ts b/src/collections/Recorders/endpoints/importFromStrapi.ts index b2a9643..4a93f54 100644 --- a/src/collections/Recorders/endpoints/importFromStrapi.ts +++ b/src/collections/Recorders/endpoints/importFromStrapi.ts @@ -51,7 +51,7 @@ export const importFromStrapi = createStrapiImportEndpoint({ if (isUndefined(language.data)) throw new Error("A language is required for a Recorder biography"); if (isUndefined(bio)) throw new Error("A bio is required for a Recorder biography"); - return { + return { language: language.data.attributes.code, biography: plainTextToLexical(bio), }; diff --git a/src/collections/Videos/Videos.ts b/src/collections/Videos/Videos.ts index 97da2f9..b4f1d27 100644 --- a/src/collections/Videos/Videos.ts +++ b/src/collections/Videos/Videos.ts @@ -1,6 +1,7 @@ import { CollectionConfig } from "payload/types"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { CollectionGroups, Collections, VideoSources } from "../../constants"; +import { rowField } from "../../fields/rowField/rowField"; import { buildCollectionConfig } from "../../utils/collectionConfig"; import { importFromStrapi } from "./endpoints/importFromStrapi"; @@ -47,40 +48,31 @@ export const Videos: CollectionConfig = buildCollectionConfig({ endpoints: [importFromStrapi], timestamps: false, fields: [ - { - type: "row", - fields: [ - { name: fields.uid, type: "text", required: true, unique: true, admin: { width: "0%" } }, - { - name: fields.gone, - type: "checkbox", - defaultValue: false, - required: true, - admin: { - description: - "Is the video no longer available (deleted, privatized, unlisted, blocked...)", - width: "0%", - }, + rowField([ + { name: fields.uid, type: "text", required: true, unique: true }, + { + name: fields.gone, + type: "checkbox", + defaultValue: false, + required: true, + admin: { + description: + "Is the video no longer available (deleted, privatized, unlisted, blocked...)", }, - { - name: fields.source, - type: "select", - required: true, - options: Object.entries(VideoSources).map(([value, label]) => ({ label, value })), - admin: { width: "0%" }, - }, - ], - }, - + }, + { + name: fields.source, + type: "select", + required: true, + options: Object.entries(VideoSources).map(([value, label]) => ({ label, value })), + }, + ]), { name: fields.title, type: "text", required: true }, { name: fields.description, type: "textarea" }, - { - type: "row", - fields: [ - { name: fields.likes, type: "number", admin: { width: "0%" } }, - { name: fields.views, type: "number", admin: { width: "0%" } }, - ], - }, + rowField([ + { name: fields.likes, type: "number" }, + { name: fields.views, type: "number" }, + ]), { name: fields.publishedDate, type: "date", diff --git a/src/collections/VideosChannels/VideosChannels.ts b/src/collections/VideosChannels/VideosChannels.ts index 75c0d88..429019b 100644 --- a/src/collections/VideosChannels/VideosChannels.ts +++ b/src/collections/VideosChannels/VideosChannels.ts @@ -1,6 +1,7 @@ import { CollectionConfig } from "payload/types"; import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin"; import { CollectionGroups, Collections } from "../../constants"; +import { rowField } from "../../fields/rowField/rowField"; import { buildCollectionConfig } from "../../utils/collectionConfig"; const fields = { @@ -30,12 +31,9 @@ export const VideosChannels: CollectionConfig = buildCollectionConfig({ timestamps: false, fields: [ { name: fields.uid, type: "text", required: true, unique: true }, - { - type: "row", - fields: [ - { name: fields.title, type: "text", required: true }, - { name: fields.subscribers, type: "number" }, - ], - }, + rowField([ + { name: fields.title, type: "text", required: true }, + { name: fields.subscribers, type: "number" }, + ]), ], }); diff --git a/src/collections/Weapons/Weapons.ts b/src/collections/Weapons/Weapons.ts index 84b794e..d8ca8cf 100644 --- a/src/collections/Weapons/Weapons.ts +++ b/src/collections/Weapons/Weapons.ts @@ -2,6 +2,7 @@ import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types import { CollectionGroups, Collections, KeysTypes } from "../../constants"; import { imageField } from "../../fields/imageField/imageField"; import { keysField } from "../../fields/keysField/keysField"; +import { rowField } from "../../fields/rowField/rowField"; import { slugField } from "../../fields/slugField/slugField"; import { translatedFields } from "../../fields/translatedFields/translatedFields"; import { createEditor } from "../../utils/editor"; @@ -45,34 +46,25 @@ export const Weapons = buildVersionedCollectionConfig({ }, endpoints: [importFromStrapi, getBySlugEndpoint], fields: [ - { - type: "row", - fields: [ - slugField({ name: fields.slug, admin: { width: "0%" } }), - imageField({ - name: fields.thumbnail, - relationTo: Collections.WeaponsThumbnails, - admin: { width: "0%" }, - }), - ], - }, - { - type: "row", - fields: [ - keysField({ - name: fields.type, - relationTo: KeysTypes.Weapons, - required: true, - admin: { allowCreate: false, width: "0%" }, - }), - { - name: fields.group, - type: "relationship", - relationTo: Collections.WeaponsGroups, - admin: { width: "0%" }, - }, - ], - }, + rowField([ + slugField({ name: fields.slug }), + imageField({ + name: fields.thumbnail, + relationTo: Collections.WeaponsThumbnails, + }), + ]), + rowField([ + keysField({ + name: fields.type, + relationTo: KeysTypes.Weapons, + required: true, + }), + { + name: fields.group, + type: "relationship", + relationTo: Collections.WeaponsGroups, + }, + ]), { name: fields.appearances, type: "array", @@ -91,7 +83,6 @@ export const Weapons = buildVersionedCollectionConfig({ required: true, hasMany: true, relationTo: KeysTypes.Categories, - admin: { allowCreate: false }, }), translatedFields({ name: fields.appearancesTranslations, @@ -103,61 +94,46 @@ export const Weapons = buildVersionedCollectionConfig({ hasCredits: true, }, fields: [ - { - type: "row", - fields: [ - { - name: fields.appearancesTranslationsName, - type: "text", - required: true, - admin: { width: "0%" }, - }, - { - name: fields.appearancesTranslationsDescription, - type: "richText", - editor: createEditor({ inlines: true }), - admin: { width: "0%" }, - }, - ], - }, - { - type: "row", - fields: [ - { - name: fields.appearancesTranslationsLevel1, - label: "Level 1", - type: "richText", - editor: createEditor({ inlines: true }), - admin: { width: "0%" }, - }, - { - name: fields.appearancesTranslationsLevel2, - label: "Level 2", - type: "richText", - editor: createEditor({ inlines: true }), - admin: { width: "0%" }, - }, - ], - }, - { - type: "row", - fields: [ - { - name: fields.appearancesTranslationsLevel3, - label: "Level 3", - type: "richText", - editor: createEditor({ inlines: true }), - admin: { width: "0%" }, - }, - { - name: fields.appearancesTranslationsLevel4, - label: "Level 4", - type: "richText", - editor: createEditor({ inlines: true }), - admin: { width: "0%" }, - }, - ], - }, + rowField([ + { + name: fields.appearancesTranslationsName, + type: "text", + required: true, + }, + { + name: fields.appearancesTranslationsDescription, + type: "richText", + editor: createEditor({ inlines: true }), + }, + ]), + rowField([ + { + name: fields.appearancesTranslationsLevel1, + label: "Level 1", + type: "richText", + editor: createEditor({ inlines: true }), + }, + { + name: fields.appearancesTranslationsLevel2, + label: "Level 2", + type: "richText", + editor: createEditor({ inlines: true }), + }, + ]), + rowField([ + { + name: fields.appearancesTranslationsLevel3, + label: "Level 3", + type: "richText", + editor: createEditor({ inlines: true }), + }, + { + name: fields.appearancesTranslationsLevel4, + label: "Level 4", + type: "richText", + editor: createEditor({ inlines: true }), + }, + ]), ], }), ], diff --git a/src/endpoints/createStrapiImportEndpoint.ts b/src/endpoints/createStrapiImportEndpoint.ts index 55a53d3..18313cc 100644 --- a/src/endpoints/createStrapiImportEndpoint.ts +++ b/src/endpoints/createStrapiImportEndpoint.ts @@ -38,6 +38,7 @@ type Params = { params: any; }; payload: { + path?: string; collection: Collections; import?: (strapiObject: S, user: any) => Promise; convert?: ( @@ -55,36 +56,38 @@ export const importStrapiEntries = async ({ const entries = await getAllStrapiEntries(strapiParams.collection, strapiParams.params); const errors: string[] = []; + let currentCount = 1; - await Promise.all( - entries.map(async ({ attributes, id }) => { - try { - if (isDefined(payloadParams.import)) { - await payloadParams.import(attributes, user); - } else if (isDefined(payloadParams.convert)) { - await payload.create({ - collection: payloadParams.collection, - data: await payloadParams.convert(attributes, user), - user, - }); - } else { - throw new Error("No function was provided to handle importing the Strapi data"); - } - } catch (e) { - console.warn(e); - if (typeof e === "object" && isDefined(e) && "name" in e) { - errors.push(`${e.name} with ${id}`); - } + for (const { attributes, id } of entries) { + console.debug(`Handling entry ${currentCount}/${entries.length} (id: ${id})`); + currentCount++; + try { + if (isDefined(payloadParams.import)) { + await payloadParams.import(attributes, user); + } else if (isDefined(payloadParams.convert)) { + await payload.create({ + collection: payloadParams.collection, + data: await payloadParams.convert(attributes, user), + user, + }); + } else { + throw new Error("No function was provided to handle importing the Strapi data"); } - }) - ); + } catch (e) { + console.warn(e); + if (typeof e === "object" && isDefined(e) && "name" in e) { + const message = "message" in e ? ` (${e.message})` : ""; + errors.push(`${e.name}${message} with ${id}`); + } + } + } return { count: entries.length, errors }; }; export const createStrapiImportEndpoint = (params: Params): CollectionEndpoint => ({ method: "post", - path: "/strapi", + path: params.payload.path ?? "/strapi", handler: async (req, res) => { if (!req.user) { return res.status(403).send({ diff --git a/src/fields/backPropagationField/backPropagationField.ts b/src/fields/backPropagationField/backPropagationField.ts index 8ab6f87..3cf7a01 100644 --- a/src/fields/backPropagationField/backPropagationField.ts +++ b/src/fields/backPropagationField/backPropagationField.ts @@ -2,7 +2,7 @@ import payload from "payload"; import { FieldBase } from "payload/dist/fields/config/types"; import { RelationshipField, Where } from "payload/types"; import { Collections } from "../../constants"; -import { isNotEmpty } from "../../utils/asserts"; +import { isEmpty } from "../../utils/asserts"; type BackPropagationField = FieldBase & { where: (data: any) => Where; @@ -30,21 +30,22 @@ export const backPropagationField = ({ ], afterRead: [ ...afterRead, - async ({ data }) => { - if (isNotEmpty(data?.id)) { - const result = await payload.find({ - collection: params.relationTo, - where: where(data), - limit: 100, - depth: 0, - }); - if (hasMany) { - return result.docs.map((doc) => doc.id); - } else { - return result.docs[0]?.id; - } + async ({ data, context }) => { + if (isEmpty(data?.id) || context.stopPropagation) { + return hasMany ? [] : undefined; + } + const result = await payload.find({ + collection: params.relationTo, + where: where(data), + limit: 100, + depth: 0, + context: { stopPropagation: true }, + }); + if (hasMany) { + return result.docs.map((doc) => doc.id); + } else { + return result.docs[0]?.id; } - return hasMany ? [] : undefined; }, ], }, diff --git a/src/fields/componentField/componentField.ts b/src/fields/componentField/componentField.ts new file mode 100644 index 0000000..08b6258 --- /dev/null +++ b/src/fields/componentField/componentField.ts @@ -0,0 +1,55 @@ +import { CollapsibleField, Condition, Field } from "payload/types"; +import { capitalize } from "../../utils/string"; + +type Props = { + name: string; + label?: string; + admin?: { + description?: string; + condition?: Condition; + }; + fields: Field[]; +}; + +export const componentField = ({ + name, + fields, + label = capitalize(name), + admin, +}: Props): CollapsibleField => { + return { + type: "collapsible", + label: name, + admin: { className: "component-field", condition: admin?.condition }, + fields: [ + { + name: `${name}Enabled`, + label, + type: "checkbox", + admin: { + className: "component-field-checkbox", + description: admin?.description, + }, + }, + { + name, + type: "group", + hooks: { + beforeChange: [ + ({ siblingData }) => { + if (!siblingData[`${name}Enabled`]) { + delete siblingData[name]; + } + }, + ], + }, + admin: { + className: "component-field-group", + condition: (_, siblingData) => siblingData[`${name}Enabled`], + hideGutter: true, + }, + fields, + }, + ], + }; +}; diff --git a/src/fields/keysField/keysField.ts b/src/fields/keysField/keysField.ts index 11c95b0..fe2d2a1 100644 --- a/src/fields/keysField/keysField.ts +++ b/src/fields/keysField/keysField.ts @@ -10,9 +10,14 @@ type KeysField = FieldBase & { export const keysField = ({ relationTo, hasMany = false, + admin, ...props }: KeysField): RelationshipField => ({ ...props, + admin: { + allowCreate: false, + ...admin, + }, type: "relationship", hasMany: hasMany, relationTo: Collections.Keys, diff --git a/src/fields/optionalGroupField/optionalGroupField.ts b/src/fields/optionalGroupField/optionalGroupField.ts deleted file mode 100644 index 9572342..0000000 --- a/src/fields/optionalGroupField/optionalGroupField.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ArrayField } from "payload/types"; - -type Props = Omit; - -export const optionalGroupField = ({ - admin: { className = "", ...otherAdmin } = {}, - ...otherProps -}: Props): ArrayField => ({ - ...otherProps, - type: "array", - minRows: 0, - maxRows: 1, - admin: { ...otherAdmin, className: `${className} group-array` }, -}); diff --git a/src/fields/rowField/rowField.ts b/src/fields/rowField/rowField.ts new file mode 100644 index 0000000..321819e --- /dev/null +++ b/src/fields/rowField/rowField.ts @@ -0,0 +1,9 @@ +import { Field, RowField } from "payload/types"; + +export const rowField = (fields: Field[]): RowField => ({ + type: "row", + fields: fields.map(({ admin, ...otherConfig }) => ({ + ...otherConfig, + admin: { width: "0%", ...admin }, + })), +}); diff --git a/src/fields/translatedFields/translatedFields.ts b/src/fields/translatedFields/translatedFields.ts index fc78009..36db56a 100644 --- a/src/fields/translatedFields/translatedFields.ts +++ b/src/fields/translatedFields/translatedFields.ts @@ -3,6 +3,7 @@ import { array } from "payload/dist/fields/validations"; import { ArrayField, Field } from "payload/types"; import { Collections } from "../../constants"; import { hasDuplicates, isDefined, isUndefined } from "../../utils/asserts"; +import { rowField } from "../rowField/rowField"; import { Cell } from "./Cell"; import { RowLabel } from "./RowLabel"; @@ -28,7 +29,7 @@ const languageField: Field = { type: "relationship", relationTo: Collections.Languages, required: true, - admin: { allowCreate: false }, + admin: { allowCreate: false }, }; const sourceLanguageField: Field = { @@ -169,9 +170,7 @@ export const translatedFields = ({ return true; }, fields: [ - hasSourceLanguage - ? { type: "row", fields: [languageField, sourceLanguageField] } - : languageField, + rowField(hasSourceLanguage ? [languageField, sourceLanguageField] : [languageField]), ...fields, ...(hasCredits ? [creditFields] : []), ], diff --git a/src/styles.scss b/src/styles.scss index 42a0d2a..822974d 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,3 +1,5 @@ +// Theme override + html[data-theme="dark"] { --color-base-0: #ffffff; --color-base-50: #ebeae7; @@ -46,8 +48,14 @@ html[data-theme="light"] { --color-base-1000: #000000; } +// Fix height disparities between certain fields + .field-type.row { padding: 1rem 0; + + > div > .field-type.checkbox { + padding-top: 2.4rem; + } } .field-type.radio-group { @@ -56,6 +64,8 @@ html[data-theme="light"] { } } +// Hide header on blocks with classname "no-label" + .field-type.no-label > header { display: none; } @@ -68,37 +78,49 @@ html[data-theme="light"] { display: none; } +// Reduce margin on Lexical blocks with the classname "reduced-margins" + .rich-text-lexical.field-type.reduced-margins { margin-top: -0.75em; margin-bottom: -2rem; } -.field-type.array-field.group-array { - > .array-field__header { - .array-field__header-actions { +// CSS for componentField + +.collapsible-field.component-field { + > .collapsible > .collapsible__toggle-wrap { + display: none; + } + + .component-field-checkbox { + margin-bottom: 0; + + .checkbox-input { + flex-direction: row-reverse; + justify-content: space-between; + display: flex; + gap: 16px; + pointer-events: none; + + > .checkbox-input__input { + pointer-events: auto; + } + } + + .field-label { + font-weight: 500; + font-family: "Suisse Intl"; + font-size: 1.7788461538rem; + padding: 0; + transform: translateY(2px); + } + } + + .component-field-group { + margin-top: 2rem; + + > .group-field__wrap > .group-field__header { display: none; } } - > div { - > div { - > div { - > .collapsible__toggle-wrap { - .array-actions__duplicate { - display: none; - } - - .collapsible__drag, - .collapsible__toggle, - .collapsible__header-wrap, - .collapsible__indicator { - display: none; - } - - .collapsible__actions-wrap { - z-index: 1; - } - } - } - } - } } diff --git a/src/types/collections.ts b/src/types/collections.ts index 4d12e10..86ab027 100644 --- a/src/types/collections.ts +++ b/src/types/collections.ts @@ -22,32 +22,32 @@ export type RecorderBiographies = { export interface Config { collections: { - 'library-folders': LibraryFolder; - 'library-items': LibraryItem; + "library-folders": LibraryFolder; + "library-items": LibraryItem; contents: Content; - 'contents-folders': ContentsFolder; + "contents-folders": ContentsFolder; posts: Post; - 'chronology-items': ChronologyItem; - 'chronology-eras': ChronologyEra; + "chronology-items": ChronologyItem; + "chronology-eras": ChronologyEra; weapons: Weapon; - 'weapons-groups': WeaponsGroup; - 'weapons-thumbnails': WeaponsThumbnail; - 'contents-thumbnails': ContentsThumbnail; - 'library-items-thumbnails': LibraryItemThumbnail; - 'library-items-scans': LibraryItemScans; - 'library-items-gallery': LibraryItemGallery; - 'recorders-thumbnails': RecordersThumbnail; - 'posts-thumbnails': PostThumbnail; + "weapons-groups": WeaponsGroup; + "weapons-thumbnails": WeaponsThumbnail; + "contents-thumbnails": ContentsThumbnail; + "library-items-thumbnails": LibraryItemThumbnail; + "library-items-scans": LibraryItemScans; + "library-items-gallery": LibraryItemGallery; + "recorders-thumbnails": RecordersThumbnail; + "posts-thumbnails": PostThumbnail; files: File; Notes: Note; videos: Video; - 'videos-channels': VideosChannel; + "videos-channels": VideosChannel; languages: Language; currencies: Currency; recorders: Recorder; keys: Key; - 'payload-preferences': PayloadPreference; - 'payload-migrations': PayloadMigration; + "payload-preferences": PayloadPreference; + "payload-migrations": PayloadMigration; }; globals: {}; } @@ -62,6 +62,7 @@ export interface LibraryFolder { }[]; id?: string; }[]; + parentFolders?: string[] | LibraryFolder[]; subfolders?: string[] | LibraryFolder[]; items?: string[] | LibraryItem[]; } @@ -71,20 +72,20 @@ export interface Language { } export interface LibraryItem { id: string; - itemType?: 'Textual' | 'Audio' | 'Video' | 'Game' | 'Other'; - digital: boolean; + itemType?: "Textual" | "Audio" | "Video" | "Game" | "Other"; slug: string; thumbnail?: string | LibraryItemThumbnail; pretitle?: string; title: string; subtitle?: string; - rootItem: boolean; - primary: boolean; + digital: boolean; gallery?: { image?: string | LibraryItemGallery; id?: string; }[]; + scansEnabled?: boolean; scans?: { + coverEnabled?: boolean; cover?: { front?: string | LibraryItemScans; spine?: string | LibraryItemScans; @@ -94,8 +95,8 @@ export interface LibraryItem { flapBack?: string | LibraryItemScans; insideFlapFront?: string | LibraryItemScans; insideFlapBack?: string | LibraryItemScans; - id?: string; - }[]; + }; + dustjacketEnabled?: boolean; dustjacket?: { front?: string | LibraryItemScans; spine?: string | LibraryItemScans; @@ -107,8 +108,8 @@ export interface LibraryItem { flapBack?: string | LibraryItemScans; insideFlapFront?: string | LibraryItemScans; insideFlapBack?: string | LibraryItemScans; - id?: string; - }[]; + }; + obiEnabled?: boolean; obi?: { front?: string | LibraryItemScans; spine?: string | LibraryItemScans; @@ -120,22 +121,19 @@ export interface LibraryItem { flapBack?: string | LibraryItemScans; insideFlapFront?: string | LibraryItemScans; insideFlapBack?: string | LibraryItemScans; - id?: string; - }[]; + }; pages?: { page: number; image: string | LibraryItemScans; id?: string; }[]; - downloadable: boolean; - id?: string; - }[]; + }; textual?: { subtype?: string[] | Key[]; languages?: string[] | Language[]; pageCount?: number; - bindingType?: 'Paperback' | 'Hardcover'; - pageOrder?: 'LeftToRight' | 'RightToLeft'; + bindingType?: "Paperback" | "Hardcover"; + pageOrder?: "LeftToRight" | "RightToLeft"; }; audio?: { audioSubtype?: string[] | Key[]; @@ -147,6 +145,17 @@ export interface LibraryItem { }; releaseDate?: string; categories?: string[] | Key[]; + sizeEnabled?: boolean; + size?: { + width: number; + height: number; + thickness?: number; + }; + priceEnabled?: boolean; + price?: { + amount: number; + currency: string | Currency; + }; translations?: { language: string | Language; description: { @@ -154,21 +163,13 @@ export interface LibraryItem { }[]; id?: string; }[]; - size?: { - width: number; - height: number; - thickness?: number; - id?: string; - }[]; - price?: { - amount: number; - currency: string | Currency; - id?: string; - }[]; urls?: { url: string; id?: string; }[]; + parentFolders?: string[] | LibraryFolder[]; + parentItems?: string[] | LibraryItem[]; + subitems?: string[] | LibraryItem[]; contents?: { content: string | Content; pageStart?: number; @@ -183,7 +184,7 @@ export interface LibraryItem { updatedBy: string | Recorder; updatedAt: string; createdAt: string; - _status?: 'draft' | 'published'; + _status?: "draft" | "published"; } export interface LibraryItemThumbnail { id: string; @@ -301,22 +302,22 @@ export interface Key { id: string; name: string; type: - | 'Contents' - | 'LibraryAudio' - | 'LibraryVideo' - | 'LibraryTextual' - | 'LibraryGroup' - | 'Library' - | 'Weapons' - | 'GamePlatforms' - | 'Categories' - | 'Wordings'; + | "Contents" + | "LibraryAudio" + | "LibraryVideo" + | "LibraryTextual" + | "LibraryGroup" + | "Library" + | "Weapons" + | "GamePlatforms" + | "Categories" + | "Wordings"; translations?: CategoryTranslations; } export interface File { id: string; filename: string; - type: 'LibraryScans' | 'LibrarySoundtracks' | 'ContentVideo' | 'ContentAudio'; + type: "LibraryScans" | "LibrarySoundtracks" | "ContentVideo" | "ContentAudio"; updatedAt: string; createdAt: string; } @@ -329,6 +330,7 @@ export interface Content { thumbnail?: string | ContentsThumbnail; categories?: string[] | Key[]; type?: string | Key; + libraryItems?: string[] | LibraryItem[]; translations: { language: string | Language; sourceLanguage: string | Language; @@ -354,10 +356,13 @@ export interface Content { audio?: string | File; id?: string; }[]; + folders?: string[] | ContentsFolder[]; + previousContents?: string[] | Content[]; + nextContents?: string[] | Content[]; updatedBy: string | Recorder; updatedAt: string; createdAt: string; - _status?: 'draft' | 'published'; + _status?: "draft" | "published"; } export interface ContentsThumbnail { id: string; @@ -403,7 +408,7 @@ export interface Recorder { avatar?: string | RecordersThumbnail; languages?: string[] | Language[]; biographies?: RecorderBiographies; - role?: ('Admin' | 'Recorder' | 'Api')[]; + role?: ("Admin" | "Recorder" | "Api")[]; anonymize: boolean; email: string; resetPasswordToken?: string; @@ -461,20 +466,20 @@ export interface Post { thumbnail?: string | PostThumbnail; authors: | { - relationTo: 'recorders'; + relationTo: "recorders"; value: string; }[] | { - relationTo: 'recorders'; + relationTo: "recorders"; value: Recorder; }[]; categories?: | { - relationTo: 'keys'; + relationTo: "keys"; value: string; }[] | { - relationTo: 'keys'; + relationTo: "keys"; value: Key; }[]; translations: { @@ -484,11 +489,11 @@ export interface Post { summary?: { [k: string]: unknown; }[]; - translators?: string[] | Recorder[]; - proofreaders?: string[] | Recorder[]; content?: { [k: string]: unknown; }[]; + translators?: string[] | Recorder[]; + proofreaders?: string[] | Recorder[]; id?: string; }[]; publishedDate: string; @@ -496,7 +501,7 @@ export interface Post { updatedBy: string | Recorder; updatedAt: string; createdAt: string; - _status?: 'draft' | 'published'; + _status?: "draft" | "published"; } export interface PostThumbnail { id: string; @@ -547,11 +552,11 @@ export interface ChronologyItem { events: { source?: | { - relationTo: 'contents'; + relationTo: "contents"; value: string | Content; } | { - relationTo: 'library-items'; + relationTo: "library-items"; value: string | LibraryItem; }; translations: { @@ -574,7 +579,7 @@ export interface ChronologyItem { updatedBy: string | Recorder; updatedAt: string; createdAt: string; - _status?: 'draft' | 'published'; + _status?: "draft" | "published"; } export interface ChronologyEra { id: string; @@ -630,7 +635,7 @@ export interface Weapon { updatedBy: string | Recorder; updatedAt: string; createdAt: string; - _status?: 'draft' | 'published'; + _status?: "draft" | "published"; } export interface WeaponsThumbnail { id: string; @@ -700,7 +705,7 @@ export interface Video { id: string; uid: string; gone: boolean; - source: 'YouTube' | 'NicoNico' | 'Tumblr'; + source: "YouTube" | "NicoNico" | "Tumblr"; title: string; description?: string; likes?: number; @@ -717,7 +722,7 @@ export interface VideosChannel { export interface PayloadPreference { id: string; user: { - relationTo: 'recorders'; + relationTo: "recorders"; value: string | Recorder; }; key?: string; @@ -741,7 +746,6 @@ export interface PayloadMigration { createdAt: string; } - -declare module 'payload' { +declare module "payload" { export interface GeneratedTypes extends Config {} -} \ No newline at end of file +} diff --git a/src/utils/localApi.ts b/src/utils/localApi.ts index 5ac6ed5..0aae998 100644 --- a/src/utils/localApi.ts +++ b/src/utils/localApi.ts @@ -7,6 +7,7 @@ export const findWeaponType = async (name: string): Promise => { const key = await payload.find({ collection: Collections.Keys, where: { name: { equals: name }, type: { equals: KeysTypes.Weapons } }, + depth: 0, }); if (!key.docs[0]) throw new Error(`Weapon type ${name} wasn't found`); return key.docs[0].id; @@ -16,27 +17,40 @@ export const findCategory = async (name: string): Promise => { const key = await payload.find({ collection: Collections.Keys, where: { name: { equals: name }, type: { equals: KeysTypes.Categories } }, + depth: 0, }); if (!key.docs[0]) throw new Error(`Category ${name} wasn't found`); - return key.docs[0]?.id; + return key.docs[0].id; }; export const findRecorder = async (name: string): Promise => { const recorder = await payload.find({ collection: Collections.Recorders, where: { username: { equals: name } }, + depth: 0, }); if (!recorder.docs[0]) throw new Error(`Recorder ${name} wasn't found`); - return recorder.docs[0]?.id; + return recorder.docs[0].id; }; export const findContentType = async (name: string): Promise => { const key = await payload.find({ collection: Collections.Keys, where: { name: { equals: name }, type: { equals: KeysTypes.Contents } }, + depth: 0, }); if (!key.docs[0]) throw new Error(`Content type ${name} wasn't found`); - return key.docs[0]?.id; + return key.docs[0].id; +}; + +export const findContent = async (slug: string): Promise => { + const content = await payload.find({ + collection: Collections.Contents, + where: { slug: { equals: slug } }, + depth: 0, + }); + if (!content.docs[0]) throw new Error(`Content ${slug} wasn't found`); + return content.docs[0].id; }; type UploadStrapiImage = { @@ -58,7 +72,7 @@ export const uploadStrapiImage = async ({ if (existingImage.docs[0]) { return existingImage.docs[0].id; } - + const url = `${process.env.STRAPI_URI}${image.data.attributes.url}`; const blob = await (await fetch(url)).blob(); diff --git a/src/utils/string.ts b/src/utils/string.ts index 4337938..0276e64 100644 --- a/src/utils/string.ts +++ b/src/utils/string.ts @@ -7,7 +7,7 @@ export const shortenEllipsis = (text: string, length: number): string => export const formatLanguageCode = (code: string): string => tags(code).valid() ? tags(code).language()?.descriptions()[0] ?? code : code; -const capitalize = (string: string): string => { +export const capitalize = (string: string): string => { const [firstLetter, ...otherLetters] = string; if (isUndefined(firstLetter)) return ""; return [firstLetter.toUpperCase(), ...otherLetters].join(""); diff --git a/src/utils/versionedCollectionConfig.ts b/src/utils/versionedCollectionConfig.ts index c445d85..568e325 100644 --- a/src/utils/versionedCollectionConfig.ts +++ b/src/utils/versionedCollectionConfig.ts @@ -14,7 +14,7 @@ const updatedByField = (): RelationshipField => ({ type: "relationship", required: true, relationTo: Collections.Recorders, - admin: { readOnly: true }, + admin: { readOnly: true, hidden: true }, }); type BuildVersionedCollectionConfig = Omit;