UNPKG

@craftercms/studio-ui

Version:

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

279 lines (277 loc) 10.7 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 { merge } from 'rxjs'; import { fetchStatus } from '../../services/publishing'; import { map } from 'rxjs/operators'; import { setSiteCookie } from '../../utils/auth'; import { trash } from '../../services/sites'; import { batchActions, dispatchDOMEvent } 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 { closeEditSiteDialog, 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, nnou } from '../../utils/object'; import { useEnv } from '../../hooks/useEnv'; import { useActiveUser } from '../../hooks/useActiveUser'; import { useLogicResource } from '../../hooks/useLogicResource'; import { useMount } from '../../hooks/useMount'; 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 { createCustomDocumentEventListener } from '../../utils/dom'; const translations = defineMessages({ siteDeleted: { id: 'sitesGrid.siteDeleted', defaultMessage: 'Project deleted successfully' } }); export function SiteManagement() { var _a; const dispatch = useDispatch(); const { formatMessage } = useIntl(); const { authoringBase, useBaseDomain } = useEnv(); const [openCreateSiteDialog, setOpenCreateSiteDialog] = useState(false); const user = useActiveUser(); const [currentView, setCurrentView] = useState( (_a = getStoredGlobalMenuSiteViewPreference(user.username)) !== null && _a !== void 0 ? _a : 'grid' ); const sitesBranch = useSitesBranch(); const sitesById = sitesBranch.byId; const isFetching = sitesBranch.isFetching; const [publishingStatusLookup, setPublishingStatusLookup] = useSpreadState({}); const [selectedSiteStatus, setSelectedSiteStatus] = useState(null); const [permissionsLookup, setPermissionsLookup] = useState(foo); const [sitesRefreshCountLookup, setSitesRefreshCountLookup] = useSpreadState({}); useEffect(() => { merge( ...Object.keys(sitesById).map((siteId) => fetchStatus(siteId).pipe( map((status) => ({ status, siteId })) ) ) ).subscribe(({ siteId, status }) => { setPublishingStatusLookup({ [siteId]: status }); }); }, [setPublishingStatusLookup, sitesById]); useMount(() => { hasGlobalPermissions('create-site', 'edit_site', 'site_delete').subscribe(setPermissionsLookup); }); const resource = useLogicResource( useMemo( () => ({ sitesById, isFetching, permissionsLookup, sitesRefreshCountLookup }), [sitesById, isFetching, permissionsLookup, sitesRefreshCountLookup] ), { shouldResolve: (source) => Boolean(source.sitesById) && permissionsLookup !== foo && !isFetching, shouldReject: () => false, shouldRenew: (source, resource) => resource.complete, resultSelector: () => Object.values(sitesById).map((site) => { if (nnou(sitesRefreshCountLookup[site.id])) { return Object.assign(Object.assign({}, site), { imageUrl: `${site.imageUrl}&v=${sitesRefreshCountLookup[site.id]}` }); } return site; }), errorSelector: () => null } ); const onSiteClick = (site) => { setSiteCookie(site.id, useBaseDomain); window.location.href = getSystemLink({ systemLinkId: 'preview', authoringBase, site: site.id }); }; const onDeleteSiteClick = (site) => { trash(site.id).subscribe( () => { dispatch( batchActions([ popSite({ siteId: site.id }), showSystemNotification({ message: formatMessage(translations.siteDeleted) }), fetchSites() ]) ); }, ({ response: { response } }) => { dispatch(showErrorDialog({ error: response })); } ); }; const onEditSiteClick = (site) => { const eventId = 'editSiteImageUploadComplete'; createCustomDocumentEventListener(eventId, ({ type }) => { var _a; if (type === 'uploadComplete') { setSitesRefreshCountLookup({ [site.id]: ((_a = sitesRefreshCountLookup[site.id]) !== null && _a !== void 0 ? _a : 0) + 1 }); } }); dispatch( showEditSiteDialog({ site, onSiteImageChange: dispatchDOMEvent({ id: eventId, type: 'uploadComplete' }), onClose: batchActions([ closeEditSiteDialog(), dispatchDOMEvent({ id: eventId, type: 'close' }) ]) }) ); }; const onPublishButtonClick = (event, site) => { event.preventDefault(); event.stopPropagation(); setSelectedSiteStatus(publishingStatusLookup[site.id]); publishingStatusDialogState.onOpen(); }; const handleChangeView = () => { if (currentView === 'grid') { setCurrentView('list'); setStoredGlobalMenuSiteViewPreference('list', user.username); } else { setCurrentView('grid'); setStoredGlobalMenuSiteViewPreference('grid', user.username); } }; const publishingStatusDialogState = useEnhancedDialogState(); return React.createElement( Paper, { elevation: 0 }, React.createElement(GlobalAppToolbar, { title: React.createElement(FormattedMessage, { id: 'GlobalMenu.Sites', defaultMessage: 'Projects' }), leftContent: permissionsLookup['create-site'] && React.createElement( Button, { startIcon: React.createElement(AddIcon, null), variant: 'outlined', color: 'primary', onClick: () => setOpenCreateSiteDialog(true) }, 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' }) } } }, React.createElement(SitesGrid, { resource: resource, publishingStatusLookup: publishingStatusLookup, onSiteClick: onSiteClick, onDeleteSiteClick: permissionsLookup['site_delete'] && onDeleteSiteClick, onEditSiteClick: permissionsLookup['edit_site'] && onEditSiteClick, currentView: currentView, onPublishButtonClick: onPublishButtonClick }) ) ), React.createElement( PublishingStatusDialog, Object.assign( { 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) }) ); } export default SiteManagement;