feat: further revisions views

This commit is contained in:
James
2021-12-07 14:49:32 -05:00
parent 40f93e9d64
commit 740d6b15e5
19 changed files with 201 additions and 73 deletions

View File

@@ -3,10 +3,15 @@ import { Modal, useModal } from '@faceless-ui/modal';
import { Transforms } from 'slate';
import { useSlate, ReactEditor } from 'slate-react';
import MinimalTemplate from '../../../../../../../src/admin/components/templates/Minimal';
import { ElementButton } from '../../../../../../../components/rich-text';
import ElementButton from '../../../../../../../src/admin/components/forms/field-types/RichText/elements/Button';
import X from '../../../../../../../src/admin/components/icons/X';
import Button from '../../../../../../../src/admin/components/elements/Button';
import { Form, Text, Checkbox, Select, Submit, reduceFieldsToValues } from '../../../../../../../components/forms';
import Form from '../../../../../../../src/admin/components/forms/Form';
import Submit from '../../../../../../../src/admin/components/forms/Submit';
import reduceFieldsToValues from '../../../../../../../src/admin/components/forms/Form/reduceFieldsToValues';
import Text from '../../../../../../../src/admin/components/forms/field-types/Text';
import Checkbox from '../../../../../../../src/admin/components/forms/field-types/Checkbox';
import Select from '../../../../../../../src/admin/components/forms/field-types/Select';
import './index.scss';

View File

@@ -1,5 +1,4 @@
import React from 'react';
import PropTypes from 'prop-types';
import './index.scss';
@@ -27,18 +26,4 @@ const ButtonElement: React.FC = ({ attributes, children, element }) => {
);
};
ButtonElement.defaultProps = {
attributes: {},
children: null,
};
ButtonElement.propTypes = {
attributes: PropTypes.shape({}),
children: PropTypes.node,
element: PropTypes.shape({
style: PropTypes.oneOf(['primary', 'secondary']),
label: PropTypes.string,
}).isRequired,
};
export default ButtonElement;

View File

@@ -1,4 +1,4 @@
import { RichTextCustomElement } from '../../../../../../dist/fields/config/types';
import { RichTextCustomElement } from '../../../../../../src/fields/config/types';
import Button from './Button';
import Element from './Element';
import plugin from './plugin';

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { LeafButton } from '../../../../../../../components/rich-text';
import LeafButton from '../../../../../../../src/admin/components/forms/field-types/RichText/leaves/Button';
const Button = () => (
<LeafButton format="purple-background">

View File

@@ -1,5 +1,4 @@
import React from 'react';
import PropTypes from 'prop-types';
const PurpleBackground: React.FC<any> = ({ attributes, children }) => (
<span
@@ -10,14 +9,4 @@ const PurpleBackground: React.FC<any> = ({ attributes, children }) => (
</span>
);
PurpleBackground.defaultProps = {
attributes: {},
children: null,
};
PurpleBackground.propTypes = {
attributes: PropTypes.shape({}),
children: PropTypes.node,
};
export default PurpleBackground;

View File

@@ -6,7 +6,7 @@ import AllFields from './collections/AllFields';
import AutoLabel from './collections/AutoLabel';
import Code from './collections/Code';
import Conditions from './collections/Conditions';
import CustomComponents from './collections/CustomComponents';
// import CustomComponents from './collections/CustomComponents';
import File from './collections/File';
import Blocks from './collections/Blocks';
import CustomID from './collections/CustomID';
@@ -67,7 +67,7 @@ export default buildConfig({
AutoLabel,
Code,
Conditions,
CustomComponents,
// CustomComponents,
CustomID,
File,
DefaultValues,

View File

@@ -4,27 +4,22 @@ import { useHistory } from 'react-router-dom';
import { useSearchParams } from '../../utilities/SearchParams';
import Popup from '../Popup';
import Chevron from '../../icons/Chevron';
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
import { defaults } from '../../../../collections/config/defaults';
import './index.scss';
const baseClass = 'per-page';
const defaultLimits = defaults.admin.pagination.limits;
type Props = {
collection: SanitizedCollectionConfig
limits: number[]
limit: number
handleChange?: (limit: number) => void
modifySearchParams?: boolean
}
const PerPage: React.FC<Props> = ({ collection, limit, handleChange, modifySearchParams = true }) => {
const {
admin: {
pagination: {
limits,
},
},
} = collection;
const PerPage: React.FC<Props> = ({ limits = defaultLimits, limit, handleChange, modifySearchParams = true }) => {
const params = useSearchParams();
const history = useHistory();

View File

@@ -9,7 +9,7 @@ div.react-select {
}
.rs__value-container {
padding: 0;
padding: base(.25) 0;
> * {
margin-top: 0;
@@ -40,9 +40,6 @@ div.react-select {
}
.rs__input {
margin-top: base(.25);
margin-bottom: base(.25);
input {
font-family: $font-body;
width: 100% !important;

View File

@@ -14,6 +14,7 @@ const ReactSelect: React.FC<Props> = (props) => {
value,
disabled = false,
placeholder,
isSearchable = true,
} = props;
const classes = [
@@ -33,6 +34,7 @@ const ReactSelect: React.FC<Props> = (props) => {
className={classes}
classNamePrefix="rs"
options={options}
isSearchable={isSearchable}
/>
);
};

View File

@@ -20,4 +20,5 @@ export type Props = {
onInputChange?: (val: string) => void
onMenuScrollToBottom?: () => void
placeholder?: string
isSearchable?: boolean
}

View File

@@ -207,7 +207,7 @@ const UploadButton: React.FC<{path: string}> = ({ path }) => {
{data.totalDocs}
</div>
<PerPage
collection={modalCollection}
limits={modalCollection?.admin?.pagination?.limits}
limit={limit}
modifySearchParams={false}
handleChange={setLimit}

View File

@@ -233,7 +233,7 @@ const Element = ({ attributes, children, element, path }) => {
{data.totalDocs}
</div>
<PerPage
collection={modalCollection}
limits={modalCollection?.admin?.pagination?.limits}
limit={limit}
modifySearchParams={false}
handleChange={setLimit}

View File

@@ -136,7 +136,7 @@ const SelectExistingUploadModal: React.FC<Props> = (props) => {
{data.totalDocs}
</div>
<PerPage
collection={collection}
limits={collection?.admin?.pagination?.limits}
limit={limit}
modifySearchParams={false}
handleChange={setLimit}

View File

@@ -0,0 +1,11 @@
@import '../../../../scss/styles.scss';
.compare-revision {
&__error-loading {
border: 1px solid $color-red;
min-height: base(2);
padding: base(.5) base(.75);
background-color: $color-red;
color: white;
}
}

View File

@@ -0,0 +1,82 @@
import React, { useState, useCallback, useEffect } from 'react';
import { useConfig } from '@payloadcms/config-provider';
import format from 'date-fns/format';
import { Props } from './types';
import ReactSelect from '../../../elements/ReactSelect';
import { PaginatedDocs } from '../../../../../mongoose/types';
import './index.scss';
const baseClass = 'compare-revision';
const maxResultsPerRequest = 10;
const CompareRevision: React.FC<Props> = (props) => {
const { onChange, value, baseURL } = props;
const {
admin: {
dateFormat,
},
} = useConfig();
const [options, setOptions] = useState([]);
const [lastLoadedPage, setLastLoadedPage] = useState(1);
const [errorLoading, setErrorLoading] = useState('');
const getResults = useCallback(async ({
lastLoadedPage: lastLoadedPageArg,
} = {}) => {
const response = await fetch(`${baseURL}?limit=${maxResultsPerRequest}&page=${lastLoadedPageArg}&depth=0`);
if (response.ok) {
const data: PaginatedDocs<any> = await response.json();
if (data.docs.length > 0) {
setOptions((existingOptions) => [
...existingOptions,
...data.docs.map((doc) => ({
label: format(new Date(doc.createdAt), dateFormat),
value: doc.id,
})),
]);
setLastLoadedPage(data.page);
}
} else {
setErrorLoading('An error has occurred.');
}
}, [dateFormat, baseURL]);
const classes = [
'field-type',
baseClass,
errorLoading && 'error-loading',
].filter(Boolean).join(' ');
useEffect(() => {
getResults({ lastLoadedPage: 1 });
}, [getResults]);
return (
<div className={classes}>
{!errorLoading && (
<ReactSelect
isSearchable={false}
placeholder="Select a revision to compare"
onChange={onChange}
onMenuScrollToBottom={() => {
getResults({ lastLoadedPage: lastLoadedPage + 1 });
}}
value={value}
options={options}
/>
)}
{errorLoading && (
<div className={`${baseClass}__error-loading`}>
{errorLoading}
</div>
)}
</div>
);
};
export default CompareRevision;

View File

@@ -0,0 +1,33 @@
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
import { PaginatedDocs } from '../../../../../mongoose/types';
export type Props = {
onChange: (val: unknown) => void,
value: Option,
baseURL: string
}
export type Option = {
label: string
value: string
relationTo?: string
options?: Option[]
}
type CLEAR = {
type: 'CLEAR'
required: boolean
}
type ADD = {
type: 'ADD'
data: PaginatedDocs<any>
collection: SanitizedCollectionConfig
}
export type Action = CLEAR | ADD
export type ValueWithRelation = {
relationTo: string
value: string
}

View File

@@ -1,5 +1,5 @@
import { useConfig } from '@payloadcms/config-provider';
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { useRouteMatch } from 'react-router-dom';
import format from 'date-fns/format';
import usePayloadAPI from '../../../hooks/usePayloadAPI';
@@ -9,7 +9,8 @@ import { useStepNav } from '../../elements/StepNav';
import { StepNavItem } from '../../elements/StepNav/types';
import Meta from '../../utilities/Meta';
import { Props } from './types';
import IDLabel from '../../elements/IDLabel';
import CompareRevision from './Compare';
import { Option } from './Compare/types';
import './index.scss';
@@ -19,28 +20,36 @@ const ViewRevision: React.FC<Props> = ({ collection, global }) => {
const { serverURL, routes: { admin, api }, admin: { dateFormat } } = useConfig();
const { setStepNav } = useStepNav();
const { params: { id, revisionID } } = useRouteMatch<{ id?: string, revisionID: string }>();
const [compareValue, setCompareValue] = useState<Option>();
let originalDocFetchURL: string;
let docFetchURL: string;
let revisionFetchURL: string;
let entityLabel: string;
let compareBaseURL: string;
let slug: string;
if (collection) {
({ slug } = collection);
originalDocFetchURL = `${serverURL}${api}/${slug}/${id}`;
docFetchURL = `${serverURL}${api}/${slug}/revisions/${revisionID}`;
revisionFetchURL = `${serverURL}${api}/${slug}/revisions/${revisionID}`;
compareBaseURL = `${serverURL}${api}/${slug}/revisions`;
entityLabel = collection.labels.singular;
}
if (global) {
({ slug } = global);
docFetchURL = `${serverURL}${api}/globals/${slug}/revisions/${revisionID}`;
originalDocFetchURL = `${serverURL}${api}/globals/${slug}`;
revisionFetchURL = `${serverURL}${api}/globals/${slug}/revisions/${revisionID}`;
compareBaseURL = `${serverURL}${api}/globals/${slug}/revisions`;
entityLabel = global.label;
}
const useAsTitle = collection?.admin?.useAsTitle || 'id';
const [{ data: doc, isLoading }] = usePayloadAPI(docFetchURL);
const compareFetchURL = compareValue?.value ? `${compareBaseURL}/${compareValue.value}/${compareValue?.value}` : '';
const [{ data: doc, isLoading }] = usePayloadAPI(revisionFetchURL);
const [{ data: originalDoc }] = usePayloadAPI(originalDocFetchURL);
const [{ data: compareDoc }] = usePayloadAPI(compareFetchURL);
useEffect(() => {
let nav: StepNavItem[] = [];
@@ -86,14 +95,15 @@ const ViewRevision: React.FC<Props> = ({ collection, global }) => {
let metaTitle: string;
let metaDesc: string;
const formattedCreatedAt = doc?.createdAt ? format(new Date(doc.createdAt), dateFormat) : '';
if (collection) {
metaTitle = `Revision - ${doc?.createdAt ? format(new Date(doc.createdAt), dateFormat) : ''} - ${doc[useAsTitle]} - ${entityLabel}`;
metaTitle = `Revision - ${formattedCreatedAt} - ${doc[useAsTitle]} - ${entityLabel}`;
metaDesc = `Viewing revision for the ${entityLabel} ${doc[useAsTitle]}`;
}
if (global) {
metaTitle = `Revision - ${doc?.createdAt ? format(new Date(doc.createdAt), dateFormat) : ''} - ${entityLabel}`;
metaTitle = `Revision - ${formattedCreatedAt} - ${entityLabel}`;
metaDesc = `Viewing revision for the global ${entityLabel}`;
}
@@ -106,11 +116,18 @@ const ViewRevision: React.FC<Props> = ({ collection, global }) => {
<Eyebrow />
<div className={`${baseClass}__wrap`}>
<header className={`${baseClass}__header`}>
<IDLabel
id={doc?.id}
prefix="Revision"
/>
<div className={`${baseClass}__intro`}>Revision created on:</div>
<h2>
{formattedCreatedAt}
</h2>
</header>
<div className={`${baseClass}__controls`}>
<CompareRevision
baseURL={compareBaseURL}
value={compareValue}
onChange={setCompareValue}
/>
</div>
{isLoading && (
<Loading />
)}

View File

@@ -86,28 +86,38 @@ const Revisions: React.FC<Props> = ({ collection, global }) => {
depth: 1,
page: undefined,
sort: undefined,
where: {
parent: {
equals: id,
},
},
limit,
where: {},
};
if (page) params.page = page;
if (sort) params.sort = sort;
let fetchURLToSet: string;
if (collection) {
fetchURLToSet = `${serverURL}${api}/${collection.slug}/revisions`;
params.where = {
parent: {
equals: id,
},
};
}
if (global) {
fetchURLToSet = `${serverURL}${api}/globals/${global.slug}/revisions`;
}
// Performance enhancement
// Setting the Fetch URL this way
// prevents a double-fetch
setFetchURL(`${serverURL}${api}/${slug}/revisions`);
setFetchURL(fetchURLToSet);
setParams(params);
}, [setParams, page, sort, slug, limit, serverURL, api, id]);
const useIDLabel = doc[useAsTitle] === doc?.id;
}, [setParams, page, sort, limit, serverURL, api, id, global, collection]);
let useIDLabel = doc[useAsTitle] === doc?.id;
let heading: string;
let metaDesc: string;
let metaTitle: string;
@@ -122,6 +132,7 @@ const Revisions: React.FC<Props> = ({ collection, global }) => {
metaTitle = `Revisions - ${entityLabel}`;
metaDesc = `Viewing revisions for the global ${entityLabel}`;
heading = entityLabel;
useIDLabel = false;
}
return (
@@ -175,7 +186,7 @@ const Revisions: React.FC<Props> = ({ collection, global }) => {
{revisionsData.totalDocs}
</div>
<PerPage
collection={collection}
limits={collection?.admin?.pagination?.limits}
limit={limit ? Number(limit) : 10}
/>
</React.Fragment>

View File

@@ -138,7 +138,7 @@ const DefaultList: React.FC<Props> = (props) => {
{data.totalDocs}
</div>
<PerPage
collection={collection}
limits={collection?.admin?.pagination?.limits}
limit={limit}
/>
</Fragment>