Turn grid view into a plugin
This commit is contained in:
parent
f454b22569
commit
3532dab712
|
@ -1,185 +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 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>
|
||||
);
|
||||
};
|
|
@ -25,6 +25,7 @@ import { WeaponsThumbnails } from "./collections/WeaponsThumbnails/WeaponsThumbn
|
|||
import { Icon } from "./components/Icon";
|
||||
import { Logo } from "./components/Logo";
|
||||
import { Collections } from "./constants";
|
||||
import { gridViewPlugin } from "./plugins/payload-grid-view";
|
||||
|
||||
export default buildConfig({
|
||||
serverURL: process.env.PAYLOAD_URI,
|
||||
|
@ -70,4 +71,5 @@ export default buildConfig({
|
|||
graphQL: {
|
||||
disable: true,
|
||||
},
|
||||
plugins: [gridViewPlugin],
|
||||
});
|
||||
|
|
|
@ -0,0 +1,228 @@
|
|||
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;
|
|
@ -0,0 +1,202 @@
|
|||
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 UploadsGridView =
|
||||
(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>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
import { Plugin } from "payload/config";
|
||||
import { CollectionAdminOptions } from "payload/dist/collections/config/types";
|
||||
import { CollectionConfig } from "payload/types";
|
||||
import { UploadsGridView, UploadsGridViewOptions } from "./components/UploadsGridView/UploadsGridView";
|
||||
|
||||
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 gridViewPlugin: 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 ? UploadsGridView(gridView) : List,
|
||||
});
|
|
@ -1,7 +1,13 @@
|
|||
import { CollectionConfig } from "payload/types";
|
||||
import { Collections } from "../constants";
|
||||
import { CollectionConfigWithGridView } from "../plugins/payload-grid-view";
|
||||
|
||||
export type BuildCollectionConfig = Omit<CollectionConfig, "slug" | "typescript" | "labels"> & {
|
||||
type CollectionConfigWithPlugins = CollectionConfig & CollectionConfigWithGridView;
|
||||
|
||||
export type BuildCollectionConfig = Omit<
|
||||
CollectionConfigWithPlugins,
|
||||
"slug" | "typescript" | "labels"
|
||||
> & {
|
||||
slug: Collections;
|
||||
labels: { singular: string; plural: string };
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { ImageSize } from "payload/dist/uploads/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";
|
||||
|
@ -21,7 +20,6 @@ export const buildImageCollectionConfig = ({
|
|||
disableDuplicate: true,
|
||||
useAsTitle: "filename",
|
||||
group: CollectionGroups.Media,
|
||||
components: { views: { List: UploadsGridView } },
|
||||
...admin,
|
||||
},
|
||||
endpoints: [createImageRegenerationEndpoint(otherConfig.slug)],
|
||||
|
|
Loading…
Reference in New Issue