From 138d7ae5717c96e609e2fc5a2557633d24967efb Mon Sep 17 00:00:00 2001 From: DrMint <29893320+DrMint@users.noreply.github.com> Date: Fri, 14 Jul 2023 16:42:37 +0200 Subject: [PATCH] Added Categories --- .vscode/settings.json | 5 ++ package.json | 5 +- prettier.config.js | 22 +++++++++ src/collections/Categories/Categories.ts | 48 +++++++++++++++++++ src/collections/{ => Images}/Images.ts | 0 .../Images}/components/ImageCell.tsx | 21 ++++---- src/collections/Recorders/Recorders.ts | 20 +++----- .../Recorders/components/BiographiesCell.tsx | 45 ----------------- .../components/BiographiesRowLabel.ts | 19 -------- src/elements/translatedFields/Cell.tsx | 24 ++++++++++ src/elements/translatedFields/RowLabel.tsx | 22 +++++++++ .../translatedFields/translatedFields.ts} | 31 ++++++++++-- src/payload.config.ts | 5 +- src/types/collections.ts | 1 + src/utils/asserts.ts | 8 ++-- src/utils/validation.ts | 3 +- 16 files changed, 177 insertions(+), 102 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 prettier.config.js create mode 100644 src/collections/Categories/Categories.ts rename src/collections/{ => Images}/Images.ts (100%) rename src/{ => collections/Images}/components/ImageCell.tsx (53%) delete mode 100644 src/collections/Recorders/components/BiographiesCell.tsx delete mode 100644 src/collections/Recorders/components/BiographiesRowLabel.ts create mode 100644 src/elements/translatedFields/Cell.tsx create mode 100644 src/elements/translatedFields/RowLabel.tsx rename src/{utils/fields.ts => elements/translatedFields/translatedFields.ts} (52%) diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d0c743c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "css.lint.unknownAtRules": "ignore", + "editor.rulers": [100], + "typescript.preferences.importModuleSpecifier": "non-relative", +} \ No newline at end of file diff --git a/package.json b/package.json index c8a40d8..71e9f3f 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,10 @@ "serve": "cross-env PAYLOAD_CONFIG_PATH=dist/payload.config.js NODE_ENV=production node dist/server.js", "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" + "generate:graphQLSchema": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload generate:graphQLSchema", + "prettier": "prettier --list-different --end-of-line auto --write src", + "tsc": "tsc --noEmit", + "precommit": "npm run generate:types && npm run prettier && npm run tsc" }, "dependencies": { "cross-env": "^7.0.3", diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..1dc757e --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,22 @@ +/** @type {import("prettier").Options} */ +module.exports = { + printWidth: 100, + tabWidth: 2, + useTabs: false, + semi: true, + singleQuote: false, + quoteProps: "as-needed", + jsxSingleQuote: false, + trailingComma: "es5", + bracketSpacing: true, + bracketSameLine: true, + arrowParens: "always", + rangeStart: 0, + rangeEnd: Infinity, + requirePragma: false, + insertPragma: false, + proseWrap: "preserve", + htmlWhitespaceSensitivity: "ignore", + endOfLine: "lf", + singleAttributePerLine: false, +}; diff --git a/src/collections/Categories/Categories.ts b/src/collections/Categories/Categories.ts new file mode 100644 index 0000000..166fd3f --- /dev/null +++ b/src/collections/Categories/Categories.ts @@ -0,0 +1,48 @@ +import { CollectionConfig } from "payload/types"; +import { localizedFields } from "../../elements/translatedFields/translatedFields"; + +const fields = { + id: "id", + translations: "translations", + name: "name", + short: "short", +} as const satisfies Record; + +const labels = { + singular: "Category", + plural: "Categories", +} as const satisfies { singular: string; plural: string }; + +export const Categories: CollectionConfig = { + slug: labels.plural, + labels, + typescript: { interface: labels.singular }, + defaultSort: fields.id, + admin: { + useAsTitle: fields.id, + defaultColumns: [fields.id, fields.translations], + }, + timestamps: false, + fields: [ + { + name: fields.id, + type: "text", + }, + localizedFields({ + name: fields.translations, + interfaceName: "CategoryTranslations", + admin: { + useAsTitle: fields.name, + }, + fields: [ + { + type: "row", + fields: [ + { name: fields.name, type: "text", required: true }, + { name: fields.short, type: "text" }, + ], + }, + ], + }), + ], +}; diff --git a/src/collections/Images.ts b/src/collections/Images/Images.ts similarity index 100% rename from src/collections/Images.ts rename to src/collections/Images/Images.ts diff --git a/src/components/ImageCell.tsx b/src/collections/Images/components/ImageCell.tsx similarity index 53% rename from src/components/ImageCell.tsx rename to src/collections/Images/components/ImageCell.tsx index 56d806f..dc0cd61 100644 --- a/src/components/ImageCell.tsx +++ b/src/collections/Images/components/ImageCell.tsx @@ -2,26 +2,25 @@ import { Props } from "payload/components/views/Cell"; import { useState, useEffect } from "react"; import React from "react"; -export const ImageCell: React.FC = ({ cellData, field }) => { +export const ImageCell = ({ cellData, field }: Props): JSX.Element => { const [imageURL, setImageURL] = useState(); useEffect(() => { const fetchUrl = async () => { if (typeof cellData !== "string") return; if (field.type !== "upload") return; - const result = await ( - await fetch(`/api/${field.relationTo}/${cellData}`) - ).json(); + const result = await (await fetch(`/api/${field.relationTo}/${cellData}`)).json(); setImageURL(result.url); }; fetchUrl(); }, [cellData]); - return imageURL ? ( - - ) : ( - "" + return ( + <> + {imageURL ? ( + + ) : ( + "" + )} + ); }; diff --git a/src/collections/Recorders/Recorders.ts b/src/collections/Recorders/Recorders.ts index 50dc06a..3dfc35e 100644 --- a/src/collections/Recorders/Recorders.ts +++ b/src/collections/Recorders/Recorders.ts @@ -1,11 +1,9 @@ import { CollectionConfig } from "payload/types"; -import { localizedFields } from "../../utils/fields"; +import { localizedFields } from "../../elements/translatedFields/translatedFields"; import { Languages } from "../Languages"; -import { Images } from "../Images"; -import { ImageCell } from "../../components/ImageCell"; -import { BiographiesCell } from "./components/BiographiesCell"; +import { Images } from "../Images/Images"; +import { ImageCell } from "../Images/components/ImageCell"; import { beforeDuplicate } from "./hooks/beforeDuplicate"; -import { BiographiesRowLabel } from "./components/BiographiesRowLabel"; const fields = { username: "username", @@ -31,12 +29,7 @@ export const Recorders: CollectionConfig = { hooks: { beforeDuplicate }, description: "Recorders are contributors of the Accord's Library project. Create a Recorder here to be able to credit them in other collections", - defaultColumns: [ - fields.username, - fields.anonymize, - fields.biographies, - fields.languages, - ], + defaultColumns: [fields.username, fields.anonymize, fields.biographies, fields.languages], }, timestamps: false, fields: [ @@ -87,8 +80,9 @@ export const Recorders: CollectionConfig = { name: fields.biographies, interfaceName: "RecorderBiographies", admin: { - initCollapsed: true, - components: { RowLabel: BiographiesRowLabel, Cell: BiographiesCell }, + useAsTitle: fields.biography, + description: + "A short personal description about you or your involvement with this project or the franchise", }, fields: [{ name: fields.biography, type: "textarea" }], }), diff --git a/src/collections/Recorders/components/BiographiesCell.tsx b/src/collections/Recorders/components/BiographiesCell.tsx deleted file mode 100644 index f55bcc2..0000000 --- a/src/collections/Recorders/components/BiographiesCell.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Props } from "payload/components/views/Cell"; -import { useMemo } from "react"; -import React from "react"; -import { RecorderBiographies } from "../../../types/collections"; -import { isDefined } from "../../../utils/asserts"; -import { formatLanguageCode } from "../../../utils/string"; - -export const BiographiesCell: React.FC = ({ cellData }) => { - if (!Array.isArray(cellData)) return <>No biographies; - - return ( -
- {cellData.map((biography: RecorderBiographies[number], index) => ( - - ))} -
- ); -}; - -const BiographyCell: React.FC< - RecorderBiographies[number] & { index: number } -> = ({ language, biography, index }) => { - const label = useMemo(() => { - if (isDefined(language) && typeof language === "string") { - return formatLanguageCode(language); - } - return `Biography ${index}`; - }, []); - - return ( -
- -
{label}
-
-
- ); -}; diff --git a/src/collections/Recorders/components/BiographiesRowLabel.ts b/src/collections/Recorders/components/BiographiesRowLabel.ts deleted file mode 100644 index 2170021..0000000 --- a/src/collections/Recorders/components/BiographiesRowLabel.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { RecorderBiographies } from "../../../types/collections"; -import { AdminComponent } from "../../../utils/components"; -import { isDefined } from "../../../utils/asserts"; -import { formatLanguageCode, shortenEllipsis } from "../../../utils/string"; - -export const BiographiesRowLabel: AdminComponent< - RecorderBiographies[number] -> = ({ data: { language, biography }, index }) => { - const labelValues = []; - if (isDefined(language) && typeof language === "string") { - labelValues.push(formatLanguageCode(language)); - } - if (isDefined(biography)) { - labelValues.push(shortenEllipsis(biography, 50)); - } - const label = labelValues.join(" — "); - if (label === "") return `Biography ${index}`; - return label; -}; diff --git a/src/elements/translatedFields/Cell.tsx b/src/elements/translatedFields/Cell.tsx new file mode 100644 index 0000000..f197e9f --- /dev/null +++ b/src/elements/translatedFields/Cell.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { Language } from "../../types/collections"; +import { isDefined } from "../../utils/asserts"; +import { formatLanguageCode } from "../../utils/string"; + +interface Props { + cellData: { language?: string | Language; title?: string }[]; +} + +export const Cell = ({ cellData }: Props): JSX.Element => ( +
+ {cellData.map(({ language, title }, index) => ( +
+ +
+ {isDefined(language) && typeof language === "string" + ? formatLanguageCode(language) + : index} +
+
+
+ ))} +
+); diff --git a/src/elements/translatedFields/RowLabel.tsx b/src/elements/translatedFields/RowLabel.tsx new file mode 100644 index 0000000..bea3f22 --- /dev/null +++ b/src/elements/translatedFields/RowLabel.tsx @@ -0,0 +1,22 @@ +import React from "react"; +import { isDefined } from "../../utils/asserts"; +import { formatLanguageCode, shortenEllipsis } from "../../utils/string"; +import { Language } from "../../types/collections"; + +interface Props { + language?: Language | string; + title?: string; +} + +export const RowLabel = ({ language, title }: Props): JSX.Element => { + return ( +
+ {isDefined(language) && typeof language === "string" && ( +
{formatLanguageCode(language)}
+ )} + {isDefined(title) && ( +
{shortenEllipsis(title, 50)}
+ )} +
+ ); +}; diff --git a/src/utils/fields.ts b/src/elements/translatedFields/translatedFields.ts similarity index 52% rename from src/utils/fields.ts rename to src/elements/translatedFields/translatedFields.ts index abd0d85..27ff6cb 100644 --- a/src/utils/fields.ts +++ b/src/elements/translatedFields/translatedFields.ts @@ -1,20 +1,43 @@ import { ArrayField } from "payload/types"; -import { hasDuplicates } from "./validation"; -import { isDefined, isUndefined } from "./asserts"; -import { Languages } from "../collections/Languages"; +import { hasDuplicates } from "../../utils/validation"; +import { isDefined, isUndefined } from "../../utils/asserts"; +import { Languages } from "../../collections/Languages"; +import { RowLabel } from "./RowLabel"; +import { Cell } from "./Cell"; const LANGUAGE_FIELD_NAME = "language"; -type LocalizedFieldsProps = Omit; +type LocalizedFieldsProps = Omit & { + admin?: ArrayField["admin"] & { useAsTitle?: string }; +}; type ArrayData = { [LANGUAGE_FIELD_NAME]?: string }[] | number | undefined; export const localizedFields = ({ fields, validate, + admin: { useAsTitle, ...admin }, ...otherProps }: LocalizedFieldsProps): ArrayField => ({ ...otherProps, type: "array", + admin: { + initCollapsed: true, + components: { + Cell: ({ cellData }) => + Cell({ + cellData: cellData.map((row) => ({ + language: row.language, + title: isDefined(useAsTitle) ? row[useAsTitle] : undefined, + })), + }), + RowLabel: ({ data }) => + RowLabel({ + language: data.language, + title: isDefined(useAsTitle) ? data[useAsTitle] : undefined, + }), + }, + ...admin, + }, validate: (value, options) => { const data = options.data[otherProps.name] as ArrayData; if (isUndefined(data)) return true; diff --git a/src/payload.config.ts b/src/payload.config.ts index e13d0a0..d7b195a 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -3,14 +3,15 @@ import path from "path"; import { Users } from "./collections/Users"; import { Languages } from "./collections/Languages"; import { Recorders } from "./collections/Recorders/Recorders"; -import { Images } from "./collections/Images"; +import { Images } from "./collections/Images/Images"; +import { Categories } from "./collections/Categories/Categories"; export default buildConfig({ serverURL: "http://localhost:3000", admin: { user: Users.slug, }, - collections: [Users, Languages, Recorders, Images], + collections: [Users, Languages, Recorders, Images, Categories], globals: [], telemetry: false, typescript: { diff --git a/src/types/collections.ts b/src/types/collections.ts index 3ad499c..a4d68ed 100644 --- a/src/types/collections.ts +++ b/src/types/collections.ts @@ -38,6 +38,7 @@ export interface Language { } export interface Recorder { id: string; + avatar?: string | Image; username: string; anonymize: boolean; languages?: string[] | Language[]; diff --git a/src/utils/asserts.ts b/src/utils/asserts.ts index e5c1e26..89682b2 100644 --- a/src/utils/asserts.ts +++ b/src/utils/asserts.ts @@ -1,9 +1,7 @@ export const isDefined = (value: T | null | undefined): value is T => value !== null && value !== undefined; -export const isUndefined = ( - value: T | null | undefined -): value is null | undefined => !isDefined(value); +export const isUndefined = (value: T | null | undefined): value is null | undefined => + !isDefined(value); -export const filterDefined = (array: (T | null | undefined)[]): T[] => - array.filter(isDefined); +export const filterDefined = (array: (T | null | undefined)[]): T[] => array.filter(isDefined); diff --git a/src/utils/validation.ts b/src/utils/validation.ts index a862373..1877248 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -1,2 +1 @@ -export const hasDuplicates = (list: T[]): boolean => - list.length !== new Set(list).size; +export const hasDuplicates = (list: T[]): boolean => list.length !== new Set(list).size;