feat: further revisions views
This commit is contained in:
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,4 +20,5 @@ export type Props = {
|
||||
onInputChange?: (val: string) => void
|
||||
onMenuScrollToBottom?: () => void
|
||||
placeholder?: string
|
||||
isSearchable?: boolean
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
11
src/admin/components/views/Revision/Compare/index.scss
Normal file
11
src/admin/components/views/Revision/Compare/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
82
src/admin/components/views/Revision/Compare/index.tsx
Normal file
82
src/admin/components/views/Revision/Compare/index.tsx
Normal 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;
|
||||
33
src/admin/components/views/Revision/Compare/types.ts
Normal file
33
src/admin/components/views/Revision/Compare/types.ts
Normal 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
|
||||
}
|
||||
@@ -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 />
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -138,7 +138,7 @@ const DefaultList: React.FC<Props> = (props) => {
|
||||
{data.totalDocs}
|
||||
</div>
|
||||
<PerPage
|
||||
collection={collection}
|
||||
limits={collection?.admin?.pagination?.limits}
|
||||
limit={limit}
|
||||
/>
|
||||
</Fragment>
|
||||
|
||||
Reference in New Issue
Block a user