feat: allows selection of revisions in certain locales to compare
This commit is contained in:
@@ -10,6 +10,7 @@ div.react-select {
|
||||
|
||||
.rs__value-container {
|
||||
padding: base(.25) 0;
|
||||
min-height: base(1.5);
|
||||
|
||||
> * {
|
||||
margin-top: 0;
|
||||
@@ -20,6 +21,8 @@ div.react-select {
|
||||
|
||||
&--is-multi {
|
||||
margin-left: - base(.25);
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,4 +8,8 @@
|
||||
background-color: $color-red;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-bottom: base(.25);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import qs from 'qs';
|
||||
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 { publishedVersionOption } from '../shared';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -11,8 +13,12 @@ const baseClass = 'compare-revision';
|
||||
|
||||
const maxResultsPerRequest = 10;
|
||||
|
||||
const baseOptions = [
|
||||
publishedVersionOption,
|
||||
];
|
||||
|
||||
const CompareRevision: React.FC<Props> = (props) => {
|
||||
const { onChange, value, baseURL } = props;
|
||||
const { onChange, value, baseURL, parentID } = props;
|
||||
|
||||
const {
|
||||
admin: {
|
||||
@@ -20,14 +26,30 @@ const CompareRevision: React.FC<Props> = (props) => {
|
||||
},
|
||||
} = useConfig();
|
||||
|
||||
const [options, setOptions] = useState([]);
|
||||
const [options, setOptions] = useState(baseOptions);
|
||||
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`);
|
||||
const query = {
|
||||
limit: maxResultsPerRequest,
|
||||
page: lastLoadedPageArg,
|
||||
depth: 0,
|
||||
where: undefined,
|
||||
};
|
||||
|
||||
if (parentID) {
|
||||
query.where = {
|
||||
parent: {
|
||||
equals: parentID,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const search = qs.stringify(query);
|
||||
const response = await fetch(`${baseURL}?${search}`);
|
||||
|
||||
if (response.ok) {
|
||||
const data: PaginatedDocs<any> = await response.json();
|
||||
@@ -44,7 +66,7 @@ const CompareRevision: React.FC<Props> = (props) => {
|
||||
} else {
|
||||
setErrorLoading('An error has occurred.');
|
||||
}
|
||||
}, [dateFormat, baseURL]);
|
||||
}, [dateFormat, baseURL, parentID]);
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
@@ -58,6 +80,9 @@ const CompareRevision: React.FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className={`${baseClass}__label`}>
|
||||
Compare revision against:
|
||||
</div>
|
||||
{!errorLoading && (
|
||||
<ReactSelect
|
||||
isSearchable={false}
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
|
||||
import { PaginatedDocs } from '../../../../../mongoose/types';
|
||||
import { CompareOption } from '../types';
|
||||
|
||||
export type Props = {
|
||||
onChange: (val: unknown) => void,
|
||||
value: Option,
|
||||
value: CompareOption,
|
||||
baseURL: string
|
||||
parentID?: string
|
||||
}
|
||||
|
||||
export type Option = {
|
||||
label: string
|
||||
value: string
|
||||
relationTo?: string
|
||||
options?: Option[]
|
||||
}
|
||||
|
||||
type CLEAR = {
|
||||
type: 'CLEAR'
|
||||
|
||||
20
src/admin/components/views/Revision/Restore/index.scss
Normal file
20
src/admin/components/views/Revision/Restore/index.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
|
||||
.restore-revision {
|
||||
cursor: pointer;
|
||||
|
||||
&__modal {
|
||||
@include blur-bg;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
|
||||
&__toggle {
|
||||
@extend %btn-reset;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-right: $baseline;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/admin/components/views/Revision/Restore/index.tsx
Normal file
54
src/admin/components/views/Revision/Restore/index.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import React, { Fragment, useCallback, useState } from 'react';
|
||||
import { Modal, useModal } from '@faceless-ui/modal';
|
||||
import { Button, MinimalTemplate, Pill } from '../../..';
|
||||
import { Props } from './types';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'restore-revision';
|
||||
const modalSlug = 'restore-revision';
|
||||
|
||||
const Restore: React.FC<Props> = ({ collection, global, className }) => {
|
||||
const { toggle } = useModal();
|
||||
const [processing, setProcessing] = useState(false);
|
||||
|
||||
const handleRestore = useCallback(() => {
|
||||
console.log(collection, global);
|
||||
}, [collection, global]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Pill
|
||||
onClick={() => toggle(modalSlug)}
|
||||
className={[baseClass, className].filter(Boolean).join(' ')}
|
||||
>
|
||||
Restore this revision
|
||||
</Pill>
|
||||
<Modal
|
||||
slug={modalSlug}
|
||||
className={`${baseClass}__modal`}
|
||||
>
|
||||
<MinimalTemplate>
|
||||
<h1>Confirm revision restoration</h1>
|
||||
<p>
|
||||
You are about to restore this document. Are you sure?
|
||||
</p>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
type="button"
|
||||
onClick={processing ? undefined : () => toggle(modalSlug)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={processing ? undefined : handleRestore}
|
||||
>
|
||||
{processing ? 'Restoring...' : 'Confirm'}
|
||||
</Button>
|
||||
</MinimalTemplate>
|
||||
</Modal>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default Restore;
|
||||
8
src/admin/components/views/Revision/Restore/types.ts
Normal file
8
src/admin/components/views/Revision/Restore/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
|
||||
import { SanitizedGlobalConfig } from '../../../../../globals/config/types';
|
||||
|
||||
export type Props = {
|
||||
collection?: SanitizedCollectionConfig
|
||||
global?: SanitizedGlobalConfig
|
||||
className?: string
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
|
||||
.select-revision-locales {
|
||||
flex-grow: 1;
|
||||
|
||||
&__label {
|
||||
margin-bottom: base(.25);
|
||||
}
|
||||
}
|
||||
24
src/admin/components/views/Revision/SelectLocales/index.tsx
Normal file
24
src/admin/components/views/Revision/SelectLocales/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import ReactSelect from '../../../elements/ReactSelect';
|
||||
import { Props } from './types';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'select-revision-locales';
|
||||
|
||||
const SelectLocales: React.FC<Props> = ({ onChange, value, options }) => (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__label`}>
|
||||
Show locales:
|
||||
</div>
|
||||
<ReactSelect
|
||||
isMulti
|
||||
placeholder="Select locales to display"
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
options={options}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default SelectLocales;
|
||||
@@ -0,0 +1,7 @@
|
||||
import { LocaleOption } from '../types';
|
||||
|
||||
export type Props = {
|
||||
onChange: (options: LocaleOption[]) => void
|
||||
value: LocaleOption[]
|
||||
options: LocaleOption[]
|
||||
}
|
||||
@@ -12,10 +12,30 @@
|
||||
|
||||
&__header {
|
||||
margin-bottom: $baseline;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__intro {
|
||||
margin-bottom: base(.5);
|
||||
&__controls {
|
||||
display: flex;
|
||||
margin-bottom: $baseline;
|
||||
margin-left: base(-.5);
|
||||
margin-right: base(-.5);
|
||||
|
||||
> * {
|
||||
margin-left: base(.5);
|
||||
margin-right: base(.5);
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&__restore {
|
||||
margin: 0 0 0 $baseline;
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
@@ -24,9 +44,20 @@
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&__intro,
|
||||
&__header {
|
||||
padding-left: $baseline;
|
||||
padding-right: $baseline;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__controls {
|
||||
display: block;
|
||||
margin: 0 $baseline;
|
||||
}
|
||||
|
||||
&__restore {
|
||||
margin: base(.5) 0 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,25 +8,30 @@ import Loading from '../../elements/Loading';
|
||||
import { useStepNav } from '../../elements/StepNav';
|
||||
import { StepNavItem } from '../../elements/StepNav/types';
|
||||
import Meta from '../../utilities/Meta';
|
||||
import { Props } from './types';
|
||||
import { LocaleOption, CompareOption, Props } from './types';
|
||||
import CompareRevision from './Compare';
|
||||
import { Option } from './Compare/types';
|
||||
import { publishedVersionOption } from './shared';
|
||||
import Restore from './Restore';
|
||||
|
||||
import './index.scss';
|
||||
import SelectLocales from './SelectLocales';
|
||||
|
||||
const baseClass = 'view-revision';
|
||||
|
||||
const ViewRevision: React.FC<Props> = ({ collection, global }) => {
|
||||
const { serverURL, routes: { admin, api }, admin: { dateFormat } } = useConfig();
|
||||
const { serverURL, routes: { admin, api }, admin: { dateFormat }, localization } = useConfig();
|
||||
const { setStepNav } = useStepNav();
|
||||
const { params: { id, revisionID } } = useRouteMatch<{ id?: string, revisionID: string }>();
|
||||
const [compareValue, setCompareValue] = useState<Option>();
|
||||
const [compareValue, setCompareValue] = useState<CompareOption>(publishedVersionOption);
|
||||
const [localeOptions] = useState<LocaleOption[]>(() => (localization?.locales ? localization.locales.map((locale) => ({ label: locale, value: locale })) : []));
|
||||
const [locales, setLocales] = useState<LocaleOption[]>(localeOptions);
|
||||
|
||||
let originalDocFetchURL: string;
|
||||
let revisionFetchURL: string;
|
||||
let entityLabel: string;
|
||||
let compareBaseURL: string;
|
||||
let slug: string;
|
||||
let parentID: string;
|
||||
|
||||
if (collection) {
|
||||
({ slug } = collection);
|
||||
@@ -34,6 +39,7 @@ const ViewRevision: React.FC<Props> = ({ collection, global }) => {
|
||||
revisionFetchURL = `${serverURL}${api}/${slug}/revisions/${revisionID}`;
|
||||
compareBaseURL = `${serverURL}${api}/${slug}/revisions`;
|
||||
entityLabel = collection.labels.singular;
|
||||
parentID = id;
|
||||
}
|
||||
|
||||
if (global) {
|
||||
@@ -45,7 +51,7 @@ const ViewRevision: React.FC<Props> = ({ collection, global }) => {
|
||||
}
|
||||
|
||||
const useAsTitle = collection?.admin?.useAsTitle || 'id';
|
||||
const compareFetchURL = compareValue?.value ? `${compareBaseURL}/${compareValue.value}/${compareValue?.value}` : '';
|
||||
const compareFetchURL = compareValue?.value && compareValue?.value !== 'published' ? `${compareBaseURL}/${compareValue.value}` : '';
|
||||
|
||||
const [{ data: doc, isLoading }] = usePayloadAPI(revisionFetchURL);
|
||||
const [{ data: originalDoc }] = usePayloadAPI(originalDocFetchURL);
|
||||
@@ -115,18 +121,29 @@ const ViewRevision: React.FC<Props> = ({ collection, global }) => {
|
||||
/>
|
||||
<Eyebrow />
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<div className={`${baseClass}__intro`}>Revision created on:</div>
|
||||
<header className={`${baseClass}__header`}>
|
||||
<div className={`${baseClass}__intro`}>Revision created on:</div>
|
||||
<h2>
|
||||
{formattedCreatedAt}
|
||||
</h2>
|
||||
<Restore
|
||||
className={`${baseClass}__restore`}
|
||||
collection={collection}
|
||||
global={global}
|
||||
/>
|
||||
</header>
|
||||
<div className={`${baseClass}__controls`}>
|
||||
<CompareRevision
|
||||
baseURL={compareBaseURL}
|
||||
parentID={parentID}
|
||||
value={compareValue}
|
||||
onChange={setCompareValue}
|
||||
/>
|
||||
<SelectLocales
|
||||
onChange={setLocales}
|
||||
options={localeOptions}
|
||||
value={locales}
|
||||
/>
|
||||
</div>
|
||||
{isLoading && (
|
||||
<Loading />
|
||||
|
||||
4
src/admin/components/views/Revision/shared.ts
Normal file
4
src/admin/components/views/Revision/shared.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const publishedVersionOption = {
|
||||
label: 'Most recently published',
|
||||
value: 'published',
|
||||
};
|
||||
@@ -1,6 +1,18 @@
|
||||
import { SanitizedCollectionConfig } from '../../../../collections/config/types';
|
||||
import { SanitizedGlobalConfig } from '../../../../globals/config/types';
|
||||
|
||||
export type LocaleOption = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export type CompareOption = {
|
||||
label: string
|
||||
value: string
|
||||
relationTo?: string
|
||||
options?: CompareOption[]
|
||||
}
|
||||
|
||||
export type Props = {
|
||||
collection?: SanitizedCollectionConfig
|
||||
global?: SanitizedGlobalConfig
|
||||
|
||||
Reference in New Issue
Block a user