UNPKG

@craftercms/studio-ui

Version:

Services, components, models & utils to build CrafterCMS authoring extensions.

352 lines (350 loc) 14.4 kB
/* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import React, { useEffect, useMemo, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import AddIcon from '@mui/icons-material/Add'; import SkeletonSitesGrid from '../SitesGrid/SitesGridSkeleton'; import CreateSiteDialog from '../CreateSiteDialog/CreateSiteDialog'; import ListViewIcon from '@mui/icons-material/ViewStreamRounded'; import GridViewIcon from '@mui/icons-material/GridOnRounded'; import Tooltip from '@mui/material/Tooltip'; import IconButton from '@mui/material/IconButton'; import { useDispatch } from 'react-redux'; import { setSiteCookie } from '../../utils/auth'; import { trash } from '../../services/sites'; import { batchActions } from '../../state/actions/misc'; import { showSystemNotification } from '../../state/actions/system'; import { fetchSites, popSite } from '../../state/actions/sites'; import { showErrorDialog } from '../../state/reducers/dialogs/error'; import { showEditSiteDialog } from '../../state/actions/dialogs'; import { ErrorBoundary } from '../ErrorBoundary/ErrorBoundary'; import { SuspenseWithEmptyState } from '../Suspencified/Suspencified'; import SitesGrid from '../SitesGrid/SitesGrid'; import PublishingStatusDialog from '../PublishingStatusDialog'; import GlobalAppToolbar from '../GlobalAppToolbar'; import Button from '@mui/material/Button'; import { getStoredGlobalMenuSiteViewPreference, setStoredGlobalMenuSiteViewPreference } from '../../utils/state'; import { hasGlobalPermissions } from '../../services/users'; import { foo } from '../../utils/object'; import { useEnv } from '../../hooks/useEnv'; import { useActiveUser } from '../../hooks/useActiveUser'; import { useLogicResource } from '../../hooks/useLogicResource'; import { useSpreadState } from '../../hooks/useSpreadState'; import { useSitesBranch } from '../../hooks/useSitesBranch'; import Paper from '@mui/material/Paper'; import { getSystemLink } from '../../utils/system'; import { useEnhancedDialogState } from '../../hooks/useEnhancedDialogState'; import { DuplicateSiteDialog } from '../DuplicateSiteDialog'; import Card from '@mui/material/Card'; import CardActionArea from '@mui/material/CardActionArea'; import CardHeader from '@mui/material/CardHeader'; import { Typography } from '@mui/material'; import Alert from '@mui/material/Alert'; import FormControlLabel from '@mui/material/FormControlLabel'; import Checkbox from '@mui/material/Checkbox'; import { ConfirmDialog } from '../ConfirmDialog'; import { previewSwitch } from '../../services/security'; const translations = defineMessages({ siteDeleted: { id: 'sitesGrid.siteDeleted', defaultMessage: 'Project deleted successfully' } }); const confirmDeleteInitialState = { site: null, open: false, checked: false }; export function SiteManagement() { const dispatch = useDispatch(); const { formatMessage } = useIntl(); const { authoringBase, useBaseDomain } = useEnv(); const [openCreateSiteDialog, setOpenCreateSiteDialog] = useState(false); const user = useActiveUser(); const [currentView, setCurrentView] = useState(getStoredGlobalMenuSiteViewPreference(user.username) ?? 'grid'); const { byId: sitesById, isFetching, active } = useSitesBranch(); const [selectedSiteStatus, setSelectedSiteStatus] = useState(null); const [permissionsLookup, setPermissionsLookup] = useState(foo); const duplicateSiteDialogState = useEnhancedDialogState(); const [duplicateSiteId, setDuplicateSiteId] = useState(null); const [isDuplicateDialogFromCreateDialog, setIsDuplicateDialogFromCreateDialog] = useState(false); const [disabledSitesLookup, setDisabledSitesLookup] = useSpreadState({}); const [confirmDeleteState, setConfirmDeleteState] = useSpreadState(confirmDeleteInitialState); useEffect(() => { const subscription = hasGlobalPermissions('create_site', 'edit_site', 'delete_site', 'duplicate_site').subscribe( setPermissionsLookup ); return () => subscription.unsubscribe(); }, []); const resource = useLogicResource( useMemo(() => ({ sitesById, isFetching, permissionsLookup }), [sitesById, isFetching, permissionsLookup]), { shouldResolve: (source) => Boolean(source.sitesById) && permissionsLookup !== foo && !isFetching, shouldReject: () => false, shouldRenew: (source, resource) => resource.complete, resultSelector: () => Object.values(sitesById), errorSelector: () => null } ); const onSiteClick = (site) => { setSiteCookie(site.id, useBaseDomain); previewSwitch().subscribe(() => { window.location.href = getSystemLink({ systemLinkId: 'preview', authoringBase, site: site.id }); }); }; const onDeleteSiteClick = (site) => { setConfirmDeleteState({ site, open: true }); }; const onConfirmDeleteSite = (site) => { setDisabledSitesLookup({ [site.id]: true }); trash(site.id).subscribe({ next() { dispatch( batchActions([ popSite({ siteId: site.id, isActive: site.id === active }), showSystemNotification({ message: formatMessage(translations.siteDeleted) }), fetchSites() ]) ); setDisabledSitesLookup({ [site.id]: false }); }, error({ response: { response } }) { setDisabledSitesLookup({ [site.id]: false }); dispatch(showErrorDialog({ error: response })); } }); }; const onEditSiteClick = (site) => { dispatch(showEditSiteDialog({ site })); }; const onPublishButtonClick = (event, site, status) => { event.preventDefault(); event.stopPropagation(); setSelectedSiteStatus(status); publishingStatusDialogState.onOpen(); }; const onDuplicateSiteClick = (siteId) => { setDuplicateSiteId(siteId); setIsDuplicateDialogFromCreateDialog(false); duplicateSiteDialogState.onOpen(); }; const handleChangeView = () => { if (currentView === 'grid') { setCurrentView('list'); setStoredGlobalMenuSiteViewPreference('list', user.username); } else { setCurrentView('grid'); setStoredGlobalMenuSiteViewPreference('grid', user.username); } }; const createSiteDialogGoBackFromDuplicate = () => { duplicateSiteDialogState.onClose(); setOpenCreateSiteDialog(true); }; const publishingStatusDialogState = useEnhancedDialogState(); const handleCreateSiteClick = () => setOpenCreateSiteDialog(true); const hasCreateSitePermission = permissionsLookup['create_site']; const cardHeaderBlock = React.createElement(CardHeader, { title: React.createElement(FormattedMessage, { defaultMessage: 'Get Started' }), titleTypographyProps: { variant: 'h6' }, subheader: hasCreateSitePermission ? React.createElement(FormattedMessage, { defaultMessage: 'Create your first project.' }) : React.createElement(FormattedMessage, { defaultMessage: 'Contact your administrator to gain access to existing projects.' }) }); return React.createElement( Paper, { elevation: 0 }, React.createElement(GlobalAppToolbar, { title: React.createElement(FormattedMessage, { id: 'GlobalMenu.Sites', defaultMessage: 'Projects' }), leftContent: hasCreateSitePermission && React.createElement( Button, { startIcon: React.createElement(AddIcon, null), variant: 'outlined', color: 'primary', onClick: handleCreateSiteClick }, React.createElement(FormattedMessage, { id: 'sites.createSite', defaultMessage: 'Create Project' }) ), rightContent: React.createElement( Tooltip, { title: React.createElement(FormattedMessage, { id: 'sites.ChangeView', defaultMessage: 'Change view' }) }, React.createElement( IconButton, { onClick: handleChangeView, size: 'large' }, currentView === 'grid' ? React.createElement(ListViewIcon, null) : React.createElement(GridViewIcon, null) ) ) }), React.createElement( ErrorBoundary, null, React.createElement( SuspenseWithEmptyState, { resource: resource, suspenseProps: { fallback: React.createElement(SkeletonSitesGrid, { numOfItems: 3, currentView: currentView }) }, withEmptyStateProps: { emptyStateProps: { title: React.createElement(FormattedMessage, { id: 'sitesGrid.emptyStateMessage', defaultMessage: 'No Projects Found' }), styles: { root: { margin: undefined } }, sxs: { root: { p: 5, bgcolor: 'background.default', borderRadius: 1, maxWidth: '550px', marginTop: '50px', marginLeft: 'auto', marginRight: 'auto' } }, children: React.createElement( Card, { elevation: hasCreateSitePermission ? 2 : 0, sx: { mt: 1, textAlign: 'center', ...(!hasCreateSitePermission && { border: '1px solid', borderColor: 'divider' }) } }, hasCreateSitePermission ? React.createElement(CardActionArea, { onClick: handleCreateSiteClick }, cardHeaderBlock) : cardHeaderBlock ) } } }, React.createElement(SitesGrid, { resource: resource, onSiteClick: onSiteClick, onDeleteSiteClick: permissionsLookup['delete_site'] && onDeleteSiteClick, onEditSiteClick: permissionsLookup['edit_site'] && onEditSiteClick, currentView: currentView, onPublishButtonClick: onPublishButtonClick, onDuplicateSiteClick: permissionsLookup['duplicate_site'] && onDuplicateSiteClick, disabledSitesLookup: disabledSitesLookup }) ) ), React.createElement(PublishingStatusDialog, { open: publishingStatusDialogState.open, onClose: publishingStatusDialogState.onClose, isMinimized: publishingStatusDialogState.isMinimized, hasPendingChanges: publishingStatusDialogState.isMinimized, isSubmitting: publishingStatusDialogState.isSubmitting, onClosed: () => { setSelectedSiteStatus(null); }, isFetching: false, ...selectedSiteStatus }), React.createElement(CreateSiteDialog, { open: openCreateSiteDialog, onClose: () => setOpenCreateSiteDialog(false), onShowDuplicate: () => { setIsDuplicateDialogFromCreateDialog(true); duplicateSiteDialogState.onOpen(); } }), React.createElement(DuplicateSiteDialog, { siteId: duplicateSiteId, open: duplicateSiteDialogState.open, onClose: () => { setDuplicateSiteId(null); duplicateSiteDialogState.onClose(); }, onGoBack: isDuplicateDialogFromCreateDialog ? createSiteDialogGoBackFromDuplicate : null, hasPendingChanges: duplicateSiteDialogState.hasPendingChanges, isSubmitting: duplicateSiteDialogState.isSubmitting, onSubmittingAndOrPendingChange: duplicateSiteDialogState.onSubmittingAndOrPendingChange }), React.createElement(ConfirmDialog, { open: confirmDeleteState.open, body: React.createElement( React.Fragment, null, React.createElement( Typography, null, React.createElement(FormattedMessage, { defaultMessage: 'Confirm the permanent deletion of the \u201C{siteId}\u201D project.', values: { siteId: confirmDeleteState.site?.id } }) ), React.createElement( Alert, { severity: 'warning', icon: false, sx: { mt: 2 } }, React.createElement(FormControlLabel, { sx: { textAlign: 'left' }, control: React.createElement(Checkbox, { color: 'primary', checked: confirmDeleteState.checked, onChange: () => setConfirmDeleteState({ checked: !confirmDeleteState.checked }) }), label: React.createElement(FormattedMessage, { defaultMessage: 'I understand deleting a project is immediate and irreversible.' }) }) ) ), okButtonText: React.createElement(FormattedMessage, { defaultMessage: 'Delete' }), disableOkButton: !confirmDeleteState.checked, onOk: () => { onConfirmDeleteSite(confirmDeleteState.site); setConfirmDeleteState(confirmDeleteInitialState); }, onCancel: () => setConfirmDeleteState(confirmDeleteInitialState) }) ); } export default SiteManagement;