@plone/volto
Version:
Volto
476 lines (460 loc) • 15.4 kB
JSX
import React, { useState, useEffect, useCallback } from 'react';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { Link } from 'react-router-dom';
import { createPortal } from 'react-dom';
import { useClient } from '@plone/volto/hooks/client/useClient';
import {
Accordion,
Button,
Container,
Divider,
Header,
Label,
Message,
Segment,
Dimmer,
Loader,
} from 'semantic-ui-react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import {
installAddon,
listAddons,
uninstallAddon,
upgradeAddon,
} from '@plone/volto/actions/addons/addons';
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
import Icon from '@plone/volto/components/theme/Icon/Icon';
import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
import Toast from '@plone/volto/components/manage/Toast/Toast';
import circleBottomSVG from '@plone/volto/icons/circle-bottom.svg';
import circleTopSVG from '@plone/volto/icons/circle-top.svg';
import backSVG from '@plone/volto/icons/back.svg';
import { toast } from 'react-toastify';
const messages = defineMessages({
activateAndDeactivate: {
id: 'Activate and deactivate',
defaultMessage: 'Activate and deactivate add-ons in the lists below.',
},
addAddons: {
id: 'Add Addons',
defaultMessage:
'To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see',
},
addonsSettings: {
id: 'Add-ons Settings',
defaultMessage: 'Add-ons Settings',
},
available: {
id: 'Available',
defaultMessage: 'Available',
},
availableVersion: {
id: 'Latest version',
defaultMessage: 'Latest version',
},
back: {
id: 'Back',
defaultMessage: 'Back',
},
installed: {
id: 'Installed',
defaultMessage: 'Installed',
},
installedVersion: {
id: 'Installed version',
defaultMessage: 'Installed version',
},
noUninstallProfile: {
id: 'No uninstall profile',
defaultMessage: 'This addon does not provide an uninstall profile.',
},
update: {
id: 'Update',
defaultMessage: 'Update',
},
updatesAvailable: {
id: 'Updates available',
defaultMessage: 'Updates available',
},
updateInstalledAddons: {
id: 'Update installed addons:',
defaultMessage: 'Update installed addons:',
},
uninstall: {
id: 'Uninstall',
defaultMessage: 'Uninstall',
},
addOns: {
id: 'Add-ons',
defaultMessage: 'Add-ons',
},
installingAnAddon: {
id: 'Installing a third party add-on',
defaultMessage: 'Installing a third party add-on',
},
success: {
id: 'Success',
defaultMessage: 'Success',
},
error: {
id: 'Error',
defaultMessage: 'Error',
},
addonUpgraded: {
id: 'Addon upgraded succesfuly',
defaultMessage: 'Addon upgraded succesfuly',
},
addonNotUpgraded: {
id: 'Addon could not be upgraded',
defaultMessage: 'Addon could not be upgraded',
},
addonInstalled: {
id: 'Addon installed succesfuly',
defaultMessage: 'Addon installed succesfuly',
},
addonNotInstalled: {
id: 'Addon could not be installed',
defaultMessage: 'Addon could not be installed',
},
addonUninstalled: {
id: 'Addon uninstalled succesfuly',
defaultMessage: 'Addon uninstalled succesfuly',
},
addonNotUninstalled: {
id: 'Addon could not be uninstalled',
defaultMessage: 'Addon could not be uninstalled',
},
});
const AddonsControlpanel = (props) => {
const { title } = props;
const intl = useIntl();
const dispatch = useDispatch();
const pathname = props.location.pathname;
const [activeIndex, setactiveIndex] = useState(-1);
const isClient = useClient();
const installedAddons = useSelector(
(state) => state.addons.installedAddons,
shallowEqual,
);
const availableAddons = useSelector(
(state) => state.addons.availableAddons,
shallowEqual,
);
const upgradableAddons = useSelector(
(state) => state.addons.upgradableAddons,
shallowEqual,
);
const loadingAddons = useSelector((state) => state.addons.loading);
useEffect(() => {
dispatch(listAddons());
}, [dispatch]);
const onInstall = useCallback(
(event, { value }) => {
event.preventDefault();
dispatch(installAddon(value))
.then(() => {
toast.success(
<Toast
success
title={intl.formatMessage(messages.success)}
content={intl.formatMessage(messages.addonInstalled, {
title: title,
})}
/>,
);
})
.catch(() => {
toast.error(
<Toast
error
title={intl.formatMessage(messages.error)}
content={intl.formatMessage(messages.addonNotInstalled)}
/>,
);
})
.finally(() => dispatch(listAddons()));
},
[dispatch, title, intl],
);
const onUninstall = useCallback(
(event, { value }) => {
event.preventDefault();
dispatch(uninstallAddon(value))
.then(() => {
toast.success(
<Toast
success
title={intl.formatMessage(messages.success)}
content={intl.formatMessage(messages.addonUninstalled)}
/>,
);
})
.catch(() => {
toast.error(
<Toast
error
title={intl.formatMessage(messages.error)}
content={intl.formatMessage(messages.addonNotUninstalled, {
title: title,
})}
/>,
);
})
.finally(() => dispatch(listAddons()));
},
[dispatch, intl, title],
);
const onUpgrade = useCallback(
(event, { value }) => {
event.preventDefault();
dispatch(upgradeAddon(value))
.then(() => {
toast.success(
<Toast
success
title={intl.formatMessage(messages.success)}
content={intl.formatMessage(messages.addonUpgraded)}
/>,
);
})
.catch(() => {
toast.error(
<Toast
error
title={intl.formatMessage(messages.error)}
content={intl.formatMessage(messages.addonNotUpgraded)}
/>,
);
})
.finally(() => dispatch(listAddons()));
},
[dispatch, intl],
);
const onAccordionClick = (event, item) => {
const newIndex = activeIndex === item.index ? -1 : item.index;
setactiveIndex(newIndex);
};
return (
<Container id="page-addons" className="controlpanel-addons">
<Helmet title={intl.formatMessage(messages.addOns)} />
<Segment.Group raised>
<Segment className="primary">
<FormattedMessage
id="Add-ons Settings"
defaultMessage="Add-ons Settings"
/>
</Segment>
{loadingAddons ? (
<Dimmer active>
<Loader />
</Dimmer>
) : (
<>
{upgradableAddons.length > 0 && (
<Message attached>
<Message.Header>
<FormattedMessage
id="Updates available"
defaultMessage="Updates available"
/>
</Message.Header>
<FormattedMessage
id="Update installed addons"
defaultMessage="Update installed addons"
/>
</Message>
)}
<Segment>
<Header as="h3">
<FormattedMessage
id="Activate and deactivate"
defaultMessage="Activate and deactivate add-ons in the lists below."
/>
</Header>
<FormattedMessage
id="Add Addons"
defaultMessage="To make new add-ons show up here, add them to your configuration, build, and restart the server process. For detailed instructions see"
/>
<a
href="https://6.docs.plone.org/install/"
target="_blank"
rel="noopener noreferrer"
>
{intl.formatMessage(messages.installingAnAddon)}
</a>
.
</Segment>
<Segment key="header-available" secondary>
<FormattedMessage id="Available" defaultMessage="Available" />:
<Label circular>{availableAddons.length}</Label>
</Segment>
<Segment key="body-available" attached>
<Accordion>
<Divider />
{availableAddons.map((item) => (
<div key={item.id}>
<Accordion.Title
active={activeIndex === item.id}
index={item.id}
onClick={onAccordionClick}
>
{item.title}
<Icon
name={
activeIndex === item.id
? circleTopSVG
: circleBottomSVG
}
size="23px"
className={`accordionToggle ${item.title}`}
/>
</Accordion.Title>
<Accordion.Content active={activeIndex === item.id}>
<div className="description">{item.description}</div>
{item.uninstall_profile_id === '' && (
<div>
<Message icon="warning" warning>
<FormattedMessage
id="No uninstall profile"
defaultMessage="This addon does not provide an uninstall profile."
/>
</Message>
</div>
)}
<Button.Group floated="right">
<Button
primary
onClick={onInstall}
value={item.id}
className="installAction"
>
<FormattedMessage
id="Install"
defaultMessage="Install"
className="button-label"
/>
</Button>
</Button.Group>
<div className="version">
<FormattedMessage
id="Latest version"
defaultMessage="Latest version"
/>
:
{item.version}
</div>
</Accordion.Content>
<Divider />
</div>
))}
</Accordion>
</Segment>
<Segment key="header-installed" secondary>
<FormattedMessage id="Installed" defaultMessage="Installed" />:
<Label circular>{installedAddons.length}</Label>
</Segment>
<Segment key="body-installed" attached>
<Accordion>
<Divider />
{installedAddons.map((item) => (
<div key={item.id}>
<Accordion.Title
active={activeIndex === item.id}
index={item.id}
onClick={onAccordionClick}
className={
item.upgrade_info.available ? 'updateAvailable' : ''
}
>
{item.title}
{item.upgrade_info.available && (
<Label>
<FormattedMessage
id="Update"
defaultMessage="Update"
/>
</Label>
)}
<Icon
name={
activeIndex === item.id
? circleTopSVG
: circleBottomSVG
}
size="24px"
className={`accordionToggle ${item.title}`}
color="#878f93"
/>
</Accordion.Title>
<Accordion.Content active={activeIndex === item.id}>
<div className="description">{item.description}</div>
<Button.Group floated="right">
{item.upgrade_info.available && (
<Button primary onClick={onUpgrade} value={item.id}>
<FormattedMessage
id="upgradeVersions"
defaultMessage="Update from version {origin} to {destination}"
values={{
origin: item.upgrade_info.installedVersion,
destination: item.upgrade_info.newVersion,
}}
/>
</Button>
)}
{item.uninstall_profile_id && (
<Button
negative
onClick={onUninstall}
value={item.id}
className="uninstallAction"
>
<FormattedMessage
id="Uninstall"
defaultMessage="Uninstall"
className="button-label"
/>
</Button>
)}
</Button.Group>
<div className="version">
<FormattedMessage
id="Installed version"
defaultMessage="Installed version"
/>
: {item.version}
</div>
</Accordion.Content>
<Divider />
</div>
))}
</Accordion>
</Segment>
</>
)}
</Segment.Group>
{isClient &&
createPortal(
<Toolbar
pathname={pathname}
hideDefaultViewButtons
inner={
<>
<Link to="/controlpanel" className="item">
<Icon
name={backSVG}
aria-label={intl.formatMessage(messages.back)}
className="contents circled"
size="30px"
title={intl.formatMessage(messages.back)}
/>
</Link>
</>
}
/>,
document.getElementById('toolbar'),
)}
</Container>
);
};
export default AddonsControlpanel;