UNPKG

@plone/volto

Version:
305 lines (289 loc) 10.6 kB
import React, { useEffect, useState } from 'react'; import { Button, Container, Segment, Table } from 'semantic-ui-react'; import Helmet from '@plone/volto/helpers/Helmet/Helmet'; import { flattenToAppURL, getBaseUrl } from '@plone/volto/helpers/Url/Url'; import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap'; import reduce from 'lodash/reduce'; import { Link, useLocation } from 'react-router-dom'; import Icon from '@plone/volto/components/theme/Icon/Icon'; import Toast from '@plone/volto/components/manage/Toast/Toast'; import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar'; import config from '@plone/volto/registry'; import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser'; import { deleteLinkTranslation, linkTranslation, } from '@plone/volto/actions/translations/translations'; import { getContent } from '@plone/volto/actions/content/content'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { useSelector, useDispatch } from 'react-redux'; import { createPortal } from 'react-dom'; import { toast } from 'react-toastify'; import addSVG from '@plone/volto/icons/add.svg'; import backSVG from '@plone/volto/icons/back.svg'; import linkSVG from '@plone/volto/icons/link.svg'; import unlinkSVG from '@plone/volto/icons/unlink.svg'; const messages = defineMessages({ success: { id: 'Success', defaultMessage: 'Success', }, linked: { id: 'Translation linked', defaultMessage: 'Translation linked', }, unlinked: { id: 'Translation linking removed', defaultMessage: 'Translation linking removed', }, link: { id: 'Link translation for', defaultMessage: 'Link translation for', }, unlink: { id: 'Unlink translation for', defaultMessage: 'Unlink translation for', }, ManageTranslations: { id: 'Manage Translations', defaultMessage: 'Manage Translations', }, ManageTranslationsTitle: { id: 'Manage translations for {title}', defaultMessage: 'Manage translations for {title}', }, back: { id: 'Back', defaultMessage: 'Back', }, }); const ManageTranslations = (props) => { const intl = useIntl(); const pathname = useLocation().pathname; const content = useSelector((state) => state.content.data); const dispatch = useDispatch(); const [isClient, setIsClient] = useState(false); useEffect(() => { setIsClient(true); }, []); const { isObjectBrowserOpen, openObjectBrowser } = props; const currentSelectedItem = React.useRef(null); React.useEffect(() => { if (!content) { dispatch(getContent(getBaseUrl(pathname))); } }, [dispatch, content, pathname]); React.useEffect(() => { // Only execute the link API call on the final item selected, once the ObjectBrowser // is closed if (!isObjectBrowserOpen && currentSelectedItem.current) { dispatch( linkTranslation( flattenToAppURL(content['@id']), currentSelectedItem.current, ), ) .then((resp) => { toast.success( <Toast success title={intl.formatMessage(messages.success)} content={intl.formatMessage(messages.linked)} />, ); dispatch(getContent(getBaseUrl(pathname))); }) .catch((error) => { // TODO: The true error sent by the API is shadowed by the superagent one // Update this when this issue is fixed. const shadowedError = JSON.parse(error.response.text); toast.error( <Toast error title={shadowedError.error.type} content={shadowedError.error.message} />, { toastId: 'linkFailed' }, ); }); } /* eslint-disable react-hooks/exhaustive-deps */ }, [isObjectBrowserOpen]); const translations = content && content['@components'].translations.items && { [content.language.token]: { url: content['@id'], }, ...reduce( content['@components'].translations.items, (acc, value) => { return { ...acc, [value.language]: { url: value['@id'] } }; }, {}, ), }; function onSelectTarget(target) { // We store the selection temporarily on the component, because we don't want it to // execute it right away, since that will lead into duplicate link requests and we // only want the last to get through currentSelectedItem.current = target; } function onDeleteTranslation(lang) { dispatch(deleteLinkTranslation(flattenToAppURL(content['@id']), lang)) .then((resp) => { toast.success( <Toast success title={intl.formatMessage(messages.success)} content={intl.formatMessage(messages.unlinked)} />, ); dispatch(getContent(getBaseUrl(pathname))); }) .catch((error) => { // TODO: The true error sent by the API is shadowed by the superagent one // Update this when this issue is fixed. const shadowedError = JSON.parse(error.response.text); toast.error( <Toast error title={shadowedError.error.type} content={shadowedError.error.message} />, { toastId: 'linkFailed' }, ); }); } return ( <Container id="page-manage-translations"> <Helmet title={intl.formatMessage(messages.ManageTranslations)} /> <Segment.Group raised> <Segment className="primary"> <FormattedMessage id="Manage translations for {title}" defaultMessage="Manage translations for {title}" values={{ title: <q>{content.title}</q> }} /> </Segment> {content && ( <Table selectable compact singleLine attached> <Table.Header> <Table.Row> <Table.HeaderCell>Language</Table.HeaderCell> <Table.HeaderCell>Path</Table.HeaderCell> <Table.HeaderCell textAlign="right">Tools</Table.HeaderCell> </Table.Row> </Table.Header> <Table.Body> {config.settings.supportedLanguages.map((lang) => ( <Table.Row key={lang}> <Table.Cell collapsing> {lang === content.language.token ? ( <strong>{langmap[lang].nativeName}</strong> ) : ( langmap[lang].nativeName )} </Table.Cell> <Table.Cell> <Link to={flattenToAppURL(translations[lang]?.url || '')}> {flattenToAppURL(translations[lang]?.url || '')} </Link> </Table.Cell> <Table.Cell textAlign="right" className="manage-multilingual-tools" > <Button.Group> <Button basic icon disabled={ lang === content.language.token || translations?.[lang] } as={Link} to={{ pathname: `${pathname}/create-translation`, state: { type: content['@type'], translationOf: flattenToAppURL(content['@id']), language: lang, }, }} > <Icon name={addSVG} size="24px" /> </Button> </Button.Group> {translations?.[lang] ? ( <Button.Group> <Button aria-label={`${intl.formatMessage( messages.unlink, )} ${langmap[lang].nativeName.toLowerCase()}`} basic icon disabled={lang === content.language.token} onClick={() => onDeleteTranslation(lang)} > <Icon name={ lang === content.language.token ? linkSVG : unlinkSVG } size="24px" /> </Button> </Button.Group> ) : ( <Button.Group> <Button aria-label={`${intl.formatMessage( messages.link, )} ${langmap[lang].nativeName.toLowerCase()}`} basic icon disabled={lang === content.language.token} onClick={() => openObjectBrowser({ mode: 'link', overlay: true, onSelectItem: (url) => { onSelectTarget(url, isObjectBrowserOpen); }, }) } > <Icon name={linkSVG} size="24px" /> </Button> </Button.Group> )} </Table.Cell> </Table.Row> ))} </Table.Body> </Table> )} {isClient && createPortal( <Toolbar pathname={pathname} hideDefaultViewButtons inner={ <Link to={`${getBaseUrl(pathname)}`} className="item"> <Icon name={backSVG} className="contents circled" size="30px" title={intl.formatMessage(messages.back)} /> </Link> } />, document.getElementById('toolbar'), )} </Segment.Group> </Container> ); }; export default withObjectBrowser(ManageTranslations);