UNPKG

@craftercms/studio-ui

Version:

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

366 lines (364 loc) 14.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, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import LegacyDashletCard from '../LegacyDashletCard'; import { FormattedMessage, useIntl } from 'react-intl'; import { fetchLegacyScheduledItems } from '../../../services/dashboard'; import { getNumOfMenuOptionsForItem, getSystemTypeFromPath, parseLegacyItemToDetailedItem } from '../../../utils/content'; import ApprovedScheduledDashletGridUI from '../LegacyApprovedScheduledDashletGrid'; import useStyles from './styles'; import ApprovedScheduledDashletSkeletonTable from '../LegacyApprovedScheduledDashletGrid/ApprovedScheduledDashletSkeletonTable'; import MenuItem from '@mui/material/MenuItem'; import TextField from '@mui/material/TextField'; import Button from '@mui/material/Button'; import { deleteContentEvent, publishEvent, workflowEvent } from '../../../state/actions/system'; import { getHostToHostBus } from '../../../utils/subjects'; import { filter } from 'rxjs/operators'; import { useSpreadState } from '../../../hooks/useSpreadState'; import { getStoredDashboardPreferences, setStoredDashboardPreferences } from '../../../utils/state'; import { useDispatch, useSelector } from 'react-redux'; import { createPresenceTable } from '../../../utils/array'; import { showItemMegaMenu } from '../../../state/actions/dialogs'; import { itemActionDispatcher } from '../../../utils/itemActions'; import { useEnv } from '../../../hooks/useEnv'; import ActionsBar from '../../ActionsBar'; import translations from './translations'; import { EmptyState, getEmptyStateStyleSet } from '../../EmptyState'; import { useActiveSite } from '../../../hooks/useActiveSite'; import { asLocalizedDateTime } from '../../../utils/datetime'; import { reversePluckProps } from '../../../utils/object'; import { useLocale } from '../../../hooks/useLocale'; import useFetchSandboxItems from '../../../hooks/useFetchSandboxItems'; import useItemsByPath from '../../../hooks/useItemsByPath'; import { ApiResponseErrorState } from '../../ApiResponseErrorState'; const dashletInitialPreferences = { filterBy: 'all', expanded: true }; export function ApprovedScheduledDashlet() { var _a; const [selectedLookup, setSelectedLookup] = useState({}); const selectedLookupRef = useRef({}); selectedLookupRef.current = selectedLookup; const [error, setError] = useState(); const { id: siteId, uuid } = useActiveSite(); const currentUser = useSelector((state) => state.user.username); const { classes } = useStyles(); const [state, setState] = useState({ targetLookup: {}, itemsLookup: {}, parentItems: null, total: null }); const [expandedLookup, setExpandedLookup] = useSpreadState({}); const [isFetching, setIsFetching] = useState(false); const dashletPreferencesId = 'approvedScheduledDashlet'; const [preferences, setPreferences] = useSpreadState( (_a = getStoredDashboardPreferences(currentUser, uuid, dashletPreferencesId)) !== null && _a !== void 0 ? _a : dashletInitialPreferences ); const dispatch = useDispatch(); const { formatMessage } = useIntl(); const { authoringBase } = useEnv(); const showExpanded = useMemo(() => Object.values(expandedLookup).some((value) => !value), [expandedLookup]); const isAllChecked = useMemo( () => Boolean(Object.keys(selectedLookup).length) && !Object.keys(state.itemsLookup).some((path) => !selectedLookup[path]), [selectedLookup, state.itemsLookup] ); const selectedItemsLength = useMemo(() => Object.values(selectedLookup).filter(Boolean).length, [selectedLookup]); const isIndeterminate = useMemo( () => Object.keys(state.itemsLookup).some((path) => selectedLookup[path]) && !isAllChecked, [isAllChecked, selectedLookup, state.itemsLookup] ); const locale = useLocale(); const itemsByPath = useItemsByPath(); useFetchSandboxItems(Object.keys(selectedLookup)); const refresh = useCallback( (backgroundRefresh) => { if (!backgroundRefresh) { setIsFetching(true); } fetchLegacyScheduledItems(siteId, 'eventDate', false, preferences.filterBy).subscribe({ next(response) { const parentItems = []; const itemsLookup = {}; const targetLookup = {}; const expandedLookup = {}; response.documents.forEach((item) => { var _a, _b; if (item.children.length) { expandedLookup[(_a = item.uri) !== null && _a !== void 0 ? _a : item.name] = true; parentItems.push({ label: asLocalizedDateTime( item.name, locale.localeCode, reversePluckProps(locale.dateTimeFormatOptions, 'hour', 'minute', 'second') ), path: (_b = item.uri) !== null && _b !== void 0 ? _b : item.name, children: item.children.map((item) => { targetLookup[item.uri] = { target: item.environment, packageId: item.packageId }; itemsLookup[item.uri] = parseLegacyItemToDetailedItem(item); // TODO: remove this when legacy item submittedToEnvironment is not null itemsLookup[item.uri].stateMap.submittedToLive = item.environment === 'live'; itemsLookup[item.uri].stateMap.submittedToStaging = item.environment === 'staging'; // endTODO return item.uri; }) }); } }); setExpandedLookup(expandedLookup); setState({ targetLookup, itemsLookup, parentItems, total: response.total }); if (!backgroundRefresh) { setIsFetching(false); } // Update selected lookup const selectedKeys = Object.keys(selectedLookupRef.current).filter((selected) => Boolean(itemsLookup[selected]) ); setSelectedLookup(createPresenceTable(selectedKeys, true)); }, error({ response }) { setError(response); } }); }, [siteId, preferences.filterBy, setExpandedLookup, locale.localeCode, locale.dateTimeFormatOptions] ); useEffect(() => { refresh(); }, [refresh]); // region Item Updates Propagation useEffect(() => { const events = [deleteContentEvent.type, workflowEvent.type, publishEvent.type]; const hostToHost$ = getHostToHostBus(); const subscription = hostToHost$.pipe(filter((e) => events.includes(e.type))).subscribe(({ type, payload }) => { refresh(true); }); return () => { subscription.unsubscribe(); }; }, [refresh, state.itemsLookup]); // endregion const onToggleCollapse = (e) => { e.stopPropagation(); const lookup = {}; Object.keys(expandedLookup).forEach((path) => { lookup[path] = showExpanded; }); setExpandedLookup(lookup); }; const onExpandedRow = (path, value) => { setExpandedLookup({ [path]: value }); }; const onToggleCheckedAll = () => { if (isAllChecked) { setSelectedLookup({}); } else { setSelectedLookup( Object.assign(Object.assign({}, selectedLookup), createPresenceTable(Object.keys(state.itemsLookup))) ); } }; const handleItemChecked = (path) => { setSelectedLookup(Object.assign(Object.assign({}, selectedLookup), { [path]: !selectedLookup[path] })); }; const onItemMenuClick = (event, item) => { const path = item.path; dispatch( showItemMegaMenu({ path, anchorReference: 'anchorPosition', anchorPosition: { top: event.clientY, left: event.clientX }, numOfLoaderItems: getNumOfMenuOptionsForItem({ path: item.path, systemType: getSystemTypeFromPath(item.path) }) }) ); }; const onActionBarOptionClicked = (option) => { if (option === 'clear') { setSelectedLookup({}); } else { const selected = Object.keys(selectedLookup) .filter((path) => selectedLookup[path]) .flatMap((path) => { var _a; return (_a = itemsByPath[path]) !== null && _a !== void 0 ? _a : []; }); itemActionDispatcher({ site: siteId, item: selected.length > 1 ? selected : selected[0], option: option, authoringBase, dispatch, formatMessage }); } }; const onFilterChange = (event) => { event.stopPropagation(); setPreferences({ filterBy: event.target.value }); }; useEffect(() => { setStoredDashboardPreferences(preferences, currentUser, uuid, dashletPreferencesId); }, [preferences, currentUser, uuid]); return React.createElement( LegacyDashletCard, { title: React.createElement(FormattedMessage, { id: 'approvedScheduledItemsDashlet.dashletTitle', defaultMessage: 'Approved Scheduled Items ({count})', values: { count: state.total } }), expanded: preferences.expanded, onToggleExpanded: () => setPreferences({ expanded: !preferences.expanded }), refreshDisabled: isFetching, onRefresh: refresh, headerRightSection: React.createElement( React.Fragment, null, React.createElement( Button, { disabled: isFetching, onClick: onToggleCollapse, className: classes.collapseAll }, showExpanded ? React.createElement(FormattedMessage, { id: 'common.expandAll', defaultMessage: 'Expand All' }) : React.createElement(FormattedMessage, { id: 'common.collapseAll', defaultMessage: 'Collapse All' }) ), React.createElement( TextField, { label: React.createElement(FormattedMessage, { id: 'approvedScheduledItemsDashlet.filterBy', defaultMessage: 'Filter by' }), select: true, size: 'small', value: preferences.filterBy, disabled: isFetching, onChange: onFilterChange, onClick: (e) => e.stopPropagation() }, React.createElement( MenuItem, { value: 'page' }, React.createElement(FormattedMessage, { id: 'words.pages', defaultMessage: 'Pages' }) ), React.createElement( MenuItem, { value: 'component' }, React.createElement(FormattedMessage, { id: 'words.components', defaultMessage: 'Components' }) ), React.createElement( MenuItem, { value: 'asset' }, React.createElement(FormattedMessage, { id: 'words.assets', defaultMessage: 'Assets' }) ), React.createElement( MenuItem, { value: 'all' }, React.createElement(FormattedMessage, { id: 'words.all', defaultMessage: 'All' }) ) ) ) }, error ? React.createElement(ApiResponseErrorState, { error: error }) : isFetching ? React.createElement(ApprovedScheduledDashletSkeletonTable, { items: state.parentItems, expandedLookup: expandedLookup }) : state.parentItems ? state.parentItems.length ? React.createElement( React.Fragment, null, (isIndeterminate || isAllChecked) && React.createElement(ActionsBar, { classes: { root: classes.actionsBarRoot, checkbox: classes.actionsBarCheckbox }, options: [ { id: 'rejectPublish', label: formatMessage(translations.reject) }, { id: 'schedulePublish', label: formatMessage(translations.schedule) }, { id: 'publish', label: formatMessage(translations.publish) }, { id: 'clear', label: formatMessage(translations.clear, { count: selectedItemsLength }) } ], isIndeterminate: isIndeterminate, isChecked: isAllChecked, onOptionClicked: onActionBarOptionClicked, onCheckboxChange: onToggleCheckedAll }), React.createElement(ApprovedScheduledDashletGridUI, { items: state.parentItems, expandedLookup: expandedLookup, targetLookup: state.targetLookup, itemsLookup: state.itemsLookup, selectedLookup: selectedLookup, onToggleCheckedAll: onToggleCheckedAll, isAllChecked: isAllChecked, isIndeterminate: isIndeterminate, onExpandedRow: onExpandedRow, onItemMenuClick: onItemMenuClick, onItemChecked: handleItemChecked }) ) : React.createElement(EmptyState, { title: React.createElement(FormattedMessage, { id: 'approvedScheduledItemsDashlet.emptyMessage', defaultMessage: 'No items are scheduled' }), styles: Object.assign( Object.assign({}, getEmptyStateStyleSet('horizontal')), getEmptyStateStyleSet('image-sm') ) }) : React.createElement(React.Fragment, null) ); } export default ApprovedScheduledDashlet;