@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
352 lines (350 loc) • 14.4 kB
JavaScript
/*
* 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;