Lots of things, again, again
This commit is contained in:
parent
f3253ce972
commit
6074ccceff
|
@ -0,0 +1,9 @@
|
||||||
|
import { RecordersRoles } from "../../constants";
|
||||||
|
import { Recorder } from "../../types/collections";
|
||||||
|
import { EndpointAccess } from "../../types/payload";
|
||||||
|
import { isDefined, isUndefined } from "../../utils/asserts";
|
||||||
|
|
||||||
|
export const mustBeAdmin: EndpointAccess<Recorder> = ({ user }) => {
|
||||||
|
if (isUndefined(user)) return false;
|
||||||
|
return isDefined(user.role) && user.role.includes(RecordersRoles.Admin);
|
||||||
|
};
|
|
@ -0,0 +1,9 @@
|
||||||
|
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);
|
||||||
|
};
|
|
@ -0,0 +1,8 @@
|
||||||
|
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;
|
||||||
|
};
|
|
@ -1,4 +1,3 @@
|
||||||
import { DateTime } from "luxon";
|
|
||||||
import { CollectionConfig } from "payload/types";
|
import { CollectionConfig } from "payload/types";
|
||||||
import {
|
import {
|
||||||
QuickFilters,
|
QuickFilters,
|
||||||
|
@ -7,10 +6,12 @@ import {
|
||||||
} from "../../components/QuickFilters";
|
} from "../../components/QuickFilters";
|
||||||
import { CollectionGroups, Collections } from "../../constants";
|
import { CollectionGroups, Collections } from "../../constants";
|
||||||
import { translatedFields } from "../../fields/translatedFields/translatedFields";
|
import { translatedFields } from "../../fields/translatedFields/translatedFields";
|
||||||
import { isEmpty, isUndefined } from "../../utils/asserts";
|
|
||||||
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
|
import { buildVersionedCollectionConfig } from "../../utils/versionedCollectionConfig";
|
||||||
import { importFromStrapi } from "./endpoints/importFromStrapi";
|
import { importFromStrapi } from "./endpoints/importFromStrapi";
|
||||||
import { beforeValidatePopulateNameField } from "./hooks/beforeValidatePopulateNameField";
|
import { beforeValidatePopulateNameField } from "./hooks/beforeValidatePopulateNameField";
|
||||||
|
import { validateDate } from "./validations/validateDate";
|
||||||
|
import { validateEventsTranslationsDescription } from "./validations/validateEventsTranslationsDescription";
|
||||||
|
import { validateEventsTranslationsTitle } from "./validations/validateEventsTranslationsTitle";
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
name: "name",
|
name: "name",
|
||||||
|
@ -65,15 +66,7 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig(
|
||||||
{
|
{
|
||||||
type: "group",
|
type: "group",
|
||||||
name: fields.date,
|
name: fields.date,
|
||||||
validate: ({ year, month, day } = {}) => {
|
validate: validateDate,
|
||||||
if (isUndefined(day)) return true;
|
|
||||||
if (isUndefined(month)) return "A month is required if a day is set";
|
|
||||||
const stringDate = `${year}/${month}/${day}`;
|
|
||||||
if (!DateTime.fromObject({ year, month, day }).isValid) {
|
|
||||||
return `The given date (${stringDate}) is not a valid date.`;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
type: "row",
|
type: "row",
|
||||||
|
@ -116,22 +109,12 @@ export const ChronologyItems: CollectionConfig = buildVersionedCollectionConfig(
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: fields.eventsTranslationsTitle,
|
name: fields.eventsTranslationsTitle,
|
||||||
validate: (_, { siblingData: { description, title } }) => {
|
validate: validateEventsTranslationsTitle,
|
||||||
if (isEmpty(description) && isEmpty(title)) {
|
|
||||||
return "This field is required if no description is set.";
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
type: "text",
|
type: "text",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: fields.eventsTranslationsDescription,
|
name: fields.eventsTranslationsDescription,
|
||||||
validate: (_, { siblingData: { description, title } }) => {
|
validate: validateEventsTranslationsDescription,
|
||||||
if (isEmpty(description) && isEmpty(title)) {
|
|
||||||
return "This field is required if no title is set.";
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
type: "textarea",
|
type: "textarea",
|
||||||
},
|
},
|
||||||
{ name: fields.eventsTranslationsNotes, type: "textarea" },
|
{ name: fields.eventsTranslationsNotes, type: "textarea" },
|
||||||
|
|
|
@ -7,17 +7,14 @@ export const beforeValidatePopulateNameField: FieldHook<
|
||||||
ChronologyItem["name"],
|
ChronologyItem["name"],
|
||||||
ChronologyItem
|
ChronologyItem
|
||||||
> = ({ data }) => {
|
> = ({ data }) => {
|
||||||
if (isUndefined(data)) {
|
if (isUndefined(data) || isUndefined(data.date) || isUndefined(data.date.year))
|
||||||
return "????-??-??";
|
return "????-??-??";
|
||||||
}
|
const { year, month, day } = data.date;
|
||||||
const { date } = data;
|
|
||||||
if (isUndefined(date) || isUndefined(date?.year)) return "????-??-??";
|
|
||||||
const { year, month, day } = date;
|
|
||||||
let result = String(year).padStart(5, " ");
|
let result = String(year).padStart(5, " ");
|
||||||
if (isDefined(month)) {
|
if (isDefined(month)) {
|
||||||
result += `-${String(date.month).padStart(2, "0")}`;
|
result += `-${String(month).padStart(2, "0")}`;
|
||||||
if (isDefined(day)) {
|
if (isDefined(day)) {
|
||||||
result += `-${String(date.day).padStart(2, "0")}`;
|
result += `-${String(day).padStart(2, "0")}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
import { Validate } from "payload/types";
|
||||||
|
import { ChronologyItem } from "../../../types/collections";
|
||||||
|
import { isUndefined } from "../../../utils/asserts";
|
||||||
|
|
||||||
|
export const validateDate: Validate<ChronologyItem["date"] | undefined> = (date) => {
|
||||||
|
if (isUndefined(date)) return "This field is required.";
|
||||||
|
const { year, month, day } = date;
|
||||||
|
if (isUndefined(day)) return true;
|
||||||
|
if (isUndefined(month)) return "A month is required if a day is set.";
|
||||||
|
const stringDate = `${year}/${month}/${day}`;
|
||||||
|
if (!DateTime.fromObject({ year, month, day }).isValid) {
|
||||||
|
return `The given date (${stringDate}) is not a valid date.`;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Validate } from "payload/types";
|
||||||
|
import { ChronologyItem } from "../../../types/collections";
|
||||||
|
import { isEmpty } from "../../../utils/asserts";
|
||||||
|
|
||||||
|
export const validateEventsTranslationsDescription: Validate<
|
||||||
|
string | undefined,
|
||||||
|
ChronologyItem,
|
||||||
|
ChronologyItem["events"][number]["translations"][number],
|
||||||
|
unknown
|
||||||
|
> = (_, { siblingData: { description, title } }) => {
|
||||||
|
if (isEmpty(description) && isEmpty(title)) {
|
||||||
|
return "This field is required if no title is set.";
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Validate } from "payload/types";
|
||||||
|
import { ChronologyItem } from "../../../types/collections";
|
||||||
|
import { isEmpty } from "../../../utils/asserts";
|
||||||
|
|
||||||
|
export const validateEventsTranslationsTitle: Validate<
|
||||||
|
string | undefined,
|
||||||
|
ChronologyItem,
|
||||||
|
ChronologyItem["events"][number]["translations"][number],
|
||||||
|
unknown
|
||||||
|
> = (_, { siblingData: { description, title } }) => {
|
||||||
|
if (isEmpty(description) && isEmpty(title)) {
|
||||||
|
return "This field is required if no description is set.";
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
|
@ -46,8 +46,8 @@ export const Contents = buildVersionedCollectionConfig({
|
||||||
description:
|
description:
|
||||||
"All the contents (textual, audio, and video) from the Library or other online sources.",
|
"All the contents (textual, audio, and video) from the Library or other online sources.",
|
||||||
defaultColumns: [
|
defaultColumns: [
|
||||||
fields.slug,
|
|
||||||
fields.thumbnail,
|
fields.thumbnail,
|
||||||
|
fields.slug,
|
||||||
fields.categories,
|
fields.categories,
|
||||||
fields.type,
|
fields.type,
|
||||||
fields.translations,
|
fields.translations,
|
||||||
|
@ -175,7 +175,7 @@ export const Contents = buildVersionedCollectionConfig({
|
||||||
fields: [
|
fields: [
|
||||||
fileField({
|
fileField({
|
||||||
name: fields.video,
|
name: fields.video,
|
||||||
filterOptions: { type: { equals: FileTypes.ContentVideo } },
|
relationTo: FileTypes.ContentVideo,
|
||||||
admin: { width: "50%" },
|
admin: { width: "50%" },
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
@ -196,7 +196,7 @@ export const Contents = buildVersionedCollectionConfig({
|
||||||
fields: [
|
fields: [
|
||||||
fileField({
|
fileField({
|
||||||
name: fields.audio,
|
name: fields.audio,
|
||||||
filterOptions: { type: { equals: FileTypes.ContentAudio } },
|
relationTo: FileTypes.ContentAudio,
|
||||||
admin: { width: "50%" },
|
admin: { width: "50%" },
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { CollectionGroups, Collections } from "../../constants";
|
import { Collections } from "../../constants";
|
||||||
|
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
|
||||||
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
filename: "filename",
|
filename: "filename",
|
||||||
mimeType: "mimeType",
|
mimeType: "mimeType",
|
||||||
filesize: "filesize",
|
filesize: "filesize",
|
||||||
|
contents: "contents",
|
||||||
|
updatedAt: "updatedAt",
|
||||||
} as const satisfies Record<string, string>;
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
export const ContentsThumbnails = buildImageCollectionConfig({
|
export const ContentsThumbnails = buildImageCollectionConfig({
|
||||||
|
@ -13,12 +16,7 @@ export const ContentsThumbnails = buildImageCollectionConfig({
|
||||||
singular: "Contents Thumbnail",
|
singular: "Contents Thumbnail",
|
||||||
plural: "Contents Thumbnails",
|
plural: "Contents Thumbnails",
|
||||||
},
|
},
|
||||||
defaultSort: fields.filename,
|
admin: { defaultColumns: [fields.filename, fields.contents, fields.updatedAt] },
|
||||||
admin: {
|
|
||||||
useAsTitle: fields.filename,
|
|
||||||
disableDuplicate: true,
|
|
||||||
group: CollectionGroups.Media,
|
|
||||||
},
|
|
||||||
upload: {
|
upload: {
|
||||||
imageSizes: [
|
imageSizes: [
|
||||||
{
|
{
|
||||||
|
@ -39,7 +37,21 @@ export const ContentsThumbnails = buildImageCollectionConfig({
|
||||||
options: { effort: 6, quality: 80, alphaQuality: 80 },
|
options: { effort: 6, quality: 80, alphaQuality: 80 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "max",
|
||||||
|
formatOptions: {
|
||||||
|
format: "webp",
|
||||||
|
options: { effort: 6, quality: 80, alphaQuality: 80 },
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
fields: [],
|
fields: [
|
||||||
|
backPropagationField({
|
||||||
|
name: fields.contents,
|
||||||
|
hasMany: true,
|
||||||
|
relationTo: Collections.Contents,
|
||||||
|
where: ({ id }) => ({ thumbnail: { equals: id } }),
|
||||||
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { CollectionGroups, Collections, FileTypes } from "../../constants";
|
import { CollectionGroups, Collections, FileTypes } from "../../constants";
|
||||||
|
import { File } from "../../types/collections";
|
||||||
import { buildCollectionConfig } from "../../utils/collectionConfig";
|
import { buildCollectionConfig } from "../../utils/collectionConfig";
|
||||||
|
import {
|
||||||
|
beforeValidateCheckFileExists,
|
||||||
|
generatePathForFile,
|
||||||
|
} from "./hooks/beforeValidateCheckFileExists";
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
filename: "filename",
|
filename: "filename",
|
||||||
|
@ -17,6 +22,13 @@ export const Files = buildCollectionConfig({
|
||||||
useAsTitle: fields.filename,
|
useAsTitle: fields.filename,
|
||||||
disableDuplicate: true,
|
disableDuplicate: true,
|
||||||
group: CollectionGroups.Media,
|
group: CollectionGroups.Media,
|
||||||
|
preview: (doc) => {
|
||||||
|
const { filename, type } = doc as unknown as File;
|
||||||
|
return generatePathForFile(type, filename);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
beforeValidate: [beforeValidateCheckFileExists],
|
||||||
},
|
},
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { CollectionBeforeValidateHook } from "payload/types";
|
||||||
|
import { FileTypes } from "../../../constants";
|
||||||
|
import { File } from "../../../types/collections";
|
||||||
|
import { isUndefined } from "../../../utils/asserts";
|
||||||
|
|
||||||
|
const reshareSubFolderFromType: Record<keyof typeof FileTypes, string> = {
|
||||||
|
ContentAudio: "/contents/audios",
|
||||||
|
ContentVideo: "/contents/videos",
|
||||||
|
LibraryScans: "/library/scans",
|
||||||
|
LibrarySoundtracks: "/library/tracks",
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectedMimeFromType = {
|
||||||
|
ContentAudio: "audio/",
|
||||||
|
ContentVideo: "video/",
|
||||||
|
LibraryScans: "application/zip",
|
||||||
|
LibrarySoundtracks: "audio/",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generatePathForFile = (type: keyof typeof FileTypes, filename: string) =>
|
||||||
|
`https://resha.re/accords${reshareSubFolderFromType[type]}/${filename}`;
|
||||||
|
|
||||||
|
export const beforeValidateCheckFileExists: CollectionBeforeValidateHook<File> = async ({
|
||||||
|
data,
|
||||||
|
}) => {
|
||||||
|
if (isUndefined(data)) throw new Error("The data is undefined");
|
||||||
|
const { type, filename } = data;
|
||||||
|
if (isUndefined(filename)) throw new Error("Filename is undefined");
|
||||||
|
if (isUndefined(type)) throw new Error("Filename is undefined");
|
||||||
|
|
||||||
|
const url = generatePathForFile(type, filename);
|
||||||
|
|
||||||
|
const result = await fetch(url, { method: "HEAD" });
|
||||||
|
|
||||||
|
if (result.status !== 200) {
|
||||||
|
throw new Error(`Unable to locate the file at the following address: ${url}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = result.headers.get("content-type");
|
||||||
|
if (isUndefined(contentType) || !contentType.startsWith(expectedMimeFromType[type])) {
|
||||||
|
throw new Error(
|
||||||
|
`Wrong MIME type found: ${contentType}. The expected MIME type was ${expectedMimeFromType[type]}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
|
@ -2,11 +2,13 @@ import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types
|
||||||
import {
|
import {
|
||||||
CollectionGroups,
|
CollectionGroups,
|
||||||
Collections,
|
Collections,
|
||||||
|
FileTypes,
|
||||||
KeysTypes,
|
KeysTypes,
|
||||||
LibraryItemsTextualBindingTypes,
|
LibraryItemsTextualBindingTypes,
|
||||||
LibraryItemsTextualPageOrders,
|
LibraryItemsTextualPageOrders,
|
||||||
LibraryItemsTypes,
|
LibraryItemsTypes,
|
||||||
} from "../../constants";
|
} from "../../constants";
|
||||||
|
import { fileField } from "../../fields/fileField/fileField";
|
||||||
import { imageField } from "../../fields/imageField/imageField";
|
import { imageField } from "../../fields/imageField/imageField";
|
||||||
import { keysField } from "../../fields/keysField/keysField";
|
import { keysField } from "../../fields/keysField/keysField";
|
||||||
import { optionalGroupField } from "../../fields/optionalGroupField/optionalGroupField";
|
import { optionalGroupField } from "../../fields/optionalGroupField/optionalGroupField";
|
||||||
|
@ -66,6 +68,9 @@ const fields = {
|
||||||
|
|
||||||
audio: "audio",
|
audio: "audio",
|
||||||
audioSubtype: "audioSubtype",
|
audioSubtype: "audioSubtype",
|
||||||
|
audioTracks: "tracks",
|
||||||
|
audioTracksFile: "file",
|
||||||
|
audioTracksTitle: "title",
|
||||||
|
|
||||||
scans: "scans",
|
scans: "scans",
|
||||||
|
|
||||||
|
@ -129,7 +134,7 @@ export const LibraryItems = buildVersionedCollectionConfig({
|
||||||
description:
|
description:
|
||||||
"A comprehensive list of all Yokoverse’s side materials (books, novellas, artbooks, \
|
"A comprehensive list of all Yokoverse’s side materials (books, novellas, artbooks, \
|
||||||
stage plays, manga, drama CDs, and comics).",
|
stage plays, manga, drama CDs, and comics).",
|
||||||
defaultColumns: [fields.slug, fields.thumbnail, fields.status],
|
defaultColumns: [fields.thumbnail, fields.slug, fields.status],
|
||||||
group: CollectionGroups.Collections,
|
group: CollectionGroups.Collections,
|
||||||
hooks: {
|
hooks: {
|
||||||
beforeDuplicate: beforeDuplicatePiping([
|
beforeDuplicate: beforeDuplicatePiping([
|
||||||
|
@ -596,6 +601,29 @@ export const LibraryItems = buildVersionedCollectionConfig({
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: fields.audioTracks,
|
||||||
|
type: "array",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: "row",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: fields.audioTracksTitle,
|
||||||
|
type: "text",
|
||||||
|
required: true,
|
||||||
|
admin: { width: "50%" },
|
||||||
|
},
|
||||||
|
fileField({
|
||||||
|
name: fields.audioTracksFile,
|
||||||
|
relationTo: FileTypes.LibrarySoundtracks,
|
||||||
|
required: true,
|
||||||
|
admin: { width: "50%" },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { styled } from "styled-components";
|
import { styled } from "styled-components";
|
||||||
import { isDefined } from "../../../utils/asserts";
|
import { isDefined } from "../../../utils/asserts";
|
||||||
import { formatLanguageCode, shortenEllipsis } from "../../../utils/string";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
page?: number;
|
page?: number;
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { CollectionGroups, Collections } from "../../constants";
|
import { Collections } from "../../constants";
|
||||||
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
filename: "filename",
|
filename: "filename",
|
||||||
mimeType: "mimeType",
|
mimeType: "mimeType",
|
||||||
filesize: "filesize",
|
filesize: "filesize",
|
||||||
|
updatedAt: "updatedAt",
|
||||||
} as const satisfies Record<string, string>;
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
export const LibraryItemsGallery = buildImageCollectionConfig({
|
export const LibraryItemsGallery = buildImageCollectionConfig({
|
||||||
|
@ -13,12 +14,7 @@ export const LibraryItemsGallery = buildImageCollectionConfig({
|
||||||
singular: "Library Item Gallery",
|
singular: "Library Item Gallery",
|
||||||
plural: "Library Item Gallery",
|
plural: "Library Item Gallery",
|
||||||
},
|
},
|
||||||
defaultSort: fields.filename,
|
admin: { defaultColumns: [fields.filename, fields.updatedAt] },
|
||||||
admin: {
|
|
||||||
useAsTitle: fields.filename,
|
|
||||||
disableDuplicate: true,
|
|
||||||
group: CollectionGroups.Media,
|
|
||||||
},
|
|
||||||
upload: {
|
upload: {
|
||||||
imageSizes: [
|
imageSizes: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { CollectionGroups, Collections } from "../../constants";
|
import { Collections } from "../../constants";
|
||||||
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
filename: "filename",
|
filename: "filename",
|
||||||
mimeType: "mimeType",
|
mimeType: "mimeType",
|
||||||
filesize: "filesize",
|
filesize: "filesize",
|
||||||
|
updatedAt: "updatedAt",
|
||||||
} as const satisfies Record<string, string>;
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
export const LibraryItemsScans = buildImageCollectionConfig({
|
export const LibraryItemsScans = buildImageCollectionConfig({
|
||||||
|
@ -13,12 +14,7 @@ export const LibraryItemsScans = buildImageCollectionConfig({
|
||||||
singular: "Library Item Scans",
|
singular: "Library Item Scans",
|
||||||
plural: "Library Item Scans",
|
plural: "Library Item Scans",
|
||||||
},
|
},
|
||||||
defaultSort: fields.filename,
|
admin: { defaultColumns: [fields.filename, fields.updatedAt] },
|
||||||
admin: {
|
|
||||||
useAsTitle: fields.filename,
|
|
||||||
disableDuplicate: true,
|
|
||||||
group: CollectionGroups.Media,
|
|
||||||
},
|
|
||||||
upload: {
|
upload: {
|
||||||
imageSizes: [
|
imageSizes: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { CollectionGroups, Collections } from "../../constants";
|
import { Collections } from "../../constants";
|
||||||
|
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
|
||||||
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
filename: "filename",
|
filename: "filename",
|
||||||
mimeType: "mimeType",
|
mimeType: "mimeType",
|
||||||
filesize: "filesize",
|
filesize: "filesize",
|
||||||
|
libraryItem: "libraryItem",
|
||||||
|
updatedAt: "updatedAt",
|
||||||
} as const satisfies Record<string, string>;
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
export const LibraryItemsThumbnails = buildImageCollectionConfig({
|
export const LibraryItemsThumbnails = buildImageCollectionConfig({
|
||||||
|
@ -13,12 +16,7 @@ export const LibraryItemsThumbnails = buildImageCollectionConfig({
|
||||||
singular: "Library Item Thumbnail",
|
singular: "Library Item Thumbnail",
|
||||||
plural: "Library Item Thumbnails",
|
plural: "Library Item Thumbnails",
|
||||||
},
|
},
|
||||||
defaultSort: fields.filename,
|
admin: { defaultColumns: [fields.filename, fields.libraryItem, fields.updatedAt] },
|
||||||
admin: {
|
|
||||||
useAsTitle: fields.filename,
|
|
||||||
disableDuplicate: true,
|
|
||||||
group: CollectionGroups.Media,
|
|
||||||
},
|
|
||||||
upload: {
|
upload: {
|
||||||
imageSizes: [
|
imageSizes: [
|
||||||
{
|
{
|
||||||
|
@ -51,5 +49,12 @@ export const LibraryItemsThumbnails = buildImageCollectionConfig({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
fields: [],
|
fields: [
|
||||||
|
backPropagationField({
|
||||||
|
name: fields.libraryItem,
|
||||||
|
hasMany: true,
|
||||||
|
relationTo: Collections.LibraryItems,
|
||||||
|
where: ({ id }) => ({ thumbnail: { equals: id } }),
|
||||||
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,7 +37,7 @@ export const Posts = buildVersionedCollectionConfig({
|
||||||
description:
|
description:
|
||||||
"News articles written by our Recorders! Here you will find announcements about \
|
"News articles written by our Recorders! Here you will find announcements about \
|
||||||
new merch/items releases, guides, theories, unboxings, showcases...",
|
new merch/items releases, guides, theories, unboxings, showcases...",
|
||||||
defaultColumns: [fields.slug, fields.thumbnail, fields.categories],
|
defaultColumns: [fields.thumbnail, fields.slug, fields.categories],
|
||||||
group: CollectionGroups.Collections,
|
group: CollectionGroups.Collections,
|
||||||
components: {
|
components: {
|
||||||
BeforeListTable: [
|
BeforeListTable: [
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { CollectionGroups, Collections } from "../../constants";
|
import { Collections } from "../../constants";
|
||||||
|
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
|
||||||
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
filename: "filename",
|
filename: "filename",
|
||||||
mimeType: "mimeType",
|
mimeType: "mimeType",
|
||||||
filesize: "filesize",
|
filesize: "filesize",
|
||||||
|
posts: "posts",
|
||||||
|
updatedAt: "updatedAt",
|
||||||
} as const satisfies Record<string, string>;
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
export const PostsThumbnails = buildImageCollectionConfig({
|
export const PostsThumbnails = buildImageCollectionConfig({
|
||||||
|
@ -13,12 +16,7 @@ export const PostsThumbnails = buildImageCollectionConfig({
|
||||||
singular: "Post Thumbnail",
|
singular: "Post Thumbnail",
|
||||||
plural: "Post Thumbnails",
|
plural: "Post Thumbnails",
|
||||||
},
|
},
|
||||||
defaultSort: fields.filename,
|
admin: { defaultColumns: [fields.filename, fields.posts, fields.updatedAt] },
|
||||||
admin: {
|
|
||||||
useAsTitle: fields.filename,
|
|
||||||
disableDuplicate: true,
|
|
||||||
group: CollectionGroups.Media,
|
|
||||||
},
|
|
||||||
upload: {
|
upload: {
|
||||||
imageSizes: [
|
imageSizes: [
|
||||||
{
|
{
|
||||||
|
@ -41,5 +39,12 @@ export const PostsThumbnails = buildImageCollectionConfig({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
fields: [],
|
fields: [
|
||||||
|
backPropagationField({
|
||||||
|
name: fields.posts,
|
||||||
|
hasMany: true,
|
||||||
|
relationTo: Collections.Posts,
|
||||||
|
where: ({ id }) => ({ thumbnail: { equals: id } }),
|
||||||
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,8 +32,8 @@ export const Recorders = buildCollectionConfig({
|
||||||
"Recorders are contributors of the Accord's Library project. Ask an admin to create a \
|
"Recorders are contributors of the Accord's Library project. Ask an admin to create a \
|
||||||
Recorder here to be able to credit them in other collections.",
|
Recorder here to be able to credit them in other collections.",
|
||||||
defaultColumns: [
|
defaultColumns: [
|
||||||
fields.username,
|
|
||||||
fields.avatar,
|
fields.avatar,
|
||||||
|
fields.username,
|
||||||
fields.anonymize,
|
fields.anonymize,
|
||||||
fields.biographies,
|
fields.biographies,
|
||||||
fields.languages,
|
fields.languages,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImpor
|
||||||
import { Recorder } from "../../../types/collections";
|
import { Recorder } from "../../../types/collections";
|
||||||
import { PayloadCreateData } from "../../../types/payload";
|
import { PayloadCreateData } from "../../../types/payload";
|
||||||
import { StrapiImage, StrapiLanguage } from "../../../types/strapi";
|
import { StrapiImage, StrapiLanguage } from "../../../types/strapi";
|
||||||
import { isUndefined } from "../../../utils/asserts";
|
import { isDefined, isUndefined } from "../../../utils/asserts";
|
||||||
import { uploadStrapiImage } from "../../../utils/localApi";
|
import { uploadStrapiImage } from "../../../utils/localApi";
|
||||||
|
|
||||||
type StrapiRecorder = {
|
type StrapiRecorder = {
|
||||||
|
@ -31,9 +31,7 @@ export const importFromStrapi = createStrapiImportEndpoint<Recorder, StrapiRecor
|
||||||
image: avatar,
|
image: avatar,
|
||||||
});
|
});
|
||||||
|
|
||||||
const data: PayloadCreateData<Recorder> = {
|
const data: Omit<PayloadCreateData<Recorder>, "password" | "email"> = {
|
||||||
email: `${anonymous_code}@accords-library.com`,
|
|
||||||
password: process.env.RECORDER_DEFAULT_PASSWORD,
|
|
||||||
username,
|
username,
|
||||||
anonymize,
|
anonymize,
|
||||||
languages: languages.data?.map((language) => language.attributes.code),
|
languages: languages.data?.map((language) => language.attributes.code),
|
||||||
|
@ -49,7 +47,26 @@ export const importFromStrapi = createStrapiImportEndpoint<Recorder, StrapiRecor
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
await payload.create({ collection: Collections.Recorders, data, user });
|
const recorder = (
|
||||||
|
await payload.find({
|
||||||
|
collection: Collections.Recorders,
|
||||||
|
where: { username: { equals: username } },
|
||||||
|
})
|
||||||
|
).docs[0] as Recorder | undefined;
|
||||||
|
|
||||||
|
if (isDefined(recorder)) {
|
||||||
|
await payload.update({ collection: Collections.Recorders, id: recorder.id, data, user });
|
||||||
|
} else {
|
||||||
|
await payload.create({
|
||||||
|
collection: Collections.Recorders,
|
||||||
|
data: {
|
||||||
|
...data,
|
||||||
|
email: `${anonymous_code}@accords-library.com`,
|
||||||
|
password: process.env.RECORDER_DEFAULT_PASSWORD,
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CollectionGroups, Collections } from "../../constants";
|
import { Collections } from "../../constants";
|
||||||
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
|
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
|
||||||
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ const fields = {
|
||||||
mimeType: "mimeType",
|
mimeType: "mimeType",
|
||||||
filesize: "filesize",
|
filesize: "filesize",
|
||||||
recorder: "recorder",
|
recorder: "recorder",
|
||||||
|
updatedAt: "updatedAt",
|
||||||
} as const satisfies Record<string, string>;
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
export const RecordersThumbnails = buildImageCollectionConfig({
|
export const RecordersThumbnails = buildImageCollectionConfig({
|
||||||
|
@ -15,27 +16,14 @@ export const RecordersThumbnails = buildImageCollectionConfig({
|
||||||
singular: "Recorders Thumbnail",
|
singular: "Recorders Thumbnail",
|
||||||
plural: "Recorders Thumbnails",
|
plural: "Recorders Thumbnails",
|
||||||
},
|
},
|
||||||
defaultSort: fields.filename,
|
admin: { defaultColumns: [fields.filename, fields.recorder, fields.updatedAt] },
|
||||||
admin: {
|
|
||||||
useAsTitle: fields.filename,
|
|
||||||
disableDuplicate: true,
|
|
||||||
group: CollectionGroups.Media,
|
|
||||||
},
|
|
||||||
upload: {
|
upload: {
|
||||||
imageSizes: [
|
imageSizes: [
|
||||||
{
|
{
|
||||||
name: "og",
|
name: "square",
|
||||||
height: 256,
|
height: 150,
|
||||||
width: 256,
|
width: 150,
|
||||||
formatOptions: {
|
fit: "cover",
|
||||||
format: "jpg",
|
|
||||||
options: { progressive: true, mozjpeg: true, compressionLevel: 9, quality: 80 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "small",
|
|
||||||
height: 128,
|
|
||||||
width: 128,
|
|
||||||
formatOptions: {
|
formatOptions: {
|
||||||
format: "webp",
|
format: "webp",
|
||||||
options: { effort: 6, quality: 80, alphaQuality: 80 },
|
options: { effort: 6, quality: 80, alphaQuality: 80 },
|
||||||
|
|
|
@ -94,7 +94,6 @@ export const Videos: CollectionConfig = buildCollectionConfig({
|
||||||
name: fields.channel,
|
name: fields.channel,
|
||||||
type: "relationship",
|
type: "relationship",
|
||||||
relationTo: Collections.VideosChannels,
|
relationTo: Collections.VideosChannels,
|
||||||
required: true,
|
|
||||||
admin: { position: "sidebar" },
|
admin: { position: "sidebar" },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import payload from "payload";
|
import payload from "payload";
|
||||||
import { Collections } from "../../../constants";
|
import { Collections, VideoSources } from "../../../constants";
|
||||||
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
|
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
|
||||||
import { Video, VideosChannel } from "../../../types/collections";
|
import { Video, VideosChannel } from "../../../types/collections";
|
||||||
import { PayloadCreateData } from "../../../types/payload";
|
import { PayloadCreateData } from "../../../types/payload";
|
||||||
import { isUndefined } from "../../../utils/asserts";
|
import { isDefined, isUndefined } from "../../../utils/asserts";
|
||||||
|
|
||||||
type StapiVideo = {
|
type StapiVideo = {
|
||||||
uid: string;
|
uid: string;
|
||||||
|
@ -16,7 +16,7 @@ type StapiVideo = {
|
||||||
};
|
};
|
||||||
views: number;
|
views: number;
|
||||||
likes: number;
|
likes: number;
|
||||||
source?: "YouTube" | "NicoNico" | "Tumblr";
|
source?: VideoSources;
|
||||||
gone: boolean;
|
gone: boolean;
|
||||||
channel: { data?: { attributes: { uid: string; title: string; subscribers: number } } };
|
channel: { data?: { attributes: { uid: string; title: string; subscribers: number } } };
|
||||||
};
|
};
|
||||||
|
@ -43,31 +43,34 @@ export const importFromStrapi = createStrapiImportEndpoint<Video, StapiVideo>({
|
||||||
user
|
user
|
||||||
) => {
|
) => {
|
||||||
if (isUndefined(source)) throw new Error("A source is required to create a Video");
|
if (isUndefined(source)) throw new Error("A source is required to create a Video");
|
||||||
if (isUndefined(channel.data)) throw new Error("A channel is required to create a Video");
|
if (source === VideoSources.YouTube && isUndefined(channel.data))
|
||||||
|
throw new Error("A channel is required to create a YouTube Video");
|
||||||
|
|
||||||
try {
|
let videoChannelId;
|
||||||
const videoChannel: PayloadCreateData<VideosChannel> = {
|
if (isDefined(channel.data)) {
|
||||||
uid: channel.data.attributes.uid,
|
try {
|
||||||
title: channel.data.attributes.title,
|
const videoChannel: PayloadCreateData<VideosChannel> = {
|
||||||
subscribers: channel.data.attributes.subscribers,
|
uid: channel.data.attributes.uid,
|
||||||
};
|
title: channel.data.attributes.title,
|
||||||
await payload.create({
|
subscribers: channel.data.attributes.subscribers,
|
||||||
|
};
|
||||||
|
await payload.create({
|
||||||
|
collection: Collections.VideosChannels,
|
||||||
|
data: videoChannel,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
const result = await payload.find({
|
||||||
collection: Collections.VideosChannels,
|
collection: Collections.VideosChannels,
|
||||||
data: videoChannel,
|
where: { uid: { equals: channel.data.attributes.uid } },
|
||||||
user,
|
|
||||||
});
|
});
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
const result = await payload.find({
|
if (result.docs.length > 0) {
|
||||||
collection: Collections.VideosChannels,
|
videoChannelId = result.docs[0].id;
|
||||||
where: { uid: { equals: channel.data.attributes.uid } },
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (result.docs.length === 0) {
|
|
||||||
throw new Error("A video channel is required to create a video");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoChannel = result.docs[0] as VideosChannel;
|
|
||||||
const video: PayloadCreateData<Video> = {
|
const video: PayloadCreateData<Video> = {
|
||||||
uid,
|
uid,
|
||||||
title,
|
title,
|
||||||
|
@ -77,7 +80,7 @@ export const importFromStrapi = createStrapiImportEndpoint<Video, StapiVideo>({
|
||||||
gone,
|
gone,
|
||||||
source,
|
source,
|
||||||
publishedDate: `${year}-${month}-${day}`,
|
publishedDate: `${year}-${month}-${day}`,
|
||||||
channel: videoChannel.id,
|
channel: videoChannelId,
|
||||||
};
|
};
|
||||||
|
|
||||||
await payload.create({
|
await payload.create({
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { CollectionConfig } from "payload/types";
|
||||||
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
|
import { mustBeAdmin } from "../../accesses/collections/mustBeAdmin";
|
||||||
import { CollectionGroups, Collections } from "../../constants";
|
import { CollectionGroups, Collections } from "../../constants";
|
||||||
import { buildCollectionConfig } from "../../utils/collectionConfig";
|
import { buildCollectionConfig } from "../../utils/collectionConfig";
|
||||||
import { importFromStrapi } from "./endpoints/importFromStrapi";
|
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
uid: "uid",
|
uid: "uid",
|
||||||
|
@ -28,7 +27,6 @@ export const VideosChannels: CollectionConfig = buildCollectionConfig({
|
||||||
create: mustBeAdmin,
|
create: mustBeAdmin,
|
||||||
delete: mustBeAdmin,
|
delete: mustBeAdmin,
|
||||||
},
|
},
|
||||||
endpoints: [importFromStrapi],
|
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
fields: [
|
fields: [
|
||||||
{ name: fields.uid, type: "text", required: true, unique: true },
|
{ name: fields.uid, type: "text", required: true, unique: true },
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
import { Collections } from "../../../constants";
|
|
||||||
import { createStrapiImportEndpoint } from "../../../endpoints/createStrapiImportEndpoint";
|
|
||||||
import { VideosChannel } from "../../../types/collections";
|
|
||||||
|
|
||||||
type StrapiVideoChannel = {
|
|
||||||
uid: string;
|
|
||||||
title: string;
|
|
||||||
subscribers: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const importFromStrapi = createStrapiImportEndpoint<VideosChannel, StrapiVideoChannel>({
|
|
||||||
strapi: {
|
|
||||||
collection: "video-channels",
|
|
||||||
params: {},
|
|
||||||
},
|
|
||||||
payload: {
|
|
||||||
collection: Collections.VideosChannels,
|
|
||||||
convert: ({ uid, title, subscribers }) => ({
|
|
||||||
uid,
|
|
||||||
title,
|
|
||||||
subscribers,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types";
|
import { RowLabelArgs } from "payload/dist/admin/components/forms/RowLabel/types";
|
||||||
import { CollectionGroups, Collections, KeysTypes } from "../../constants";
|
import { CollectionGroups, Collections, KeysTypes } from "../../constants";
|
||||||
|
import { createGetByEndpoint } from "../../endpoints/createGetByEndpoint";
|
||||||
|
import { createGetSlugsEndpoint } from "../../endpoints/createGetSlugsEndpoint";
|
||||||
import { imageField } from "../../fields/imageField/imageField";
|
import { imageField } from "../../fields/imageField/imageField";
|
||||||
import { keysField } from "../../fields/keysField/keysField";
|
import { keysField } from "../../fields/keysField/keysField";
|
||||||
import { slugField } from "../../fields/slugField/slugField";
|
import { slugField } from "../../fields/slugField/slugField";
|
||||||
|
@ -32,8 +34,8 @@ export const Weapons = buildVersionedCollectionConfig({
|
||||||
admin: {
|
admin: {
|
||||||
useAsTitle: fields.slug,
|
useAsTitle: fields.slug,
|
||||||
defaultColumns: [
|
defaultColumns: [
|
||||||
fields.slug,
|
|
||||||
fields.thumbnail,
|
fields.thumbnail,
|
||||||
|
fields.slug,
|
||||||
fields.group,
|
fields.group,
|
||||||
fields.type,
|
fields.type,
|
||||||
fields.appearances,
|
fields.appearances,
|
||||||
|
@ -41,7 +43,11 @@ export const Weapons = buildVersionedCollectionConfig({
|
||||||
],
|
],
|
||||||
group: CollectionGroups.Collections,
|
group: CollectionGroups.Collections,
|
||||||
},
|
},
|
||||||
endpoints: [importFromStrapi],
|
endpoints: [
|
||||||
|
importFromStrapi,
|
||||||
|
createGetSlugsEndpoint(Collections.Weapons),
|
||||||
|
createGetByEndpoint(Collections.Weapons, "slug"),
|
||||||
|
],
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
type: "row",
|
type: "row",
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { CollectionGroups, Collections } from "../../constants";
|
import { Collections } from "../../constants";
|
||||||
|
import { backPropagationField } from "../../fields/backPropagationField/backPropagationField";
|
||||||
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
import { buildImageCollectionConfig } from "../../utils/imageCollectionConfig";
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
filename: "filename",
|
filename: "filename",
|
||||||
mimeType: "mimeType",
|
mimeType: "mimeType",
|
||||||
filesize: "filesize",
|
filesize: "filesize",
|
||||||
|
weapon: "weapon",
|
||||||
|
updatedAt: "updatedAt",
|
||||||
} as const satisfies Record<string, string>;
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
export const WeaponsThumbnails = buildImageCollectionConfig({
|
export const WeaponsThumbnails = buildImageCollectionConfig({
|
||||||
|
@ -13,12 +16,7 @@ export const WeaponsThumbnails = buildImageCollectionConfig({
|
||||||
singular: "Weapons Thumbnail",
|
singular: "Weapons Thumbnail",
|
||||||
plural: "Weapons Thumbnails",
|
plural: "Weapons Thumbnails",
|
||||||
},
|
},
|
||||||
defaultSort: fields.filename,
|
admin: { defaultColumns: [fields.filename, fields.weapon, fields.updatedAt] },
|
||||||
admin: {
|
|
||||||
useAsTitle: fields.filename,
|
|
||||||
disableDuplicate: true,
|
|
||||||
group: CollectionGroups.Media,
|
|
||||||
},
|
|
||||||
upload: {
|
upload: {
|
||||||
imageSizes: [
|
imageSizes: [
|
||||||
{
|
{
|
||||||
|
@ -62,5 +60,12 @@ export const WeaponsThumbnails = buildImageCollectionConfig({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
fields: [],
|
fields: [
|
||||||
|
backPropagationField({
|
||||||
|
name: fields.weapon,
|
||||||
|
hasMany: false,
|
||||||
|
relationTo: Collections.Weapons,
|
||||||
|
where: ({ id }) => ({ thumbnail: { equals: id } }),
|
||||||
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
.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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
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;
|
|
@ -0,0 +1,185 @@
|
||||||
|
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 ListControls from "payload/dist/admin/components/elements/ListControls";
|
||||||
|
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 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 } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Grid from "./Grid";
|
||||||
|
|
||||||
|
const baseClass = "collection-list";
|
||||||
|
|
||||||
|
export const UploadsGridView: React.ComponentType<Props> = (props) => {
|
||||||
|
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 {
|
||||||
|
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}
|
||||||
|
resetParams={resetParams}
|
||||||
|
/>
|
||||||
|
{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>
|
||||||
|
<Grid data={formattedDocs} collection={collection} />
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
};
|
|
@ -79,6 +79,7 @@ export enum LibraryItemsTextualPageOrders {
|
||||||
export enum RecordersRoles {
|
export enum RecordersRoles {
|
||||||
Admin = "Admin",
|
Admin = "Admin",
|
||||||
Recorder = "Recorder",
|
Recorder = "Recorder",
|
||||||
|
Api = "Api",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum CollectionStatus {
|
export enum CollectionStatus {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { CollectionEndpoint } from "../types/payload";
|
||||||
export const createGetByEndpoint = <T, R>(
|
export const createGetByEndpoint = <T, R>(
|
||||||
collection: string,
|
collection: string,
|
||||||
attribute: string,
|
attribute: string,
|
||||||
handler: (doc: T) => Promise<R>
|
handler: (doc: T) => Promise<R> | R = (doc) => doc as unknown as R
|
||||||
): CollectionEndpoint => ({
|
): CollectionEndpoint => ({
|
||||||
path: `/${attribute}/:${attribute}`,
|
path: `/${attribute}/:${attribute}`,
|
||||||
method: "get",
|
method: "get",
|
|
@ -0,0 +1,39 @@
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,66 @@
|
||||||
|
import payload from "payload";
|
||||||
|
import { mustBeAdmin } from "../accesses/endpoints/mustBeAdmin";
|
||||||
|
import { Collections } from "../constants";
|
||||||
|
import { CollectionEndpoint } from "../types/payload";
|
||||||
|
import { isDefined } from "../utils/asserts";
|
||||||
|
|
||||||
|
type Image = {
|
||||||
|
filename: string;
|
||||||
|
id: string | number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createImageRegenerationEndpoint = (collection: Collections): CollectionEndpoint => ({
|
||||||
|
method: "get",
|
||||||
|
path: "/regenerate",
|
||||||
|
handler: async (req, res) => {
|
||||||
|
if (!mustBeAdmin(req)) {
|
||||||
|
return res.status(403).send({
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
message: "You are not allowed to perform this action.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let page = 1;
|
||||||
|
let totalPage = 1;
|
||||||
|
let count = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
while (page <= totalPage) {
|
||||||
|
const images = await payload.find({
|
||||||
|
collection,
|
||||||
|
page,
|
||||||
|
user: req.user,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
images.docs.map(async (image: Image) => {
|
||||||
|
try {
|
||||||
|
await payload.update({
|
||||||
|
collection,
|
||||||
|
id: image.id,
|
||||||
|
data: {},
|
||||||
|
filePath: `uploads/${collection}/${image.filename}`,
|
||||||
|
overwriteExistingFiles: true,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
if (typeof e === "object" && isDefined(e) && "name" in e) {
|
||||||
|
errors.push(`${e.name} with ${image.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
totalPage = images.totalPages;
|
||||||
|
count += images.docs.length;
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
.status(200)
|
||||||
|
.json({ message: `${count} entries have been regenerated successfully.`, errors });
|
||||||
|
},
|
||||||
|
});
|
|
@ -40,7 +40,7 @@ export const backPropagationField = ({
|
||||||
if (hasMany) {
|
if (hasMany) {
|
||||||
return result.docs.map((doc) => doc.id);
|
return result.docs.map((doc) => doc.id);
|
||||||
} else {
|
} else {
|
||||||
return result.docs[0].id;
|
return result.docs[0]?.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hasMany ? [] : undefined;
|
return hasMany ? [] : undefined;
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
import { RelationshipField, UploadField } from "payload/types";
|
import { FieldBase, RelationshipField } from "payload/dist/fields/config/types";
|
||||||
import { Collections } from "../../constants";
|
import { Collections, FileTypes } from "../../constants";
|
||||||
|
|
||||||
type Props = Omit<UploadField, "type" | "relationTo">;
|
type FileField = FieldBase & {
|
||||||
|
relationTo: FileTypes;
|
||||||
|
hasMany?: boolean;
|
||||||
|
admin?: RelationshipField["admin"];
|
||||||
|
};
|
||||||
|
|
||||||
export const fileField = (props: Props): RelationshipField => ({
|
export const fileField = ({
|
||||||
|
relationTo,
|
||||||
|
hasMany = false,
|
||||||
|
...props
|
||||||
|
}: FileField): RelationshipField => ({
|
||||||
...props,
|
...props,
|
||||||
type: "relationship",
|
type: "relationship",
|
||||||
|
hasMany: hasMany,
|
||||||
relationTo: Collections.Files,
|
relationTo: Collections.Files,
|
||||||
|
filterOptions: { type: { equals: getFileTypesKey(relationTo) } },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getFileTypesKey = (fileType: FileTypes): string | undefined =>
|
||||||
|
Object.entries(FileTypes).find(([, value]) => value === fileType)?.[0];
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Collections, KeysTypes } from "../../constants";
|
||||||
type KeysField = FieldBase & {
|
type KeysField = FieldBase & {
|
||||||
relationTo: KeysTypes;
|
relationTo: KeysTypes;
|
||||||
hasMany?: boolean;
|
hasMany?: boolean;
|
||||||
admin: RelationshipField["admin"];
|
admin?: RelationshipField["admin"];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const keysField = ({
|
export const keysField = ({
|
||||||
|
|
|
@ -120,6 +120,11 @@ export interface LibraryItem {
|
||||||
};
|
};
|
||||||
audio?: {
|
audio?: {
|
||||||
audioSubtype?: string[] | Key[];
|
audioSubtype?: string[] | Key[];
|
||||||
|
tracks?: {
|
||||||
|
title: string;
|
||||||
|
file: string | File;
|
||||||
|
id?: string;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
releaseDate?: string;
|
releaseDate?: string;
|
||||||
categories?: string[] | Key[];
|
categories?: string[] | Key[];
|
||||||
|
@ -159,6 +164,7 @@ export interface LibraryItem {
|
||||||
}
|
}
|
||||||
export interface LibraryItemThumbnail {
|
export interface LibraryItemThumbnail {
|
||||||
id: string;
|
id: string;
|
||||||
|
libraryItem?: string[] | LibraryItem[];
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
@ -304,6 +310,13 @@ export interface Language {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
export interface File {
|
||||||
|
id: string;
|
||||||
|
filename: string;
|
||||||
|
type: "LibraryScans" | "LibrarySoundtracks" | "ContentVideo" | "ContentAudio";
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
export interface Currency {
|
export interface Currency {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
@ -337,6 +350,7 @@ export interface Content {
|
||||||
}
|
}
|
||||||
export interface ContentsThumbnail {
|
export interface ContentsThumbnail {
|
||||||
id: string;
|
id: string;
|
||||||
|
contents?: string[] | Content[];
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
@ -370,6 +384,14 @@ export interface ContentsThumbnail {
|
||||||
filesize?: number;
|
filesize?: number;
|
||||||
filename?: string;
|
filename?: string;
|
||||||
};
|
};
|
||||||
|
max?: {
|
||||||
|
url?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
mimeType?: string;
|
||||||
|
filesize?: number;
|
||||||
|
filename?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export interface Recorder {
|
export interface Recorder {
|
||||||
|
@ -378,7 +400,7 @@ export interface Recorder {
|
||||||
avatar?: string | RecordersThumbnail;
|
avatar?: string | RecordersThumbnail;
|
||||||
languages?: string[] | Language[];
|
languages?: string[] | Language[];
|
||||||
biographies?: RecorderBiographies;
|
biographies?: RecorderBiographies;
|
||||||
role?: ("Admin" | "Recorder")[];
|
role?: ("Admin" | "Recorder" | "Api")[];
|
||||||
anonymize: boolean;
|
anonymize: boolean;
|
||||||
email: string;
|
email: string;
|
||||||
resetPasswordToken?: string;
|
resetPasswordToken?: string;
|
||||||
|
@ -409,15 +431,7 @@ export interface RecordersThumbnail {
|
||||||
filesize?: number;
|
filesize?: number;
|
||||||
filename?: string;
|
filename?: string;
|
||||||
};
|
};
|
||||||
og?: {
|
square?: {
|
||||||
url?: string;
|
|
||||||
width?: number;
|
|
||||||
height?: number;
|
|
||||||
mimeType?: string;
|
|
||||||
filesize?: number;
|
|
||||||
filename?: string;
|
|
||||||
};
|
|
||||||
small?: {
|
|
||||||
url?: string;
|
url?: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
|
@ -614,13 +628,6 @@ export interface Tabs_Tab_Section_Section_Section_Section {
|
||||||
blockName?: string;
|
blockName?: string;
|
||||||
blockType: "section";
|
blockType: "section";
|
||||||
}
|
}
|
||||||
export interface File {
|
|
||||||
id: string;
|
|
||||||
filename: string;
|
|
||||||
type: "LibraryScans" | "LibrarySoundtracks" | "ContentVideo" | "ContentAudio";
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
export interface ContentsFolder {
|
export interface ContentsFolder {
|
||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
|
@ -671,6 +678,7 @@ export interface Post {
|
||||||
}
|
}
|
||||||
export interface PostThumbnail {
|
export interface PostThumbnail {
|
||||||
id: string;
|
id: string;
|
||||||
|
posts?: string[] | Post[];
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
@ -788,6 +796,7 @@ export interface Weapon {
|
||||||
}
|
}
|
||||||
export interface WeaponsThumbnail {
|
export interface WeaponsThumbnail {
|
||||||
id: string;
|
id: string;
|
||||||
|
weapon?: string | Weapon;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
@ -859,7 +868,7 @@ export interface Video {
|
||||||
likes?: number;
|
likes?: number;
|
||||||
views?: number;
|
views?: number;
|
||||||
publishedDate: string;
|
publishedDate: string;
|
||||||
channel: string | VideosChannel;
|
channel?: string | VideosChannel;
|
||||||
}
|
}
|
||||||
export interface VideosChannel {
|
export interface VideosChannel {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { CollectionConfig } from "payload/types";
|
import { CollectionConfig, PayloadRequest } from "payload/types";
|
||||||
|
|
||||||
export type PayloadCreateData<T> = Omit<
|
export type PayloadCreateData<T> = Omit<
|
||||||
T,
|
T,
|
||||||
|
@ -6,3 +6,5 @@ export type PayloadCreateData<T> = Omit<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type CollectionEndpoint = NonNullable<CollectionConfig["endpoints"]>[number];
|
export type CollectionEndpoint = NonNullable<CollectionConfig["endpoints"]>[number];
|
||||||
|
|
||||||
|
export type EndpointAccess<U> = (req: PayloadRequest<U>) => boolean;
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import { ImageSize } from "payload/dist/uploads/types";
|
import { ImageSize } from "payload/dist/uploads/types";
|
||||||
import { CollectionConfig } from "payload/types";
|
import { CollectionConfig } from "payload/types";
|
||||||
|
import { UploadsGridView } from "../components/UploadsGridView/UploadsGridView";
|
||||||
|
import { CollectionGroups } from "../constants";
|
||||||
|
import { createImageRegenerationEndpoint } from "../endpoints/createImageRegenerationEndpoint";
|
||||||
import { BuildCollectionConfig, buildCollectionConfig } from "./collectionConfig";
|
import { BuildCollectionConfig, buildCollectionConfig } from "./collectionConfig";
|
||||||
|
|
||||||
type BuildImageCollectionConfig = Omit<BuildCollectionConfig, "upload"> & {
|
type BuildImageCollectionConfig = Omit<BuildCollectionConfig, "upload"> & {
|
||||||
|
@ -7,11 +10,21 @@ type BuildImageCollectionConfig = Omit<BuildCollectionConfig, "upload"> & {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildImageCollectionConfig = ({
|
export const buildImageCollectionConfig = ({
|
||||||
|
admin,
|
||||||
upload: { imageSizes },
|
upload: { imageSizes },
|
||||||
...otherConfig
|
...otherConfig
|
||||||
}: BuildImageCollectionConfig): CollectionConfig =>
|
}: BuildImageCollectionConfig): CollectionConfig =>
|
||||||
buildCollectionConfig({
|
buildCollectionConfig({
|
||||||
...otherConfig,
|
...otherConfig,
|
||||||
|
defaultSort: "-updatedAt",
|
||||||
|
admin: {
|
||||||
|
disableDuplicate: true,
|
||||||
|
useAsTitle: "filename",
|
||||||
|
group: CollectionGroups.Media,
|
||||||
|
components: { views: { List: UploadsGridView } },
|
||||||
|
...admin,
|
||||||
|
},
|
||||||
|
endpoints: [createImageRegenerationEndpoint(otherConfig.slug)],
|
||||||
upload: {
|
upload: {
|
||||||
staticDir: `../uploads/${otherConfig.slug}`,
|
staticDir: `../uploads/${otherConfig.slug}`,
|
||||||
mimeTypes: ["image/*"],
|
mimeTypes: ["image/*"],
|
||||||
|
|
|
@ -97,8 +97,8 @@
|
||||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
"alwaysStrict": true /* Ensure 'use strict' is always emitted. */,
|
"alwaysStrict": true /* Ensure 'use strict' is always emitted. */,
|
||||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
"noUnusedLocals": true /* Enable error reporting when local variables aren't read. */,
|
||||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
"noUnusedParameters": true /* Raise an error when a function parameter isn't read. */,
|
||||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
"noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */,
|
"noFallthroughCasesInSwitch": true /* Enable error reporting for fallthrough cases in switch statements. */,
|
||||||
|
|
Loading…
Reference in New Issue