Moved to 2.0

This commit is contained in:
DrMint 2023-10-14 11:27:32 +02:00
parent 26b4798761
commit 76b7e4a8a2
59 changed files with 2113 additions and 2405 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -9,7 +9,7 @@ services:
- .:/home/node/app
- node_modules:/home/node/app/node_modules
working_dir: /home/node/app/
command: sh -c "npm install && npm run dev"
command: sh -c "npm install && npm run generate:types && npm run dev"
depends_on:
- mongo
environment:

2174
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,21 +13,25 @@
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png}\" dist/",
"generate:types": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:types",
"generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema",
"unused-exports": "ts-unused-exports ./tsconfig.json --excludePathsFromReport='src/payload.config.ts;src/sdk.ts;src/types/collections.ts'",
"prettier": "prettier --list-different --end-of-line auto --write src",
"tsc": "tsc --noEmit",
"precommit": "npm run generate:types && npm run prettier && npm run tsc",
"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"
},
"dependencies": {
"@fontsource/vollkorn": "^5.0.14",
"@payloadcms/bundler-webpack": "^1.0.3",
"@payloadcms/db-mongodb": "^1.0.3",
"@payloadcms/richtext-lexical": "^0.1.8",
"clean-deep": "^3.4.0",
"cross-env": "^7.0.3",
"language-tags": "^1.0.9",
"luxon": "^3.4.3",
"payload": "^1.15.7",
"styled-components": "^6.0.8"
"payload": "^2.0.5",
"styled-components": "^6.0.9"
},
"devDependencies": {
"@types/dotenv": "^8.2.0",
@ -36,10 +40,12 @@
"@types/luxon": "^3.3.2",
"@types/qs": "^6.9.8",
"@types/react-router-dom": "^5.3.3",
"@types/styled-components": "^5.1.28",
"copyfiles": "^2.4.1",
"nodemon": "^3.0.1",
"prettier": "^3.0.3",
"ts-node": "^10.9.1",
"ts-unused-exports": "^10.0.1",
"typescript": "^5.2.2"
}
}

View File

@ -1,8 +0,0 @@
import { Access } from "payload/config";
import { Recorder } from "../../types/collections";
import { isDefined, isUndefined } from "../../utils/asserts";
export const mustHaveAtLeastOneRole: Access<unknown, Recorder> = ({ req: { user } }): boolean => {
if (isUndefined(user)) return false;
return isDefined(user.role) && user.role.length > 0;
};

View File

@ -1,9 +0,0 @@
import { RecordersRoles } from "../../constants";
import { Recorder } from "../../types/collections";
import { EndpointAccess } from "../../types/payload";
import { isDefined, isUndefined } from "../../utils/asserts";
export const mustBeApi: EndpointAccess<Recorder> = ({ user }) => {
if (isUndefined(user)) return false;
return isDefined(user.role) && user.role.includes(RecordersRoles.Api);
};

View File

@ -1,8 +0,0 @@
import { Recorder } from "../../types/collections";
import { EndpointAccess } from "../../types/payload";
import { isDefined, isUndefined } from "../../utils/asserts";
export const mustHaveAtLeastOneRole: EndpointAccess<Recorder> = ({ user }) => {
if (isUndefined(user)) return false;
return isDefined(user.role) && user.role.length > 0;
};

View File

@ -50,14 +50,14 @@ export const ChronologyEras: CollectionConfig = buildCollectionConfig({
type: "number",
min: 0,
required: true,
admin: { width: "50%", description: "The year the era started (year included)" },
admin: { width: "0%", description: "The year the era started (year included)" },
},
{
name: fields.endingYear,
type: "number",
min: 0,
required: true,
admin: { width: "50%", description: "The year the era ended (year included)" },
admin: { width: "0%", description: "The year the era ended (year included)" },
},
],
},

View File

@ -1,6 +1,5 @@
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { ChronologyEra } from "../../../types/collections";
import { StrapiLanguage } from "../../../types/strapi";
import { isUndefined } from "../../../utils/asserts";
@ -11,7 +10,7 @@ type StrapiChronologyEra = {
title: { title: string; language: StrapiLanguage; description?: string }[];
};
export const importFromStrapi = createStrapiImportEndpoint<ChronologyEra, StrapiChronologyEra>({
export const importFromStrapi = createStrapiImportEndpoint<StrapiChronologyEra>({
strapi: {
collection: "chronology-eras",
params: {

View File

@ -76,10 +76,22 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig(
type: "number",
required: true,
min: 0,
admin: { width: "33%" },
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%" },
},
{ name: fields.dateMonth, type: "number", min: 1, max: 12, admin: { width: "33%" } },
{ name: fields.dateDay, type: "number", min: 1, max: 31, admin: { width: "33%" } },
],
},
],

View File

@ -1,6 +1,5 @@
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { ChronologyItem } from "../../../types/collections";
import { StrapiLanguage } from "../../../types/strapi";
import { isUndefined } from "../../../utils/asserts";
@ -18,7 +17,7 @@ type StrapiChronologyItem = {
}[];
};
export const importFromStrapi = createStrapiImportEndpoint<ChronologyItem, StrapiChronologyItem>({
export const importFromStrapi = createStrapiImportEndpoint<StrapiChronologyItem>({
strapi: {
collection: "chronology-items",
params: {

View File

@ -1,81 +0,0 @@
import { BlocksConfig, generateBlocks } from "../../../utils/recursiveBlocks";
import { quoteBlock } from "./quoteBlock";
import { textBlock } from "./textBlock";
import { transcriptBlock } from "./transcriptBlock";
const enum BlockName {
Text = "Text",
Section = "Section",
Tabs = "Tabs",
Tab = "Tab",
Transcript = "Transcript",
Quote = "Quote",
}
const blocksConfig: BlocksConfig<BlockName> = {
Text: {
root: true,
block: textBlock,
},
Section: {
root: true,
block: {
slug: "section",
labels: { singular: "Section", plural: "Sections" },
recursion: {
name: "content",
condition: (depth) => depth < 5,
newDepth: (depth) => depth + 1,
blocks: [
BlockName.Section,
BlockName.Tabs,
BlockName.Transcript,
BlockName.Quote,
BlockName.Text,
],
},
},
},
Tabs: {
root: true,
block: {
slug: "tabs",
labels: { singular: "Tabs", plural: "Tabs" },
recursion: {
name: "tabs",
newDepth: (depth) => depth,
condition: (depth, parents) => !parents.includes(BlockName.Tabs) && depth < 5,
blocks: [BlockName.Tab],
},
},
},
Tab: {
root: false,
block: {
slug: "tab",
labels: { singular: "Tab", plural: "Tabs" },
recursion: {
name: "content",
condition: (depth) => depth < 5,
newDepth: (depth) => depth + 1,
blocks: [
BlockName.Section,
BlockName.Tabs,
BlockName.Transcript,
BlockName.Quote,
BlockName.Text,
],
},
},
},
Transcript: {
root: true,
block: transcriptBlock,
},
Quote: {
root: true,
block: quoteBlock,
},
};
export const contentBlocks = generateBlocks(blocksConfig);

View File

@ -10,11 +10,6 @@ export const lineBlock: Block = {
label: false,
type: "richText",
required: true,
admin: {
hideGutter: true,
elements: [],
leaves: ["bold", "italic", "underline", "strikethrough", "code"],
},
},
],
};

View File

@ -1,24 +0,0 @@
import { Block } from "payload/types";
export const quoteBlock: Block = {
slug: "quoteBlock",
interfaceName: "QuoteBlock",
labels: { singular: "Quote", plural: "Quotes" },
fields: [
{
name: "from",
type: "text",
required: true,
},
{
name: "content",
type: "richText",
label: false,
required: true,
admin: {
hideGutter: true,
elements: [],
},
},
],
};

View File

@ -1,19 +0,0 @@
import { Block } from "payload/types";
export const textBlock: Block = {
slug: "textBlock",
interfaceName: "TextBlock",
labels: { singular: "Text", plural: "Texts" },
fields: [
{
name: "content",
type: "richText",
label: false,
required: true,
admin: {
hideGutter: true,
elements: ["ul", "ol", "indent", "link", "relationship", "upload", "blockquote"],
},
},
],
};

View File

@ -9,7 +9,6 @@ import { beforeDuplicatePiping } from "../../hooks/beforeDuplicatePiping";
import { beforeDuplicateUnpublish } from "../../hooks/beforeDuplicateUnpublish";
import { isDefined } from "../../utils/asserts";
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
import { contentBlocks } from "./Blocks/blocks";
const fields = {
slug: "slug",
@ -66,11 +65,11 @@ export const Contents = buildVersionedCollectionConfig({
{
type: "row",
fields: [
slugField({ name: fields.slug, admin: { width: "50%" } }),
slugField({ name: fields.slug, admin: { width: "0%" } }),
imageField({
name: fields.thumbnail,
relationTo: Collections.ContentsThumbnails,
admin: { width: "50%" },
admin: { width: "0%" },
}),
],
},
@ -81,12 +80,12 @@ export const Contents = buildVersionedCollectionConfig({
name: fields.categories,
relationTo: KeysTypes.Categories,
hasMany: true,
admin: { allowCreate: false, width: "50%" },
admin: { allowCreate: false, width: "0%" },
}),
keysField({
name: fields.type,
relationTo: KeysTypes.Contents,
admin: { allowCreate: false, width: "50%" },
admin: { allowCreate: false, width: "0%" },
}),
],
},
@ -115,6 +114,7 @@ export const Contents = buildVersionedCollectionConfig({
{
label: "Text",
fields: [
{ name: fields.textContent, type: "richText" },
{
type: "row",
fields: [
@ -127,7 +127,7 @@ export const Contents = buildVersionedCollectionConfig({
admin: {
condition: (_, siblingData) =>
siblingData.language === siblingData.sourceLanguage,
width: "50%",
width: "0%",
},
},
{
@ -139,7 +139,7 @@ export const Contents = buildVersionedCollectionConfig({
admin: {
condition: (_, siblingData) =>
siblingData.language !== siblingData.sourceLanguage,
width: "50%",
width: "0%",
},
},
{
@ -148,18 +148,10 @@ export const Contents = buildVersionedCollectionConfig({
type: "relationship",
relationTo: Collections.Recorders,
hasMany: true,
admin: { width: "50%" },
admin: { width: "0%" },
},
],
},
{
name: fields.textContent,
label: "Content",
labels: { singular: "Block", plural: "Blocks" },
type: "blocks",
admin: { initCollapsed: true },
blocks: contentBlocks,
},
{
name: fields.textNotes,
label: "Notes",
@ -176,13 +168,13 @@ export const Contents = buildVersionedCollectionConfig({
fileField({
name: fields.video,
relationTo: FileTypes.ContentVideo,
admin: { width: "50%" },
admin: { width: "0%" },
}),
{
name: fields.videoNotes,
label: "Notes",
type: "textarea",
admin: { width: "50%" },
admin: { width: "0%" },
},
],
},
@ -197,13 +189,13 @@ export const Contents = buildVersionedCollectionConfig({
fileField({
name: fields.audio,
relationTo: FileTypes.ContentAudio,
admin: { width: "50%" },
admin: { width: "0%" },
}),
{
name: fields.audioNotes,
label: "Notes",
type: "textarea",
admin: { width: "50%" },
admin: { width: "0%" },
},
],
},

View File

@ -44,14 +44,14 @@ export const ContentsFolders = buildCollectionConfig({
name: fields.subfolders,
relationTo: Collections.ContentsFolders,
hasMany: true,
admin: { width: "50%" },
admin: { width: "0%" },
},
{
type: "relationship",
name: fields.contents,
relationTo: Collections.Contents,
hasMany: true,
admin: { width: "50%" },
admin: { width: "0%" },
},
],
},

View File

@ -1,13 +1,12 @@
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Language } from "../../../types/collections";
type StrapiLanguage = {
code: string;
name: string;
};
export const importFromStrapi = createStrapiImportEndpoint<Language, StrapiLanguage>({
export const importFromStrapi = createStrapiImportEndpoint<StrapiLanguage>({
strapi: {
collection: "currencies",
params: {},

View File

@ -102,7 +102,7 @@ export const Keys = buildCollectionConfig({
name: fields.translationsName,
type: "text",
required: true,
admin: { width: "50%" },
admin: { width: "0%" },
},
{
name: fields.translationsShort,
@ -110,7 +110,7 @@ export const Keys = buildCollectionConfig({
admin: {
condition: (data: Partial<Key>) =>
isDefined(data.type) && keysTypesWithShort.includes(data.type),
width: "50%",
width: "0%",
},
},
],

View File

@ -5,51 +5,45 @@ import {
importStrapiEntries,
} from "../../../endpoints/createStrapiImportEndpoint";
import { Key } from "../../../types/collections";
import { CollectionEndpoint, PayloadCreateData } from "../../../types/payload";
import { CollectionEndpoint } from "../../../types/payload";
import { StrapiLanguage } from "../../../types/strapi";
import { isDefined, isUndefined } from "../../../utils/asserts";
import { formatToCamelCase } from "../../../utils/string";
const importStrapiWordings: typeof importStrapiEntries = async ({
payload: payloadParams,
strapi: strapiParams,
user,
}) => {
const rawEntries = await getAllStrapiEntries<any>(strapiParams.collection, strapiParams.params);
const importStrapiWordings: typeof importStrapiEntries = async ({ strapi: strapiParams, user }) => {
const rawEntries = await getAllStrapiEntries(strapiParams.collection, strapiParams.params);
const { ui_language, createdAt, updatedAt, ...otherKeys } = rawEntries[0].attributes;
const entries: PayloadCreateData<Key>[] = Object.keys(otherKeys).map((key) => ({
name: formatToCamelCase(key),
type: "Wordings",
translations: rawEntries
.map((entry) => ({
language: entry.attributes.ui_language.data.attributes.code,
name: entry.attributes[key],
}))
.filter(({ name }) => isDefined(name) && name !== ""),
}));
const errors: string[] = [];
await Promise.all(
entries.map(async (entry) => {
Object.keys(otherKeys).map(async (key) => {
try {
await payload.create({
collection: payloadParams.collection,
data: entry,
collection: Collections.Keys,
data: {
name: formatToCamelCase(key),
type: "Wordings",
translations: rawEntries
.map((entry) => ({
language: entry.attributes.ui_language.data.attributes.code,
name: entry.attributes[key],
}))
.filter(({ name }) => isDefined(name) && name !== ""),
},
user,
});
} catch (e) {
console.warn(e);
if (typeof e === "object" && isDefined(e) && "name" in e) {
errors.push(`${e.name} with ${entry.name}`);
errors.push(`${e.name} with ${key}`);
}
}
})
);
return { count: entries.length, errors };
return { count: Object.keys(otherKeys).length, errors };
};
export const importFromStrapi: CollectionEndpoint = {
@ -71,289 +65,271 @@ export const importFromStrapi: CollectionEndpoint = {
titles: { title?: string; short?: string; language: StrapiLanguage }[];
};
const { count: categoriesCount, errors: categoriesErrors } = await importStrapiEntries<
Key,
StrapiCategories
>({
strapi: {
collection: "categories",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "Categories",
translations: titles.map(({ title, short, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
if (isUndefined(title))
throw new Error("A title is required for a Keys title translation");
return {
name: title,
short,
language: language.data.attributes.code,
};
const { count: categoriesCount, errors: categoriesErrors } =
await importStrapiEntries<StrapiCategories>({
strapi: {
collection: "categories",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "Categories",
translations: titles.map(({ title, short, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
if (isUndefined(title))
throw new Error("A title is required for a Keys title translation");
return {
name: title,
short,
language: language.data.attributes.code,
};
}),
}),
}),
},
user: req.user,
});
},
user: req.user,
});
type StrapiContentType = {
slug: string;
titles: { title: string; language: StrapiLanguage }[];
};
const { count: contentTypesCount, errors: contentTypesErrors } = await importStrapiEntries<
Key,
StrapiContentType
>({
strapi: {
collection: "content-types",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "Contents",
translations: titles.map(({ title, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
return {
name: title,
language: language.data.attributes.code,
};
const { count: contentTypesCount, errors: contentTypesErrors } =
await importStrapiEntries<StrapiContentType>({
strapi: {
collection: "content-types",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "Contents",
translations: titles.map(({ title, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
return {
name: title,
language: language.data.attributes.code,
};
}),
}),
}),
},
user: req.user,
});
},
user: req.user,
});
type StrapiGamePlatform = {
slug: string;
titles: { title?: string; short?: string; language: StrapiLanguage }[];
};
const { count: gamePlatformsCount, errors: gamePlatformsErrors } = await importStrapiEntries<
Key,
StrapiGamePlatform
>({
strapi: {
collection: "game-platforms",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "GamePlatforms",
translations: titles.map(({ title, short, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
if (isUndefined(title))
throw new Error("A title is required for a Keys title translation");
return {
name: title,
short,
language: language.data.attributes.code,
};
const { count: gamePlatformsCount, errors: gamePlatformsErrors } =
await importStrapiEntries<StrapiGamePlatform>({
strapi: {
collection: "game-platforms",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "GamePlatforms",
translations: titles.map(({ title, short, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
if (isUndefined(title))
throw new Error("A title is required for a Keys title translation");
return {
name: title,
short,
language: language.data.attributes.code,
};
}),
}),
}),
},
user: req.user,
});
},
user: req.user,
});
type StrapiMetadataTypes = {
slug: string;
titles: { title: string; language: StrapiLanguage }[];
};
const { count: libraryCount, errors: libraryErrors } = await importStrapiEntries<
Key,
StrapiMetadataTypes
>({
strapi: {
collection: "metadata-types",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "Library",
translations: titles.map(({ title, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
return {
name: title,
language: language.data.attributes.code,
};
const { count: libraryCount, errors: libraryErrors } =
await importStrapiEntries<StrapiMetadataTypes>({
strapi: {
collection: "metadata-types",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "Library",
translations: titles.map(({ title, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
return {
name: title,
language: language.data.attributes.code,
};
}),
}),
}),
},
user: req.user,
});
},
user: req.user,
});
type StrapiAudioSubtypes = {
slug: string;
titles: { title: string; language: StrapiLanguage }[];
};
const { count: libraryAudioCount, errors: libraryAudioErrors } = await importStrapiEntries<
Key,
StrapiAudioSubtypes
>({
strapi: {
collection: "audio-subtypes",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "LibraryAudio",
translations: titles.map(({ title, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
return {
name: title,
language: language.data.attributes.code,
};
const { count: libraryAudioCount, errors: libraryAudioErrors } =
await importStrapiEntries<StrapiAudioSubtypes>({
strapi: {
collection: "audio-subtypes",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "LibraryAudio",
translations: titles.map(({ title, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
return {
name: title,
language: language.data.attributes.code,
};
}),
}),
}),
},
user: req.user,
});
},
user: req.user,
});
type StrapiGroupSubtypes = {
slug: string;
titles: { title: string; language: StrapiLanguage }[];
};
const { count: libraryGroupCount, errors: libraryGroupErrors } = await importStrapiEntries<
Key,
StrapiGroupSubtypes
>({
strapi: {
collection: "group-subtypes",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "LibraryGroup",
translations: titles.map(({ title, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
return {
name: title,
language: language.data.attributes.code,
};
const { count: libraryGroupCount, errors: libraryGroupErrors } =
await importStrapiEntries<StrapiGroupSubtypes>({
strapi: {
collection: "group-subtypes",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "LibraryGroup",
translations: titles.map(({ title, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
return {
name: title,
language: language.data.attributes.code,
};
}),
}),
}),
},
user: req.user,
});
},
user: req.user,
});
type StrapiTextualSubtypes = {
slug: string;
titles: { title: string; language: StrapiLanguage }[];
};
const { count: libraryTextualCount, errors: libraryTextualErrors } = await importStrapiEntries<
Key,
StrapiTextualSubtypes
>({
strapi: {
collection: "textual-subtypes",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "LibraryTextual",
translations: titles.map(({ title, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
return {
name: title,
language: language.data.attributes.code,
};
const { count: libraryTextualCount, errors: libraryTextualErrors } =
await importStrapiEntries<StrapiTextualSubtypes>({
strapi: {
collection: "textual-subtypes",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "LibraryTextual",
translations: titles.map(({ title, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
return {
name: title,
language: language.data.attributes.code,
};
}),
}),
}),
},
user: req.user,
});
},
user: req.user,
});
type StrapiVideoSubtypes = {
slug: string;
titles: { title: string; language: StrapiLanguage }[];
};
const { count: libraryVideoCount, errors: libraryVideoErrors } = await importStrapiEntries<
Key,
StrapiVideoSubtypes
>({
strapi: {
collection: "video-subtypes",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "LibraryVideo",
translations: titles.map(({ title, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
return {
name: title,
language: language.data.attributes.code,
};
const { count: libraryVideoCount, errors: libraryVideoErrors } =
await importStrapiEntries<StrapiVideoSubtypes>({
strapi: {
collection: "video-subtypes",
params: { populate: { titles: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, titles }) => ({
name: slug,
type: "LibraryVideo",
translations: titles.map(({ title, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
return {
name: title,
language: language.data.attributes.code,
};
}),
}),
}),
},
user: req.user,
});
},
user: req.user,
});
type StrapiWeaponTypes = {
slug: string;
translations: { name?: string; language: StrapiLanguage }[];
};
const { count: weaponsCount, errors: weaponsErrors } = await importStrapiEntries<
Key,
StrapiWeaponTypes
>({
strapi: {
collection: "weapon-story-types",
params: { populate: { translations: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, translations }) => ({
name: slug,
type: "Weapons",
translations: translations.map(({ name, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
if (isUndefined(name))
throw new Error("A name is required for a Keys title translation");
return {
name,
language: language.data.attributes.code,
};
const { count: weaponsCount, errors: weaponsErrors } =
await importStrapiEntries<StrapiWeaponTypes>({
strapi: {
collection: "weapon-story-types",
params: { populate: { translations: { populate: "language" } } },
},
payload: {
collection: Collections.Keys,
convert: ({ slug, translations }) => ({
name: slug,
type: "Weapons",
translations: translations.map(({ name, language }) => {
if (isUndefined(language.data))
throw new Error("A language is required for a Keys title translation");
if (isUndefined(name))
throw new Error("A name is required for a Keys title translation");
return {
name,
language: language.data.attributes.code,
};
}),
}),
}),
},
user: req.user,
});
},
user: req.user,
});
const { count: wordingsCount, errors: wordingsErrors } = await importStrapiWordings<Key, Key>({
const { count: wordingsCount, errors: wordingsErrors } = await importStrapiWordings<Key>({
strapi: { collection: "website-interfaces", params: { populate: "ui_language" } },
payload: { collection: Collections.Keys, convert: (strapiObject) => strapiObject },
user: req.user,

View File

@ -1,13 +1,12 @@
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Language } from "../../../types/collections";
type StrapiLanguage = {
name: string;
code: string;
};
export const importFromStrapi = createStrapiImportEndpoint<Language, StrapiLanguage>({
export const importFromStrapi = createStrapiImportEndpoint<StrapiLanguage>({
strapi: {
collection: "languages",
params: {},

View File

@ -170,12 +170,10 @@ export const LibraryItems = buildVersionedCollectionConfig({
fields: [
slugField({
name: fields.slug,
admin: { width: "50%" },
}),
imageField({
name: fields.thumbnail,
relationTo: Collections.LibraryItemsThumbnails,
admin: { width: "50%" },
}),
],
},
@ -197,7 +195,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
defaultValue: true,
admin: {
description: "Only items that can be sold separetely should be root items.",
width: "25%",
width: "0%",
},
},
{
@ -208,7 +206,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
admin: {
description:
"A primary item is an official item that focuses primarly on one or more of our Categories.",
width: "25%",
width: "0%",
},
},
{
@ -219,7 +217,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
admin: {
description:
"The item is the digital version of another item, or the item is sold only digitally.",
width: "25%",
width: "0%",
},
},
{
@ -229,7 +227,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
defaultValue: false,
admin: {
description: "Are the scans available for download?",
width: "25%",
width: "0%",
},
},
],
@ -265,17 +263,17 @@ export const LibraryItems = buildVersionedCollectionConfig({
imageField({
name: fields.scansCoverFront,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansCoverSpine,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansCoverBack,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
],
},
@ -285,12 +283,12 @@ export const LibraryItems = buildVersionedCollectionConfig({
imageField({
name: fields.scansCoverInsideFront,
relationTo: Collections.LibraryItemsScans,
admin: { width: "50%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansCoverBack,
relationTo: Collections.LibraryItemsScans,
admin: { width: "50%" },
admin: { width: "0%" },
}),
],
},
@ -300,22 +298,22 @@ export const LibraryItems = buildVersionedCollectionConfig({
imageField({
name: fields.scansCoverFlapFront,
relationTo: Collections.LibraryItemsScans,
admin: { width: "25%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansCoverFlapBack,
relationTo: Collections.LibraryItemsScans,
admin: { width: "25%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansCoverInsideFlapFront,
relationTo: Collections.LibraryItemsScans,
admin: { width: "25%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansCoverInsideFlapBack,
relationTo: Collections.LibraryItemsScans,
admin: { width: "25%" },
admin: { width: "0%" },
}),
],
},
@ -337,17 +335,17 @@ export const LibraryItems = buildVersionedCollectionConfig({
imageField({
name: fields.scansDustjacketFront,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansDustjacketSpine,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansDustjacketBack,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
],
},
@ -357,17 +355,17 @@ export const LibraryItems = buildVersionedCollectionConfig({
imageField({
name: fields.scansDustjacketInsideFront,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansDustjacketInsideSpine,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansDustjacketInsideBack,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
],
},
@ -377,22 +375,22 @@ export const LibraryItems = buildVersionedCollectionConfig({
imageField({
name: fields.scansDustjacketFlapFront,
relationTo: Collections.LibraryItemsScans,
admin: { width: "25%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansDustjacketFlapBack,
relationTo: Collections.LibraryItemsScans,
admin: { width: "25%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansDustjacketInsideFlapFront,
relationTo: Collections.LibraryItemsScans,
admin: { width: "25%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansDustjacketInsideFlapBack,
relationTo: Collections.LibraryItemsScans,
admin: { width: "25%" },
admin: { width: "0%" },
}),
],
},
@ -414,17 +412,17 @@ export const LibraryItems = buildVersionedCollectionConfig({
imageField({
name: fields.scansObiFront,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansObiSpine,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansObiBack,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
],
},
@ -434,17 +432,17 @@ export const LibraryItems = buildVersionedCollectionConfig({
imageField({
name: fields.scansObiInsideFront,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansObiInsideSpine,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansObiInsideBack,
relationTo: Collections.LibraryItemsScans,
admin: { width: "33%" },
admin: { width: "0%" },
}),
],
},
@ -454,22 +452,22 @@ export const LibraryItems = buildVersionedCollectionConfig({
imageField({
name: fields.scansObiFlapFront,
relationTo: Collections.LibraryItemsScans,
admin: { width: "25%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansObiFlapBack,
relationTo: Collections.LibraryItemsScans,
admin: { width: "25%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansObiInsideFlapFront,
relationTo: Collections.LibraryItemsScans,
admin: { width: "25%" },
admin: { width: "0%" },
}),
imageField({
name: fields.scansObiInsideFlapBack,
relationTo: Collections.LibraryItemsScans,
admin: { width: "25%" },
admin: { width: "0%" },
}),
],
},
@ -495,13 +493,13 @@ export const LibraryItems = buildVersionedCollectionConfig({
name: fields.scansPagesPage,
type: "number",
required: true,
admin: { width: "33%" },
admin: { width: "0%" },
},
imageField({
name: fields.scansPagesImage,
relationTo: Collections.LibraryItemsScans,
required: true,
admin: { width: "66%" },
admin: { width: "0%" },
}),
],
},
@ -518,6 +516,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
{
name: fields.textual,
type: "group",
label: false,
admin: {
condition: (data: Partial<LibraryItem>) =>
data.itemType === LibraryItemsTypes.Textual,
@ -530,14 +529,14 @@ export const LibraryItems = buildVersionedCollectionConfig({
name: fields.textualSubtype,
relationTo: KeysTypes.LibraryTextual,
hasMany: true,
admin: { allowCreate: false, width: "50%" },
admin: { allowCreate: false, width: "0%" },
}),
{
name: fields.textualLanguages,
type: "relationship",
relationTo: Collections.Languages,
hasMany: true,
admin: { allowCreate: false, width: "50%" },
admin: { allowCreate: false, width: "0%" },
},
],
},
@ -548,7 +547,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
name: fields.textualPageCount,
type: "number",
min: 1,
admin: { width: "33%" },
admin: { width: "0%" },
},
{
name: fields.textualBindingType,
@ -561,7 +560,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
),
admin: {
layout: "horizontal",
width: "33%",
width: "0%",
},
},
{
@ -575,7 +574,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
),
admin: {
layout: "horizontal",
width: "33%",
width: "0%",
},
},
],
@ -585,6 +584,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
{
name: fields.audio,
type: "group",
label: false,
admin: {
condition: (data: Partial<LibraryItem>) =>
data.itemType === LibraryItemsTypes.Audio,
@ -597,7 +597,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
name: fields.audioSubtype,
relationTo: KeysTypes.LibraryAudio,
hasMany: true,
admin: { allowCreate: false, width: "50%" },
admin: { allowCreate: false, width: "0%" },
}),
],
},
@ -612,13 +612,13 @@ export const LibraryItems = buildVersionedCollectionConfig({
name: fields.audioTracksTitle,
type: "text",
required: true,
admin: { width: "50%" },
admin: { width: "0%" },
},
fileField({
name: fields.audioTracksFile,
relationTo: FileTypes.LibrarySoundtracks,
required: true,
admin: { width: "50%" },
admin: { width: "0%" },
}),
],
},
@ -639,21 +639,21 @@ export const LibraryItems = buildVersionedCollectionConfig({
type: "date",
admin: {
date: { pickerAppearance: "dayOnly", displayFormat: "yyyy-MM-dd" },
width: "50%",
width: "0%",
},
},
keysField({
name: fields.categories,
relationTo: KeysTypes.Categories,
hasMany: true,
admin: { allowCreate: false, width: "50%" },
admin: { allowCreate: false, width: "0%" },
}),
],
},
translatedFields({
name: fields.translations,
label: "Descriptions",
admin: { initCollapsed: true },
admin: { initCollapsed: true, useAsTitle: fields.translationsDescription },
fields: [{ name: fields.translationsDescription, type: "textarea", required: true }],
}),
optionalGroupField({
@ -667,18 +667,18 @@ export const LibraryItems = buildVersionedCollectionConfig({
name: fields.width,
type: "number",
required: true,
admin: { step: 1, width: "33%", description: "in mm." },
admin: { step: 1, width: "0%", description: "in mm." },
},
{
name: fields.height,
type: "number",
required: true,
admin: { step: 1, width: "33%", description: "in mm." },
admin: { step: 1, width: "0%", description: "in mm." },
},
{
name: fields.thickness,
type: "number",
admin: { step: 1, width: "33%", description: "in mm." },
admin: { step: 1, width: "0%", description: "in mm." },
},
],
},
@ -686,7 +686,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
}),
optionalGroupField({
name: fields.price,
admin: { className: "group-array", width: "50%" },
admin: { className: "group-array", width: "0%" },
fields: [
{
type: "row",
@ -696,14 +696,14 @@ export const LibraryItems = buildVersionedCollectionConfig({
type: "number",
required: true,
min: 0,
admin: { width: "50%" },
admin: { width: "0%" },
},
{
name: fields.priceCurrency,
type: "relationship",
relationTo: Collections.Currencies,
required: true,
admin: { allowCreate: false, width: "50%" },
admin: { allowCreate: false, width: "0%" },
},
],
},
@ -715,7 +715,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
type: "array",
admin: {
description: "Links to official websites where to get/buy the item.",
width: "50%",
width: "0%",
},
fields: [{ name: fields.urlsUrl, type: "text", required: true }],
},

View File

@ -1,5 +1,5 @@
import React from "react";
import { styled } from "styled-components";
import styled from "styled-components";
import { isDefined } from "../../../utils/asserts";
interface Props {

View File

@ -60,11 +60,11 @@ export const Posts = buildVersionedCollectionConfig({
{
type: "row",
fields: [
slugField({ name: fields.slug, admin: { width: "50%" } }),
slugField({ name: fields.slug, admin: { width: "0%" } }),
imageField({
name: fields.thumbnail,
relationTo: Collections.PostsThumbnails,
admin: { width: "50%" },
admin: { width: "0%" },
}),
],
},
@ -78,7 +78,7 @@ export const Posts = buildVersionedCollectionConfig({
required: true,
minRows: 1,
hasMany: true,
admin: { width: "35%" },
admin: { width: "0%" },
},
{
name: fields.categories,
@ -86,7 +86,7 @@ export const Posts = buildVersionedCollectionConfig({
relationTo: [Collections.Keys],
filterOptions: { type: { equals: KeysTypes.Categories } },
hasMany: true,
admin: { allowCreate: false, width: "35%" },
admin: { allowCreate: false, width: "0%" },
},
],
},
@ -125,7 +125,7 @@ export const Posts = buildVersionedCollectionConfig({
}
return siblingData.language !== siblingData.sourceLanguage;
},
width: "50%",
width: "0%",
},
validate: (translators, { siblingData }) => {
if (isUndefined(siblingData.language) || isUndefined(siblingData.sourceLanguage)) {
@ -145,11 +145,11 @@ export const Posts = buildVersionedCollectionConfig({
type: "relationship",
relationTo: Collections.Recorders,
hasMany: true,
admin: { width: "50%" },
admin: { width: "0%" },
},
],
},
{ name: fields.content, type: "richText", admin: { hideGutter: true } },
{ name: fields.content, type: "richText" },
],
}),
{

View File

@ -84,12 +84,12 @@ export const Recorders = buildCollectionConfig({
type: "text",
unique: true,
required: true,
admin: { description: "The username must be unique", width: "33%" },
admin: { description: "The username must be unique", width: "0%" },
},
imageField({
name: fields.avatar,
relationTo: Collections.RecordersThumbnails,
admin: { width: "66%" },
admin: { width: "0%" },
}),
],
},

View File

@ -2,7 +2,6 @@ import payload from "payload";
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Recorder } from "../../../types/collections";
import { PayloadCreateData } from "../../../types/payload";
import { StrapiImage, StrapiLanguage } from "../../../types/strapi";
import { isDefined, isUndefined } from "../../../utils/asserts";
import { uploadStrapiImage } from "../../../utils/localApi";
@ -16,7 +15,7 @@ type StrapiRecorder = {
bio: { language: StrapiLanguage; bio?: string }[];
};
export const importFromStrapi = createStrapiImportEndpoint<Recorder, StrapiRecorder>({
export const importFromStrapi = createStrapiImportEndpoint<StrapiRecorder>({
strapi: {
collection: "recorders",
params: {
@ -31,22 +30,6 @@ export const importFromStrapi = createStrapiImportEndpoint<Recorder, StrapiRecor
image: avatar,
});
const data: Omit<PayloadCreateData<Recorder>, "password" | "email"> = {
username,
anonymize,
languages: languages.data?.map((language) => language.attributes.code),
avatar: avatarId,
biographies: bios?.map(({ language, bio }) => {
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 {
language: language.data.attributes.code,
biography: bio,
};
}),
};
const recorder = (
await payload.find({
collection: Collections.Recorders,
@ -55,12 +38,44 @@ export const importFromStrapi = createStrapiImportEndpoint<Recorder, StrapiRecor
).docs[0] as Recorder | undefined;
if (isDefined(recorder)) {
await payload.update({ collection: Collections.Recorders, id: recorder.id, data, user });
await payload.update({
collection: Collections.Recorders,
id: recorder.id,
data: {
username,
anonymize,
languages: languages.data?.map((language) => language.attributes.code),
avatar: avatarId,
biographies: bios?.map(({ language, bio }) => {
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 {
language: language.data.attributes.code,
biography: bio,
};
}),
},
user,
});
} else {
await payload.create({
collection: Collections.Recorders,
data: {
...data,
username,
anonymize,
languages: languages.data?.map((language) => language.attributes.code),
avatar: avatarId,
biographies: bios?.map(({ language, bio }) => {
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 {
language: language.data.attributes.code,
biography: bio,
};
}),
email: `${anonymous_code}@accords-library.com`,
password: process.env.RECORDER_DEFAULT_PASSWORD,
},

View File

@ -50,7 +50,7 @@ export const Videos: CollectionConfig = buildCollectionConfig({
{
type: "row",
fields: [
{ name: fields.uid, type: "text", required: true, unique: true, admin: { width: "33%" } },
{ name: fields.uid, type: "text", required: true, unique: true, admin: { width: "0%" } },
{
name: fields.gone,
type: "checkbox",
@ -59,7 +59,7 @@ export const Videos: CollectionConfig = buildCollectionConfig({
admin: {
description:
"Is the video no longer available (deleted, privatized, unlisted, blocked...)",
width: "33%",
width: "0%",
},
},
{
@ -67,7 +67,7 @@ export const Videos: CollectionConfig = buildCollectionConfig({
type: "select",
required: true,
options: Object.entries(VideoSources).map(([value, label]) => ({ label, value })),
admin: { width: "33%" },
admin: { width: "0%" },
},
],
},
@ -77,8 +77,8 @@ export const Videos: CollectionConfig = buildCollectionConfig({
{
type: "row",
fields: [
{ name: fields.likes, type: "number", admin: { width: "50%" } },
{ name: fields.views, type: "number", admin: { width: "50%" } },
{ name: fields.likes, type: "number", admin: { width: "0%" } },
{ name: fields.views, type: "number", admin: { width: "0%" } },
],
},
{

View File

@ -1,8 +1,6 @@
import payload from "payload";
import { Collections, VideoSources } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Video, VideosChannel } from "../../../types/collections";
import { PayloadCreateData } from "../../../types/payload";
import { isDefined, isUndefined } from "../../../utils/asserts";
type StapiVideo = {
@ -21,7 +19,7 @@ type StapiVideo = {
channel: { data?: { attributes: { uid: string; title: string; subscribers: number } } };
};
export const importFromStrapi = createStrapiImportEndpoint<Video, StapiVideo>({
export const importFromStrapi = createStrapiImportEndpoint<StapiVideo>({
strapi: {
collection: "videos",
params: { populate: "published_date,channel" },
@ -49,14 +47,13 @@ export const importFromStrapi = createStrapiImportEndpoint<Video, StapiVideo>({
let videoChannelId;
if (isDefined(channel.data)) {
try {
const videoChannel: PayloadCreateData<VideosChannel> = {
uid: channel.data.attributes.uid,
title: channel.data.attributes.title,
subscribers: channel.data.attributes.subscribers,
};
await payload.create({
collection: Collections.VideosChannels,
data: videoChannel,
data: {
uid: channel.data.attributes.uid,
title: channel.data.attributes.title,
subscribers: channel.data.attributes.subscribers,
},
user,
});
} catch (e) {}
@ -66,26 +63,24 @@ export const importFromStrapi = createStrapiImportEndpoint<Video, StapiVideo>({
where: { uid: { equals: channel.data.attributes.uid } },
});
if (result.docs.length > 0) {
if (result.docs[0]) {
videoChannelId = result.docs[0].id;
}
}
const video: PayloadCreateData<Video> = {
uid,
title,
description,
views,
likes,
gone,
source,
publishedDate: `${year}-${month}-${day}`,
channel: videoChannelId,
};
await payload.create({
collection: Collections.Videos,
data: video,
data: {
uid,
title,
description,
views,
likes,
gone,
source,
publishedDate: `${year}-${month}-${day}`,
channel: videoChannelId,
},
user,
});
},

View File

@ -1,6 +1,5 @@
import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types";
import { CollectionGroups, Collections, KeysTypes } from "../../constants";
import { createGetSlugsEndpoint } from "../../endpoints/createGetSlugsEndpoint";
import { imageField } from "../../fields/imageField/imageField";
import { keysField } from "../../fields/keysField/keysField";
import { slugField } from "../../fields/slugField/slugField";
@ -43,20 +42,16 @@ export const Weapons = buildVersionedCollectionConfig({
],
group: CollectionGroups.Collections,
},
endpoints: [
importFromStrapi,
createGetSlugsEndpoint(Collections.Weapons),
getBySlugEndpoint,
],
endpoints: [importFromStrapi, getBySlugEndpoint],
fields: [
{
type: "row",
fields: [
slugField({ name: fields.slug, admin: { width: "50%" } }),
slugField({ name: fields.slug, admin: { width: "0%" } }),
imageField({
name: fields.thumbnail,
relationTo: Collections.WeaponsThumbnails,
admin: { width: "50%" },
admin: { width: "0%" },
}),
],
},
@ -67,13 +62,13 @@ export const Weapons = buildVersionedCollectionConfig({
name: fields.type,
relationTo: KeysTypes.Weapons,
required: true,
admin: { allowCreate: false, width: "50%" },
admin: { allowCreate: false, width: "0%" },
}),
{
name: fields.group,
type: "relationship",
relationTo: Collections.WeaponsGroups,
admin: { width: "50%" },
admin: { width: "0%" },
},
],
},
@ -114,12 +109,12 @@ export const Weapons = buildVersionedCollectionConfig({
name: fields.appearancesTranslationsName,
type: "text",
required: true,
admin: { width: "50%" },
admin: { width: "0%" },
},
{
name: fields.appearancesTranslationsDescription,
type: "textarea",
admin: { width: "50%" },
admin: { width: "0%" },
},
],
},
@ -130,13 +125,13 @@ export const Weapons = buildVersionedCollectionConfig({
name: fields.appearancesTranslationsLevel1,
label: "Level 1",
type: "textarea",
admin: { width: "50%" },
admin: { width: "0%" },
},
{
name: fields.appearancesTranslationsLevel2,
label: "Level 2",
type: "textarea",
admin: { width: "50%" },
admin: { width: "0%" },
},
],
},
@ -147,13 +142,13 @@ export const Weapons = buildVersionedCollectionConfig({
name: fields.appearancesTranslationsLevel3,
label: "Level 3",
type: "textarea",
admin: { width: "50%" },
admin: { width: "0%" },
},
{
name: fields.appearancesTranslationsLevel4,
label: "Level 4",
type: "textarea",
admin: { width: "50%" },
admin: { width: "0%" },
},
],
},

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from "react";
import { styled } from "styled-components";
import styled from "styled-components";
import { Collections } from "../../../constants";
interface Props {

View File

@ -1,8 +1,6 @@
import payload from "payload";
import { Collections } from "../../../constants";
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
import { Weapon, WeaponsGroup } from "../../../types/collections";
import { PayloadCreateData } from "../../../types/payload";
import { StrapiImage, StrapiLanguage } from "../../../types/strapi";
import { isDefined, isUndefined } from "../../../utils/asserts";
import { findCategory, findWeaponType, uploadStrapiImage } from "../../../utils/localApi";
@ -26,7 +24,7 @@ type StrapiWeapon = {
}[];
};
export const importFromStrapi = createStrapiImportEndpoint<Weapon, StrapiWeapon>({
export const importFromStrapi = createStrapiImportEndpoint<StrapiWeapon>({
strapi: {
collection: "weapon-stories",
params: {
@ -46,12 +44,11 @@ export const importFromStrapi = createStrapiImportEndpoint<Weapon, StrapiWeapon>
let groupId: string | undefined;
if (isDefined(weapon_group.data)) {
try {
const groupData: PayloadCreateData<WeaponsGroup> = {
slug: weapon_group.data.attributes.slug,
};
await payload.create({
collection: Collections.WeaponsGroups,
data: groupData,
data: {
slug: weapon_group.data.attributes.slug,
},
user,
});
} catch (e) {}
@ -61,7 +58,7 @@ export const importFromStrapi = createStrapiImportEndpoint<Weapon, StrapiWeapon>
where: { slug: { equals: weapon_group.data.attributes.slug } },
});
if (result.docs.length > 0) {
if (result.docs[0]) {
groupId = result.docs[0].id;
}
}
@ -73,43 +70,46 @@ export const importFromStrapi = createStrapiImportEndpoint<Weapon, StrapiWeapon>
if (isUndefined(type.data)) throw new Error("A type is required to create a Weapon");
const data: PayloadCreateData<Weapon> = {
slug,
type: await findWeaponType(type.data.attributes.slug),
group: groupId,
thumbnail: thumbnailId,
appearances: await Promise.all(
stories.map(async ({ categories, translations }) => ({
categories: await Promise.all(
categories.data.map(({ attributes }) => findCategory(attributes.slug))
),
translations: translations.map(
({ language, description, level_1, level_2, level_3, level_4 }) => {
if (isUndefined(language.data))
throw new Error("A language is required to create a Weapon translation");
const name = names.find(
(name) => name.language.data?.attributes.code === language.data?.attributes.code
)?.name;
if (isUndefined(name))
throw new Error("A name is required to create a Weapon translation");
return {
language: language.data?.attributes.code,
sourceLanguage: language.data?.attributes.code,
name,
description,
level1: level_1,
level2: level_2,
level3: level_3,
level4: level_4,
transcribers: [user.id],
};
}
),
}))
),
};
await payload.create({ collection: Collections.Weapons, data, user });
await payload.create({
collection: Collections.Weapons,
data: {
updatedBy: user.id,
slug,
type: await findWeaponType(type.data.attributes.slug),
group: groupId,
thumbnail: thumbnailId,
appearances: await Promise.all(
stories.map(async ({ categories, translations }) => ({
categories: await Promise.all(
categories.data.map(({ attributes }) => findCategory(attributes.slug))
),
translations: translations.map(
({ language, description, level_1, level_2, level_3, level_4 }) => {
if (isUndefined(language.data))
throw new Error("A language is required to create a Weapon translation");
const name = names.find(
(name) => name.language.data?.attributes.code === language.data?.attributes.code
)?.name;
if (isUndefined(name))
throw new Error("A name is required to create a Weapon translation");
return {
language: language.data?.attributes.code,
sourceLanguage: language.data?.attributes.code,
name,
description,
level1: level_1,
level2: level_2,
level3: level_3,
level4: level_4,
transcribers: [user.id],
};
}
),
}))
),
},
user,
});
},
},
});

View File

@ -1,8 +1,8 @@
import { styled } from "styled-components";
import styled from "styled-components";
export const Icon = styled.div`
width: 46px;
height: 46px;
width: 18px;
height: 18px;
mask: url("/public/accords.svg");
background-color: var(--theme-elevation-1000);
mask-size: contain;

View File

@ -1,6 +1,6 @@
import "@fontsource/vollkorn/700.css";
import React from "react";
import { styled } from "styled-components";
import styled from "styled-components";
export const Logo = (): JSX.Element => (
<Container>

View File

@ -1,8 +1,8 @@
import React from "react";
import { styled } from "styled-components";
import { Link } from "react-router-dom";
import QueryString from "qs";
import { Options } from "payload/dist/collections/operations/local/find";
import QueryString from "qs";
import React from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
import { LanguageCodes } from "../constants";
type Props = {

View File

@ -1,10 +1,11 @@
import payload from "payload";
import payload, { GeneratedTypes } from "payload";
import { Collections } from "../constants";
import { CollectionEndpoint } from "../types/payload";
export const createGetByEndpoint = <T, R>(
collection: string,
export const createGetByEndpoint = <C extends Collections, R>(
collection: C,
attribute: string,
handler: (doc: T) => Promise<R> | R = (doc) => doc as unknown as R
handler: (doc: GeneratedTypes["collections"][C]) => Promise<R> | R
): CollectionEndpoint => ({
path: `/${attribute}/:${attribute}`,
method: "get",
@ -24,7 +25,7 @@ export const createGetByEndpoint = <T, R>(
where: { [attribute]: { equals: req.params[attribute] } },
});
if (result.docs.length === 0) {
if (!result.docs[0]) {
return res.sendStatus(404);
}

View File

@ -1,39 +0,0 @@
import payload from "payload";
import { mustBeApi } from "../accesses/endpoints/mustBeApi";
import { Collections } from "../constants";
import { CollectionEndpoint } from "../types/payload";
export const createGetSlugsEndpoint = (collection: Collections): CollectionEndpoint => ({
path: "/slugs",
method: "get",
handler: async (req, res) => {
if (!mustBeApi(req)) {
return res.status(403).send({
errors: [
{
message: "You are not allowed to perform this action.",
},
],
});
}
let page = 1;
let totalPage = 1;
const slugs: string[] = [];
while (page <= totalPage) {
const entries = await payload.find({
collection,
page,
user: req.user,
});
entries.docs.forEach(({ slug }: { slug: string }) => slugs.push(slug));
totalPage = entries.totalPages;
page++;
}
res.status(200).json(slugs);
},
});

View File

@ -6,7 +6,7 @@ import { isDefined } from "../utils/asserts";
type Image = {
filename: string;
id: string | number;
id: string;
};
export const createImageRegenerationEndpoint = (collection: Collections): CollectionEndpoint => ({
@ -36,7 +36,7 @@ export const createImageRegenerationEndpoint = (collection: Collections): Collec
});
await Promise.all(
images.docs.map(async (image: Image) => {
images.docs.filter(isImage).map(async (image: Image) => {
try {
await payload.update({
collection,
@ -64,3 +64,9 @@ export const createImageRegenerationEndpoint = (collection: Collections): Collec
.json({ message: `${count} entries have been regenerated successfully.`, errors });
},
});
const isImage = (item: Object): item is Image => {
if (!("id" in item) || typeof item.id !== "string") return false;
if (!("filename" in item) || typeof item.filename !== "string") return false;
return true;
};

View File

@ -1,16 +1,18 @@
import payload from "payload";
import payload, { GeneratedTypes } from "payload";
import { BasePayload } from "payload/dist/payload";
import QueryString from "qs";
import { Collections } from "../constants";
import { Recorder } from "../types/collections";
import { CollectionEndpoint, PayloadCreateData } from "../types/payload";
import { CollectionEndpoint } from "../types/payload";
import { isDefined } from "../utils/asserts";
export const getAllStrapiEntries = async <T>(
export const getAllStrapiEntries = async (
collectionSlug: string,
params: Object
): Promise<T[]> => {
): Promise<any[]> => {
let page = 1;
let totalPage = 1;
const result: T[] = [];
const result: any[] = [];
while (page <= totalPage) {
const paramsWithPagination = QueryString.stringify({
@ -30,24 +32,27 @@ export const getAllStrapiEntries = async <T>(
return result;
};
type Params<T, S> = {
type Params<S> = {
strapi: {
collection: string;
params: any;
};
payload: {
collection: string;
collection: Collections;
import?: (strapiObject: S, user: any) => Promise<void>;
convert?: (strapiObject: S, user: any) => PayloadCreateData<T>;
convert?: (
strapiObject: S,
user: any
) => Parameters<BasePayload<GeneratedTypes>["create"]>[0]["data"];
};
};
export const importStrapiEntries = async <T, S>({
export const importStrapiEntries = async <S>({
strapi: strapiParams,
payload: payloadParams,
user,
}: Params<T, S> & { user: Recorder }) => {
const entries = await getAllStrapiEntries<any>(strapiParams.collection, strapiParams.params);
}: Params<S> & { user: Recorder }) => {
const entries = await getAllStrapiEntries(strapiParams.collection, strapiParams.params);
const errors: string[] = [];
@ -77,7 +82,7 @@ export const importStrapiEntries = async <T, S>({
return { count: entries.length, errors };
};
export const createStrapiImportEndpoint = <T, S>(params: Params<T, S>): CollectionEndpoint => ({
export const createStrapiImportEndpoint = <S>(params: Params<S>): CollectionEndpoint => ({
method: "post",
path: "/strapi",
handler: async (req, res) => {

View File

@ -1,11 +1,12 @@
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";
type BackPropagationField = FieldBase & {
where: (data: any) => Where;
relationTo: string;
relationTo: Collections;
hasMany?: boolean;
};
export const backPropagationField = ({

View File

@ -1,7 +1,7 @@
import { Props } from "payload/components/views/Cell";
import React, { useEffect, useMemo, useState } from "react";
import { Link } from "react-router-dom";
import { styled } from "styled-components";
import styled from "styled-components";
import { isUndefined } from "../../utils/asserts";
const Image = styled.img`

View File

@ -1,8 +1,8 @@
import React from "react";
import styled from "styled-components";
import { Language } from "../../types/collections";
import { isDefined } from "../../utils/asserts";
import { formatLanguageCode, shortenEllipsis } from "../../utils/string";
import { Language } from "../../types/collections";
import { styled } from "styled-components";
interface Props {
language?: Language | string;

View File

@ -28,7 +28,7 @@ const languageField: Field = {
type: "relationship",
relationTo: Collections.Languages,
required: true,
admin: { allowCreate: false, width: "50%" },
admin: { allowCreate: false, width: "0%" },
};
const sourceLanguageField: Field = {
@ -36,7 +36,7 @@ const sourceLanguageField: Field = {
type: "relationship",
relationTo: Collections.Languages,
required: true,
admin: { allowCreate: false, width: "50%" },
admin: { allowCreate: false, width: "0%" },
};
const creditFields: Field = {
@ -64,7 +64,7 @@ const creditFields: Field = {
},
admin: {
condition: (_, siblingData) => siblingData.language === siblingData.sourceLanguage,
width: "50%",
width: "0%",
},
validate: (count, { siblingData }) => {
if (siblingData[fieldsNames.language] !== siblingData[fieldsNames.sourceLanguage]) {
@ -95,7 +95,7 @@ const creditFields: Field = {
admin: {
condition: (_, siblingData) =>
siblingData[fieldsNames.language] !== siblingData[fieldsNames.sourceLanguage],
width: "50%",
width: "0%",
},
validate: (count, { siblingData }) => {
if (siblingData[fieldsNames.language] === siblingData[fieldsNames.sourceLanguage]) {
@ -114,7 +114,7 @@ const creditFields: Field = {
type: "relationship",
relationTo: "recorders",
hasMany: true,
admin: { width: "50%" },
admin: { width: "0%" },
},
],
};

View File

@ -1,7 +1,11 @@
import { webpackBundler } from "@payloadcms/bundler-webpack";
import { mongooseAdapter } from "@payloadcms/db-mongodb";
import { BlocksFeature, lexicalEditor } from "@payloadcms/richtext-lexical";
import path from "path";
import { buildConfig } from "payload/config";
import { ChronologyEras } from "./collections/ChronologyEras/ChronologyEras";
import { ChronologyItems } from "./collections/ChronologyItems/ChronologyItems";
import { transcriptBlock } from "./collections/Contents/Blocks/transcriptBlock";
import { Contents } from "./collections/Contents/Contents";
import { ContentsFolders } from "./collections/ContentsFolders/ContentsFolders";
import { ContentsThumbnails } from "./collections/ContentsThumbnails/ContentsThumbnails";
@ -25,7 +29,6 @@ import { WeaponsThumbnails } from "./collections/WeaponsThumbnails/WeaponsThumbn
import { Icon } from "./components/Icon";
import { Logo } from "./components/Logo";
import { Collections } from "./constants";
import { payloadGridView } from "./plugins/payload-grid-view";
export default buildConfig({
serverURL: process.env.PAYLOAD_URI,
@ -38,7 +41,14 @@ export default buildConfig({
titleSuffix: "- Accords Library",
},
css: path.resolve(__dirname, "styles.scss"),
bundler: webpackBundler(),
},
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
BlocksFeature({ blocks: [transcriptBlock] }),
],
}),
collections: [
LibraryItems,
Contents,
@ -63,6 +73,9 @@ export default buildConfig({
Recorders,
Keys,
],
db: mongooseAdapter({
url: process.env.MONGODB_URI ?? "mongodb://mongo:27017/payload",
}),
globals: [],
telemetry: false,
typescript: {
@ -71,5 +84,4 @@ export default buildConfig({
graphQL: {
disable: true,
},
plugins: [payloadGridView],
});

View File

@ -1,69 +0,0 @@
.grid {
margin-bottom: 25px;
> .grid__header {
margin-bottom: 15px;
display: flex;
gap: 1.5rem;
}
> .grid__cells {
display: grid;
grid-gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
> .grid__cells__cell {
display: grid;
grid-template-rows: 1fr;
background-color: var(--theme-elevation-50);
border: 1px solid var(--theme-elevation-100);
position: relative;
> .grid__cells__cell__filename {
position: relative;
aspect-ratio: 1;
.thumbnail {
> img {
position: absolute;
inset: 0;
object-fit: contain;
background-size: 9% 9%;
background-position: center;
background-image: radial-gradient(
circle,
var(--theme-elevation-100) 1px,
var(--theme-elevation-0) 1px
);
}
}
}
> .grid__cells__cell__selector {
position: absolute;
left: 0.75rem;
top: 0.75rem;
}
> .grid__cells__cell__info {
display: grid;
line-height: 1.5;
padding: 1rem;
gap: 0.5rem;
overflow-x: hidden;
> .grid__cells__cell__title {
font-weight: 600;
}
> .grid__cells__cell__others {
display: grid;
line-height: 1.5;
color: var(--theme-elevation-600);
width: 100%;
font-size: 90%;
}
}
}
}
}

View File

@ -1,100 +0,0 @@
import { useTableColumns } from "payload/dist/admin/components/elements/TableColumns";
import React, { Fragment } from "react";
import { Link } from "react-router-dom";
import "payload/dist/admin/components/elements/Table/index.scss";
import { Column } from "payload/dist/admin/components/elements/Table/types";
import Thumbnail from "payload/dist/admin/components/elements/Thumbnail";
import { SanitizedCollectionConfig } from "payload/types";
import "./index.scss";
const baseClass = "grid";
type Props = {
data: any[];
collection: SanitizedCollectionConfig;
};
const fieldNames = {
filename: "filename",
select: "_select",
};
export const Grid: React.FC<Props> = ({ data, collection }) => {
const { columns: columnsFromContext } = useTableColumns();
const fields = columnsFromContext;
const otherFields = fields?.filter(
(col) => col.active && ![fieldNames.filename, fieldNames.select].includes(col.accessor)
);
const filenameField = fields.find((col) => col.accessor === fieldNames.filename);
const selectorField = fields.find((col) => col.accessor === fieldNames.select);
const headerColumns = fields
.sort((a, b) => {
const sortingValue = (value: Column) => {
switch (value.accessor) {
case fieldNames.select:
return 2;
case fieldNames.filename:
return 1;
default:
return 0;
}
};
return sortingValue(b) - sortingValue(a);
})
.filter(({ active, accessor }) => active || accessor === fieldNames.filename);
return (
<div className={baseClass}>
<div className={`${baseClass}__header`}>
{headerColumns.map((col, index) => (
<div key={index} id={`heading-${col.accessor}`}>
{col.components.Heading}
</div>
))}
</div>
<div className={`${baseClass}__cells`}>
{data &&
data.map((gridCell, cellIndex) => (
<div key={cellIndex} className={`${baseClass}__cells__cell`}>
{filenameField && (
<Link
className={`${baseClass}__cells__cell__filename`}
to={`${collection.slug}/${gridCell.id}`}>
<Thumbnail collection={collection} doc={gridCell} />
</Link>
)}
{selectorField && (
<div className={`${baseClass}__cells__cell__selector`}>
{selectorField.components.renderCell(gridCell, gridCell[selectorField.accessor])}
</div>
)}
<div className={`${baseClass}__cells__cell__info`}>
{filenameField && (
<Link
className={`${baseClass}__cells__cell__title`}
to={`${collection.slug}/${gridCell.id}`}>
{String(gridCell[filenameField.accessor])}
</Link>
)}
{otherFields.length > 0 && (
<div className={`${baseClass}__cells__cell__others`}>
{otherFields.map((col, colIndex) => (
<Fragment key={colIndex}>
{col.components.renderCell(gridCell, gridCell[col.accessor])}
</Fragment>
))}
</div>
)}
</div>
</div>
))}
</div>
</div>
);
};
export default Grid;

View File

@ -1,202 +0,0 @@
import { useWindowInfo } from "@faceless-ui/window-info";
import Button from "payload/dist/admin/components/elements/Button";
import DeleteMany from "payload/dist/admin/components/elements/DeleteMany";
import EditMany from "payload/dist/admin/components/elements/EditMany";
import Eyebrow from "payload/dist/admin/components/elements/Eyebrow";
import { Gutter } from "payload/dist/admin/components/elements/Gutter";
import ListSelection from "payload/dist/admin/components/elements/ListSelection";
import Paginator from "payload/dist/admin/components/elements/Paginator";
import PerPage from "payload/dist/admin/components/elements/PerPage";
import Pill from "payload/dist/admin/components/elements/Pill";
import PublishMany from "payload/dist/admin/components/elements/PublishMany";
import { StaggeredShimmers } from "payload/dist/admin/components/elements/ShimmerEffect";
import { Table } from "payload/dist/admin/components/elements/Table";
import UnpublishMany from "payload/dist/admin/components/elements/UnpublishMany";
import ViewDescription from "payload/dist/admin/components/elements/ViewDescription";
import Meta from "payload/dist/admin/components/utilities/Meta";
import { RelationshipProvider } from "payload/dist/admin/components/views/collections/List/RelationshipProvider";
import { SelectionProvider } from "payload/dist/admin/components/views/collections/List/SelectionProvider";
import { Props } from "payload/dist/admin/components/views/collections/List/types";
import formatFilesize from "payload/dist/uploads/formatFilesize";
import { getTranslation } from "payload/dist/utilities/getTranslation";
import React, { Fragment, useState } from "react";
import { useTranslation } from "react-i18next";
import Grid from "../Grid";
import ListControls, { ViewMode } from "../ListControls";
const baseClass = "collection-list";
export type UploadsGridViewOptions = {
list?: boolean;
grid?: boolean;
};
export const DefaultList =
(options: UploadsGridViewOptions) =>
(props: Props): JSX.Element => {
const {
collection,
collection: {
labels: { singular: singularLabel, plural: pluralLabel },
admin: {
description,
components: { BeforeList, BeforeListTable, AfterListTable, AfterList } = {},
} = {},
},
data,
newDocumentURL,
limit,
hasCreatePermission,
disableEyebrow,
modifySearchParams,
handleSortChange,
handleWhereChange,
handlePageChange,
handlePerPageChange,
customHeader,
resetParams,
} = props;
const [viewMode, setViewMode] = useState<ViewMode>(options.grid === true ? "grid" : "list");
const {
breakpoints: { s: smallBreak },
} = useWindowInfo();
const { t, i18n } = useTranslation("general");
let formattedDocs = data.docs || [];
if (collection.upload) {
formattedDocs = formattedDocs?.map((doc) => {
return {
...doc,
filesize: formatFilesize(doc.filesize),
};
});
}
return (
<div className={baseClass}>
{Array.isArray(BeforeList) &&
BeforeList.map((Component, i) => <Component key={i} {...props} />)}
<Meta title={getTranslation(collection.labels.plural, i18n)} />
<SelectionProvider docs={data.docs} totalDocs={data.totalDocs}>
{!disableEyebrow && <Eyebrow />}
<Gutter className={`${baseClass}__wrap`}>
<header className={`${baseClass}__header`}>
{customHeader && customHeader}
{!customHeader && (
<Fragment>
<h1>{getTranslation(pluralLabel, i18n)}</h1>
{hasCreatePermission && (
<Pill
to={newDocumentURL}
aria-label={t("createNewLabel", {
label: getTranslation(singularLabel, i18n),
})}>
{t("createNew")}
</Pill>
)}
{!smallBreak && (
<ListSelection label={getTranslation(collection.labels.plural, i18n)} />
)}
{description && (
<div className={`${baseClass}__sub-header`}>
<ViewDescription description={description} />
</div>
)}
</Fragment>
)}
</header>
<ListControls
collection={collection}
modifySearchQuery={modifySearchParams}
handleSortChange={handleSortChange}
handleWhereChange={handleWhereChange}
handleViewModeChange={(newViewMode) => setViewMode(newViewMode)}
resetParams={resetParams}
viewMode={viewMode}
showViewModeToggle={options.list === true && options.grid === true}
/>
{Array.isArray(BeforeListTable) &&
BeforeListTable.map((Component, i) => <Component key={i} {...props} />)}
{!data.docs && (
<StaggeredShimmers
className={[`${baseClass}__shimmer`, `${baseClass}__shimmer--rows`].join(" ")}
count={6}
/>
)}
{data.docs && data.docs.length > 0 && (
<RelationshipProvider>
{viewMode === "grid" ? (
<Grid data={formattedDocs} collection={collection} />
) : (
<Table data={formattedDocs} />
)}
</RelationshipProvider>
)}
{data.docs && data.docs.length === 0 && (
<div className={`${baseClass}__no-results`}>
<p>{t("noResults", { label: getTranslation(pluralLabel, i18n) })}</p>
{hasCreatePermission && newDocumentURL && (
<Button el="link" to={newDocumentURL}>
{t("createNewLabel", { label: getTranslation(singularLabel, i18n) })}
</Button>
)}
</div>
)}
{Array.isArray(AfterListTable) &&
AfterListTable.map((Component, i) => <Component key={i} {...props} />)}
<div className={`${baseClass}__page-controls`}>
<Paginator
limit={data.limit}
totalPages={data.totalPages}
page={data.page}
hasPrevPage={data.hasPrevPage}
hasNextPage={data.hasNextPage}
prevPage={data.prevPage ?? undefined}
nextPage={data.nextPage ?? undefined}
numberOfNeighbors={1}
disableHistoryChange={modifySearchParams === false}
onChange={handlePageChange}
/>
{data?.totalDocs > 0 && (
<Fragment>
<div className={`${baseClass}__page-info`}>
{data.page ?? 1 * data.limit - (data.limit - 1)}-
{data.totalPages > 1 && data.totalPages !== data.page
? data.limit * (data.page ?? 1)
: data.totalDocs}{" "}
{t("of")} {data.totalDocs}
</div>
<PerPage
limits={collection?.admin?.pagination?.limits}
limit={limit}
modifySearchParams={modifySearchParams}
handleChange={handlePerPageChange}
resetPage={data.totalDocs <= data.pagingCounter}
/>
<div className={`${baseClass}__list-selection`}>
{smallBreak && (
<Fragment>
<ListSelection label={getTranslation(collection.labels.plural, i18n)} />
<div className={`${baseClass}__list-selection-actions`}>
<EditMany collection={collection} resetParams={resetParams} />
<PublishMany collection={collection} resetParams={resetParams} />
<UnpublishMany collection={collection} resetParams={resetParams} />
<DeleteMany collection={collection} resetParams={resetParams} />
</div>
</Fragment>
)}
</div>
</Fragment>
)}
</div>
</Gutter>
</SelectionProvider>
{Array.isArray(AfterList) &&
AfterList.map((Component, i) => <Component key={i} {...props} />)}
</div>
);
};

View File

@ -1,228 +0,0 @@
import { useWindowInfo } from "@faceless-ui/window-info";
import Button from "payload/dist/admin/components/elements/Button";
import ColumnSelector from "payload/dist/admin/components/elements/ColumnSelector";
import DeleteMany from "payload/dist/admin/components/elements/DeleteMany";
import EditMany from "payload/dist/admin/components/elements/EditMany";
import { getTextFieldsToBeSearched } from "payload/dist/admin/components/elements/ListControls/getTextFieldsToBeSearched";
import { Props } from "payload/dist/admin/components/elements/ListControls/types";
import Pill from "payload/dist/admin/components/elements/Pill";
import PublishMany from "payload/dist/admin/components/elements/PublishMany";
import SearchFilter from "payload/dist/admin/components/elements/SearchFilter";
import SortComplex from "payload/dist/admin/components/elements/SortComplex";
import UnpublishMany from "payload/dist/admin/components/elements/UnpublishMany";
import WhereBuilder from "payload/dist/admin/components/elements/WhereBuilder";
import validateWhereQuery from "payload/dist/admin/components/elements/WhereBuilder/validateWhereQuery";
import Chevron from "payload/dist/admin/components/icons/Chevron";
import { useSearchParams } from "payload/dist/admin/components/utilities/SearchParams";
import { SanitizedCollectionConfig } from "payload/dist/collections/config/types";
import { fieldAffectsData } from "payload/dist/fields/config/types";
import flattenFields from "payload/dist/utilities/flattenTopLevelFields";
import { getTranslation } from "payload/dist/utilities/getTranslation";
import React, { useEffect, useState } from "react";
import AnimateHeight from "react-animate-height";
import { useTranslation } from "react-i18next";
const baseClass = "list-controls";
export type ViewMode = "grid" | "list";
const getUseAsTitle = (collection: SanitizedCollectionConfig) => {
const {
admin: { useAsTitle },
fields,
} = collection;
const topLevelFields = flattenFields(fields);
return topLevelFields.find((field) => fieldAffectsData(field) && field.name === useAsTitle);
};
/**
* The ListControls component is used to render the controls (search, filter, where)
* for a collection's list view. You can find those directly above the table which lists
* the collection's documents.
*/
const ListControls: React.FC<
Props & {
viewMode: ViewMode;
handleViewModeChange: (newMode: ViewMode) => void;
showViewModeToggle: boolean;
}
> = (props) => {
const {
collection,
enableColumns = true,
enableSort = false,
handleSortChange,
handleWhereChange,
modifySearchQuery = true,
resetParams = () => undefined,
viewMode,
handleViewModeChange,
collection: {
fields,
admin: { listSearchableFields },
},
showViewModeToggle,
} = props;
const params = useSearchParams();
const shouldInitializeWhereOpened = validateWhereQuery(params?.where);
const [titleField, setTitleField] = useState(getUseAsTitle(collection));
useEffect(() => {
setTitleField(getUseAsTitle(collection));
}, [collection]);
const [textFieldsToBeSearched] = useState(
getTextFieldsToBeSearched(listSearchableFields, fields)
);
const [visibleDrawer, setVisibleDrawer] = useState<"where" | "sort" | "columns" | undefined>(
shouldInitializeWhereOpened ? "where" : undefined
);
const { t, i18n } = useTranslation("general");
const {
breakpoints: { s: smallBreak },
} = useWindowInfo();
return (
<div className={baseClass}>
<div className={`${baseClass}__wrap`}>
<SearchFilter
fieldName={titleField && fieldAffectsData(titleField) ? titleField.name : undefined}
handleChange={handleWhereChange}
modifySearchQuery={modifySearchQuery}
fieldLabel={
titleField && fieldAffectsData(titleField)
? getTranslation(String(titleField.label ?? titleField.name), i18n)
: undefined
}
listSearchableFields={textFieldsToBeSearched}
/>
<div className={`${baseClass}__buttons`}>
<div className={`${baseClass}__buttons-wrap`}>
{!smallBreak && (
<React.Fragment>
<EditMany collection={collection} resetParams={resetParams} />
<PublishMany collection={collection} resetParams={resetParams} />
<UnpublishMany collection={collection} resetParams={resetParams} />
<DeleteMany collection={collection} resetParams={resetParams} />
</React.Fragment>
)}
{enableColumns && (
<Pill
pillStyle="light"
className={`${baseClass}__toggle-columns ${
visibleDrawer === "columns" ? `${baseClass}__buttons-active` : ""
}`}
onClick={() =>
setVisibleDrawer(visibleDrawer !== "columns" ? "columns" : undefined)
}
aria-expanded={visibleDrawer === "columns"}
aria-controls={`${baseClass}-columns`}
icon={<Chevron />}>
{t("columns")}
</Pill>
)}
<Pill
pillStyle="light"
className={`${baseClass}__toggle-where ${
visibleDrawer === "where" ? `${baseClass}__buttons-active` : ""
}`}
onClick={() => setVisibleDrawer(visibleDrawer !== "where" ? "where" : undefined)}
aria-expanded={visibleDrawer === "where"}
aria-controls={`${baseClass}-where`}
icon={<Chevron />}>
{t("filters")}
</Pill>
{enableSort && (
<Button
className={`${baseClass}__toggle-sort`}
buttonStyle={visibleDrawer === "sort" ? undefined : "secondary"}
onClick={() => setVisibleDrawer(visibleDrawer !== "sort" ? "sort" : undefined)}
aria-expanded={visibleDrawer === "sort"}
aria-controls={`${baseClass}-sort`}
icon="chevron"
iconStyle="none">
{t("sort")}
</Button>
)}
{showViewModeToggle && (
<div style={{ marginLeft: 10 }}>
<svg
onClick={() => handleViewModeChange("list")}
style={{
cursor: "pointer",
color:
viewMode === "list"
? "var(--theme-elevation-1000)"
: "var(--theme-elevation-500)",
}}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
preserveAspectRatio="none"
height="32"
width="28">
<path
fill="currentColor"
d="M333-242h432.5q12 0 22-10t10-22v-100.5H333V-242ZM162.5-586h145v-132h-113q-12 0-22 10t-10 22v100Zm0 187h145v-161.5h-145V-399Zm32 157h113v-132.5h-145V-274q0 12 10 22t22 10ZM333-399h464.5v-161.5H333V-399Zm0-187h464.5v-100q0-12-10-22t-22-10H333v132ZM194.28-216.5q-24.218 0-40.749-16.531Q137-249.562 137-273.802v-412.396q0-24.24 16.531-40.771Q170.062-743.5 194.28-743.5h571.44q24.218 0 40.749 16.531Q823-710.438 823-686.198v412.396q0 24.24-16.531 40.771Q789.938-216.5 765.72-216.5H194.28Z"
/>
</svg>
<svg
onClick={() => handleViewModeChange("grid")}
style={{
cursor: "pointer",
color:
viewMode === "grid"
? "var(--theme-elevation-1000)"
: "var(--theme-elevation-500)",
}}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
height="28"
width="28">
<path
fill="currentColor"
d="M176.5-519v-264.5h265V-519h-265Zm0 342.5v-265h265v265h-265ZM519-519v-264.5h264.5V-519H519Zm0 342.5v-265h264.5v265H519Zm-317-368h214V-758H202v213.5Zm342.5 0H758V-758H544.5v213.5Zm0 342.5H758v-214H544.5v214ZM202-202h214v-214H202v214Zm342.5-342.5Zm0 128.5ZM416-416Zm0-128.5Z"
/>
</svg>
</div>
)}
</div>
</div>
</div>
{enableColumns && (
<AnimateHeight
className={`${baseClass}__columns`}
height={visibleDrawer === "columns" ? "auto" : 0}
id={`${baseClass}-columns`}>
<ColumnSelector collection={collection} />
</AnimateHeight>
)}
<AnimateHeight
className={`${baseClass}__where`}
height={visibleDrawer === "where" ? "auto" : 0}
id={`${baseClass}-where`}>
<WhereBuilder
collection={collection}
modifySearchQuery={modifySearchQuery}
handleChange={handleWhereChange}
/>
</AnimateHeight>
{enableSort && (
<AnimateHeight
className={`${baseClass}__sort`}
height={visibleDrawer === "sort" ? "auto" : 0}
id={`${baseClass}-sort`}>
<SortComplex
modifySearchQuery={modifySearchQuery}
collection={collection}
handleChange={handleSortChange}
/>
</AnimateHeight>
)}
</div>
);
};
export default ListControls;

View File

@ -1,53 +0,0 @@
import { Plugin } from "payload/config";
import { CollectionAdminOptions } from "payload/dist/collections/config/types";
import { CollectionConfig } from "payload/types";
import { DefaultList, UploadsGridViewOptions } from "./components/List";
type Components = Required<CollectionAdminOptions>["components"];
type ViewsComponents = Required<Required<CollectionAdminOptions>["components"]>["views"];
type Options = {
isUploadEnabled: boolean;
gridView: UploadsGridViewOptions;
};
export type CollectionConfigWithGridView = CollectionConfig & {
custom?: { gridView?: UploadsGridViewOptions };
};
export const payloadGridView: Plugin = ({ collections, ...others }) => ({
collections: collections?.map(handleCollection),
...others,
});
const handleCollection = ({
admin,
...others
}: CollectionConfigWithGridView): CollectionConfig => ({
...others,
admin: handleAdmin(admin, {
isUploadEnabled: others.upload !== undefined,
gridView: others.custom?.gridView ?? { grid: true, list: true },
}),
});
const handleAdmin = (
{ components, ...others }: CollectionAdminOptions = {},
options: Options
): CollectionAdminOptions => ({
...others,
components: handleComponents(components, options),
});
const handleComponents = ({ views, ...others }: Components = {}, options: Options): Components => ({
...others,
views: handleViewsComponents(views, options),
});
const handleViewsComponents = (
{ List, ...others }: ViewsComponents = {},
{ isUploadEnabled, gridView }: Options
): ViewsComponents => ({
...others,
List: isUploadEnabled ? DefaultList(gridView) : List,
});

View File

@ -167,8 +167,6 @@ export type PayloadImage = {
};
export const payload = {
getSlugsWeapons: async (): Promise<string[]> =>
await (await request(payloadApiUrl(Collections.Weapons, "slugs"))).json(),
getWeapon: async (slug: string): Promise<EndpointWeapon> =>
await (await request(payloadApiUrl(Collections.Weapons, `slug/${slug}`))).json(),
getEras: async (): Promise<EndpointEra[]> =>

View File

@ -4,8 +4,6 @@ import { readFileSync } from "fs";
import path from "path";
import payload from "payload";
import { Collections, RecordersRoles } from "./constants";
import { Recorder } from "./types/collections";
import { PayloadCreateData } from "./types/payload";
import { isDefined, isUndefined } from "./utils/asserts";
const app = express();
@ -28,7 +26,6 @@ const start = async () => {
await payload.init({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
express: app,
onInit: async () => {
payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
@ -43,16 +40,15 @@ const start = async () => {
if (recorders.docs.length === 0) {
payload.logger.info("Seeding some initial data");
const recorder: PayloadCreateData<Recorder> = {
email: process.env.SEEDING_ADMIN_EMAIL,
password: process.env.SEEDING_ADMIN_PASSWORD,
username: process.env.SEEDING_ADMIN_USERNAME,
role: [RecordersRoles.Admin, RecordersRoles.Api],
anonymize: false,
};
await payload.create({
collection: Collections.Recorders,
data: recorder,
data: {
email: process.env.SEEDING_ADMIN_EMAIL,
password: process.env.SEEDING_ADMIN_PASSWORD,
username: process.env.SEEDING_ADMIN_USERNAME,
role: [RecordersRoles.Admin, RecordersRoles.Api],
anonymize: false,
},
});
}
}

View File

@ -46,6 +46,16 @@ html[data-theme="light"] {
--color-base-1000: #000000;
}
.field-type.row {
padding: 1rem 0;
}
.field-type.radio-group {
> ul {
padding: 0.75rem 0;
}
}
.field-type.array-field.group-array {
> .array-field__header {
.array-field__header-actions {

View File

@ -25,34 +25,36 @@ export type ContentFoldersTranslation = {
export interface Config {
collections: {
"library-items": LibraryItem;
'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;
videos: Video;
"videos-channels": VideosChannel;
'videos-channels': VideosChannel;
languages: Language;
currencies: Currency;
recorders: Recorder;
keys: Key;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
globals: {};
}
export interface LibraryItem {
id: string;
itemType?: "Textual" | "Audio" | "Video" | "Game" | "Other";
itemType?: 'Textual' | 'Audio' | 'Video' | 'Game' | 'Other';
slug: string;
thumbnail?: string | LibraryItemThumbnail;
pretitle?: string;
@ -115,8 +117,8 @@ export interface LibraryItem {
subtype?: string[] | Key[];
languages?: string[] | Language[];
pageCount?: number;
bindingType?: "Paperback" | "Hardcover";
pageOrder?: "LeftToRight" | "RightToLeft";
bindingType?: 'Paperback' | 'Hardcover';
pageOrder?: 'LeftToRight' | 'RightToLeft';
};
audio?: {
audioSubtype?: string[] | Key[];
@ -160,7 +162,7 @@ export interface LibraryItem {
updatedBy: string | Recorder;
updatedAt: string;
createdAt: string;
_status?: "draft" | "published";
_status?: 'draft' | 'published';
}
export interface LibraryItemThumbnail {
id: string;
@ -278,16 +280,16 @@ 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 Language {
@ -297,7 +299,7 @@ export interface Language {
export interface File {
id: string;
filename: string;
type: "LibraryScans" | "LibrarySoundtracks" | "ContentVideo" | "ContentAudio";
type: 'LibraryScans' | 'LibrarySoundtracks' | 'ContentVideo' | 'ContentAudio';
updatedAt: string;
createdAt: string;
}
@ -317,10 +319,12 @@ export interface Content {
title: string;
subtitle?: string;
summary?: string;
textContent?: {
[k: string]: unknown;
}[];
textTranscribers?: string[] | Recorder[];
textTranslators?: string[] | Recorder[];
textProofreaders?: string[] | Recorder[];
textContent?: (TextBlock | Section | Tabs | TranscriptBlock | QuoteBlock)[];
textNotes?: string;
video?: string | File;
videoNotes?: string;
@ -330,7 +334,7 @@ export interface Content {
updatedBy: string | Recorder;
updatedAt: string;
createdAt: string;
_status?: "draft" | "published";
_status?: 'draft' | 'published';
}
export interface ContentsThumbnail {
id: string;
@ -376,7 +380,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;
@ -417,193 +421,6 @@ export interface RecordersThumbnail {
};
};
}
export interface TextBlock {
content: {
[k: string]: unknown;
}[];
id?: string;
blockName?: string;
blockType: "textBlock";
}
export interface Section {
content?: (Section_Section | Section_Tabs | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Section_Section {
content?: (
| Section_Section_Section
| Section_Section_Tabs
| TranscriptBlock
| QuoteBlock
| TextBlock
)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Section_Section_Section {
content?: (
| Section_Section_Section_Section
| Section_Section_Section_Tabs
| TranscriptBlock
| QuoteBlock
| TextBlock
)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Section_Section_Section_Section {
content?: (Section_Section_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Section_Section_Section_Section_Section {
content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface TranscriptBlock {
lines: (LineBlock | CueBlock)[];
id?: string;
blockName?: string;
blockType: "transcriptBlock";
}
export interface LineBlock {
content: {
[k: string]: unknown;
}[];
id?: string;
blockName?: string;
blockType: "lineBlock";
}
export interface CueBlock {
content: string;
id?: string;
blockName?: string;
blockType: "cueBlock";
}
export interface QuoteBlock {
from: string;
content: {
[k: string]: unknown;
}[];
id?: string;
blockName?: string;
blockType: "quoteBlock";
}
export interface Section_Section_Section_Tabs {
tabs?: Section_Section_Section_Tabs_Tab[];
id?: string;
blockName?: string;
blockType: "tabs";
}
export interface Section_Section_Section_Tabs_Tab {
content?: (Section_Section_Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "tab";
}
export interface Section_Section_Section_Tabs_Tab_Section {
content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Section_Section_Tabs {
tabs?: Section_Section_Tabs_Tab[];
id?: string;
blockName?: string;
blockType: "tabs";
}
export interface Section_Section_Tabs_Tab {
content?: (Section_Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "tab";
}
export interface Section_Section_Tabs_Tab_Section {
content?: (Section_Section_Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Section_Section_Tabs_Tab_Section_Section {
content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Section_Tabs {
tabs?: Section_Tabs_Tab[];
id?: string;
blockName?: string;
blockType: "tabs";
}
export interface Section_Tabs_Tab {
content?: (Section_Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "tab";
}
export interface Section_Tabs_Tab_Section {
content?: (Section_Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Section_Tabs_Tab_Section_Section {
content?: (Section_Tabs_Tab_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Section_Tabs_Tab_Section_Section_Section {
content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Tabs {
tabs?: Tabs_Tab[];
id?: string;
blockName?: string;
blockType: "tabs";
}
export interface Tabs_Tab {
content?: (Tabs_Tab_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "tab";
}
export interface Tabs_Tab_Section {
content?: (Tabs_Tab_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Tabs_Tab_Section_Section {
content?: (Tabs_Tab_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Tabs_Tab_Section_Section_Section {
content?: (Tabs_Tab_Section_Section_Section_Section | TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface Tabs_Tab_Section_Section_Section_Section {
content?: (TranscriptBlock | QuoteBlock | TextBlock)[];
id?: string;
blockName?: string;
blockType: "section";
}
export interface ContentsFolder {
id: string;
slug: string;
@ -617,21 +434,21 @@ export interface Post {
thumbnail?: string | PostThumbnail;
authors:
| {
relationTo: 'recorders';
value: string;
relationTo: "recorders";
}[]
| {
relationTo: 'recorders';
value: Recorder;
relationTo: "recorders";
}[];
categories?:
| {
relationTo: 'keys';
value: string;
relationTo: "keys";
}[]
| {
relationTo: 'keys';
value: Key;
relationTo: "keys";
}[];
translations: {
language: string | Language;
@ -650,7 +467,7 @@ export interface Post {
updatedBy: string | Recorder;
updatedAt: string;
createdAt: string;
_status?: "draft" | "published";
_status?: 'draft' | 'published';
}
export interface PostThumbnail {
id: string;
@ -701,12 +518,12 @@ export interface ChronologyItem {
events: {
source?:
| {
relationTo: 'contents';
value: string | Content;
relationTo: "contents";
}
| {
relationTo: 'library-items';
value: string | LibraryItem;
relationTo: "library-items";
};
translations: {
language: string | Language;
@ -724,7 +541,7 @@ export interface ChronologyItem {
updatedBy: string | Recorder;
updatedAt: string;
createdAt: string;
_status?: "draft" | "published";
_status?: 'draft' | 'published';
}
export interface ChronologyEra {
id: string;
@ -768,7 +585,7 @@ export interface Weapon {
updatedBy: string | Recorder;
updatedAt: string;
createdAt: string;
_status?: "draft" | "published";
_status?: 'draft' | 'published';
}
export interface WeaponsThumbnail {
id: string;
@ -830,7 +647,7 @@ export interface Video {
id: string;
uid: string;
gone: boolean;
source: "YouTube" | "NicoNico" | "Tumblr";
source: 'YouTube' | 'NicoNico' | 'Tumblr';
title: string;
description?: string;
likes?: number;
@ -844,3 +661,62 @@ export interface VideosChannel {
title: string;
subscribers?: number;
}
export interface PayloadPreference {
id: string;
user: {
relationTo: 'recorders';
value: string | Recorder;
};
key?: string;
value?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
}
export interface PayloadMigration {
id: string;
name?: string;
batch?: number;
updatedAt: string;
createdAt: string;
}
declare module 'payload' {
export interface GeneratedTypes {
collections: {
'library-items': LibraryItem
'contents': Content
'contents-folders': Contents Folder
'posts': Post
'chronology-items': ChronologyItem
'chronology-eras': Chronology Era
'weapons': Weapon
'weapons-groups': Weapons Group
'weapons-thumbnails': Weapons Thumbnail
'contents-thumbnails': Contents Thumbnail
'library-items-thumbnails': Library Item Thumbnail
'library-items-scans': Library Item Scans
'library-items-gallery': Library Item Gallery
'recorders-thumbnails': Recorders Thumbnail
'posts-thumbnails': Post Thumbnail
'files': File
'videos': Video
'videos-channels': Videos Channel
'languages': Language
'currencies': Currency
'recorders': Recorder
'keys': Key
'payload-preferences': PayloadPreference
'payload-migrations': PayloadMigration
}
}
}

View File

@ -1,10 +1,6 @@
import { CollectionConfig, PayloadRequest } from "payload/types";
import { Endpoint } from "payload/config";
import { PayloadRequest } from "payload/types";
export type PayloadCreateData<T> = Omit<
T,
"id" | "updatedAt" | "createdAt" | "sizes" | "updatedBy"
>;
export type CollectionEndpoint = NonNullable<CollectionConfig["endpoints"]>[number];
export type CollectionEndpoint = Omit<Endpoint, "root">;
export type EndpointAccess<U> = (req: PayloadRequest<U>) => boolean;

View File

@ -6,10 +6,6 @@ export const isDefined = <T>(value: T | null | undefined): value is T =>
export const isUndefined = <T>(value: T | null | undefined): value is null | undefined =>
!isDefined(value);
export const filterDefined = <T>(array: (T | null | undefined)[]): T[] => array.filter(isDefined);
export const isValidDate = (date: Date): boolean => date instanceof Date && !isNaN(date.getDate());
export const isNotEmpty = (value: string | null | undefined): value is string =>
isDefined(value) && value.trim().length > 0;
@ -17,7 +13,7 @@ export const isEmpty = (value: string | null | undefined): value is string =>
isUndefined(value) || value.trim().length === 0;
type Span = [number, number];
export const hasNoIntersection = (a: Span, b: Span): boolean => {
const hasNoIntersection = (a: Span, b: Span): boolean => {
const [aStart, aEnd] = a;
const [bStart, bEnd] = b;
return aEnd < bStart || aStart > bEnd;

View File

@ -1,8 +1,7 @@
import { CollectionConfig } from "payload/types";
import { Collections } from "../constants";
import { CollectionConfigWithGridView } from "../plugins/payload-grid-view";
type CollectionConfigWithPlugins = CollectionConfig & CollectionConfigWithGridView;
type CollectionConfigWithPlugins = CollectionConfig;
export type BuildCollectionConfig = Omit<
CollectionConfigWithPlugins,

View File

@ -8,6 +8,7 @@ export const findWeaponType = async (name: string): Promise<string> => {
collection: Collections.Keys,
where: { name: { equals: name }, type: { equals: KeysTypes.Weapons } },
});
if (!key.docs[0]) throw new Error(`Weapon type ${name} wasn't found`);
return key.docs[0].id;
};
@ -16,7 +17,8 @@ export const findCategory = async (name: string): Promise<string> => {
collection: Collections.Keys,
where: { name: { equals: name }, type: { equals: KeysTypes.Categories } },
});
return key.docs[0].id;
if (!key.docs[0]) throw new Error(`Category ${name} wasn't found`);
return key.docs[0]?.id;
};
type UploadStrapiImage = {

View File

@ -1,91 +0,0 @@
import { Block, BlockField } from "payload/types";
import { capitalize } from "./string";
const isDefined = <T>(value: T | null | undefined): value is T =>
value !== null && value !== undefined;
const recursionFieldName = "recursion" as const;
type BlockConfig<T extends string> = {
root: boolean;
block: RecursiveBlock<T> | Block;
};
type RecursiveBlock<T extends string> = Omit<Block, "fields" | "interfaceName"> & {
[recursionFieldName]: Omit<BlockField, "blocks" | "type"> & {
newDepth: (currentDepth: number) => number;
condition: (currentDepth: number, parents: T[]) => boolean;
blocks: T[];
};
fields?: Block["fields"];
};
export type BlocksConfig<T extends string> = Record<T, BlockConfig<T>>;
export const generateBlocks = <T extends string>(blocksConfig: BlocksConfig<T>): Block[] => {
const isRecursiveBlock = (block: RecursiveBlock<T> | Block): block is RecursiveBlock<T> =>
recursionFieldName in block;
const getInterfaceName = (parents: T[], currentBlockName: T): string => {
return [...parents, currentBlockName]
.map((blockName) => blocksConfig[blockName].block.slug)
.map(capitalize)
.join("_");
};
const getCurrentDepth = (parents: T[]): number =>
parents.reduce((acc, blockName) => {
const block = blocksConfig[blockName].block;
if (!isRecursiveBlock(block)) return acc;
return block[recursionFieldName].newDepth(acc);
}, 1);
const generateRecursiveBlocks = (parents: T[], blockName: T): Block | undefined => {
const currentDepth = getCurrentDepth(parents);
const block = blocksConfig[blockName].block;
if (!isRecursiveBlock(block)) return block;
const {
slug,
labels,
fields = [],
recursion: { newDepth, blocks, condition, ...fieldsProps },
} = block;
const generatedBlocks = blocks
.filter((blockName) => {
const block = blocksConfig[blockName].block;
if (!isRecursiveBlock(block)) return true;
return block[recursionFieldName].condition(currentDepth, parents);
})
.map((nextBlockName) => generateRecursiveBlocks([...parents, blockName], nextBlockName))
.filter(isDefined);
// Cut dead branches (branches without leafs)
if (generatedBlocks.length === 0) {
return undefined;
}
return {
slug,
interfaceName: getInterfaceName(parents, blockName),
labels,
fields: [
...fields,
{
...fieldsProps,
type: "blocks",
blocks: generatedBlocks,
},
],
};
};
const rootBlockNames = Object.entries<BlockConfig<T>>(blocksConfig)
.filter(([_, blockConfig]) => blockConfig.root)
.map(([blockName]) => blockName as T);
return rootBlockNames
.map((blockName) => generateRecursiveBlocks([], blockName))
.filter(isDefined);
};

View File

@ -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;
export const capitalize = (string: string): string => {
const capitalize = (string: string): string => {
const [firstLetter, ...otherLetters] = string;
if (isUndefined(firstLetter)) return "";
return [firstLetter.toUpperCase(), ...otherLetters].join("");
@ -19,6 +19,3 @@ export const formatToCamelCase = (name: string): string =>
.split(/[ \_-]/g)
.map((part, index) => (index > 0 ? capitalize(part) : part))
.join("");
export const formatToKebabCase = (name: string): string =>
name.toLowerCase().replaceAll(/[ \_]/g, "-");

View File

@ -14,7 +14,7 @@ const updatedByField = (): RelationshipField => ({
type: "relationship",
required: true,
relationTo: Collections.Recorders,
admin: { readOnly: true, position: "sidebar" },
admin: { readOnly: true },
});
type BuildVersionedCollectionConfig = Omit<BuildCollectionConfig, "timestamps" | "versions">;