@plone/volto
Version:
Volto
287 lines (272 loc) • 8.62 kB
JSX
/**
* Diff component.
* @module components/manage/Diff/Diff
*/
import React, { useEffect, useMemo } from 'react';
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
import { useSelector, useDispatch } from 'react-redux';
import filter from 'lodash/filter';
import isEqual from 'lodash/isEqual';
import map from 'lodash/map';
import { Container, Button, Dropdown, Grid, Table } from 'semantic-ui-react';
import { Link, useLocation, useHistory } from 'react-router-dom';
import { createPortal } from 'react-dom';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import qs from 'query-string';
import { getDiff } from '@plone/volto/actions/diff/diff';
import { getSchema } from '@plone/volto/actions/schema/schema';
import { getHistory } from '@plone/volto/actions/history/history';
import { getBaseUrl } from '@plone/volto/helpers/Url/Url';
import {
getBlocksFieldname,
getBlocksLayoutFieldname,
hasBlocksData,
} from '@plone/volto/helpers/Blocks/Blocks';
import FormattedDate from '@plone/volto/components/theme/FormattedDate/FormattedDate';
import Icon from '@plone/volto/components/theme/Icon/Icon';
import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
import Unauthorized from '@plone/volto/components/theme/Unauthorized/Unauthorized';
import DiffField from '@plone/volto/components/manage/Diff/DiffField';
import { useClient } from '@plone/volto/hooks/client/useClient';
import backSVG from '@plone/volto/icons/back.svg';
const messages = defineMessages({
diff: {
id: 'Diff',
defaultMessage: 'Diff',
},
back: {
id: 'Back',
defaultMessage: 'Back',
},
split: {
id: 'Split',
defaultMessage: 'Split',
},
unified: {
id: 'Unified',
defaultMessage: 'Unified',
},
});
/**
* Diff component.
* @function Diff
* @returns {JSX.Element}
*/
function Diff() {
const dispatch = useDispatch();
const location = useLocation();
const history = useHistory();
const isClient = useClient();
const intl = useIntl();
const data = useSelector((state) => state.diff.data);
const historyEntries = useSelector((state) => state.history.entries);
const schema = useSelector((state) => state.schema.schema);
const error = useSelector((state) => state.diff.error);
const title = useSelector((state) => state.content.data?.title);
const type = useSelector((state) => state.content.data?.['@type']);
const pathname = location.pathname;
const searchParams = qs.parse(location.search);
const one = searchParams.one;
const two = searchParams.two;
const view = searchParams.view || 'split';
useEffect(() => {
if (type) {
dispatch(getSchema(type));
}
if (pathname) {
dispatch(getHistory(getBaseUrl(pathname)));
}
if (pathname && one && two) {
dispatch(getDiff(getBaseUrl(pathname), one, two));
}
}, [pathname, one, two, type, dispatch]);
/**
* On select view handler
* @method onSelectView
* @param {object} event Event object
* @param {string} value Value
* @returns {undefined}
*/
const onSelectView = (event, { value }) => {
history.push(`${pathname}?one=${one}&two=${two}&view=${value}`);
};
/**
* On change one handler
* @method onChangeOne
* @param {object} event Event object
* @param {string} value Value
* @returns {undefined}
*/
const onChangeOne = (event, { value }) => {
history.push(`${pathname}?one=${value}&two=${two}&view=${view}`);
};
/**
* On change two handler
* @method onChangeTwo
* @param {object} event Event object
* @param {string} value Value
* @returns {undefined}
*/
const onChangeTwo = (event, { value }) => {
history.push(`${pathname}?one=${one}&two=${value}&view=${view}`);
};
const versions = useMemo(
() =>
map(
filter(historyEntries, (entry) => 'version' in entry),
(entry, index) => ({
text: (
<>
{index === 0 ? 'Current' : entry.version} (
<FormattedDate date={entry.time} long className="text" />,
{entry.actor.fullname})
</>
),
value: `${entry.version}`,
key: `${entry.version}`,
}),
),
[historyEntries],
);
return error?.status === 401 ? (
<Unauthorized />
) : (
<Container id="page-diff">
<Helmet title={intl.formatMessage(messages.diff)} />
<h1>
<FormattedMessage
id="Difference between revision {one} and {two} of {title}"
defaultMessage="Difference between revision {one} and {two} of {title}"
values={{
one,
two,
title,
}}
/>
</h1>
<Grid>
<Grid.Column width={9}>
<p className="description">
<FormattedMessage
id="You can view the difference of the revisions below."
defaultMessage="You can view the difference of the revisions below."
/>
</p>
</Grid.Column>
<Grid.Column width={3} textAlign="right">
<Button.Group>
{map(
[
{
id: 'split',
label: intl.formatMessage(messages.split),
},
{
id: 'unified',
label: intl.formatMessage(messages.unified),
},
],
(viewOption) => (
<Button
type="button"
key={viewOption.id}
value={viewOption.id}
active={view === viewOption.id}
onClick={onSelectView}
>
{viewOption.label}
</Button>
),
)}
</Button.Group>
</Grid.Column>
</Grid>
{historyEntries.length > 0 && (
<Table basic="very">
<Table.Header>
<Table.Row>
<Table.HeaderCell width={6}>
<FormattedMessage id="Base" defaultMessage="Base" />
<Dropdown
onChange={onChangeOne}
value={one}
selection
fluid
options={versions}
/>
</Table.HeaderCell>
<Table.HeaderCell width={6}>
<FormattedMessage id="Compare" defaultMessage="Compare" />
<Dropdown
onChange={onChangeTwo}
value={two}
selection
fluid
options={versions}
/>
</Table.HeaderCell>
</Table.Row>
</Table.Header>
</Table>
)}
{schema &&
data.length > 0 &&
map(schema.fieldsets, (fieldset) =>
map(
fieldset.fields,
(field) =>
!isEqual(data[0][field], data[1][field]) &&
field !== getBlocksFieldname(data[0]) &&
field !== getBlocksLayoutFieldname(data[0]) && (
<DiffField
key={field}
one={data[0][field]}
two={data[1][field]}
schema={schema.properties[field]}
view={view}
/>
),
),
)}
{schema &&
data.length > 0 &&
hasBlocksData(data[0]) &&
(!isEqual(
data[0][getBlocksFieldname(data[0])],
data[1][getBlocksFieldname(data[1])],
) ||
!isEqual(
data[0][getBlocksLayoutFieldname(data[0])],
data[1][getBlocksLayoutFieldname(data[1])],
)) && (
<DiffField
one={data[0][getBlocksFieldname(data[0])]}
two={data[1][getBlocksFieldname(data[1])]}
contentOne={data[0]}
contentTwo={data[1]}
schema={schema.properties[getBlocksFieldname(data[0])]}
view={view}
/>
)}
{isClient &&
createPortal(
<Toolbar
pathname={pathname}
hideDefaultViewButtons
inner={
<Link to={`${getBaseUrl(pathname)}/historyview`} className="item">
<Icon
name={backSVG}
className="contents circled"
size="30px"
title={intl.formatMessage(messages.back)}
/>
</Link>
}
/>,
document.getElementById('toolbar'),
)}
</Container>
);
}
export default Diff;