UNPKG

@craftercms/studio-ui

Version:

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

308 lines (306 loc) 11.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-2023 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, useState } from 'react'; import useSpreadState from '../../../hooks/useSpreadState'; import { useDispatch, useSelector } from 'react-redux'; import { getStoredDashboardPreferences, setStoredDashboardPreferences } from '../../../utils/state'; import useActiveSite from '../../../hooks/useActiveSite'; import { fetchPendingApproval as fetchPendingApprovalService } from '../../../services/dashboard'; import { LegacyDashletCard } from '../LegacyDashletCard'; import { FormattedMessage, useIntl } from 'react-intl'; import TextField from '@mui/material/TextField'; import MenuItem from '@mui/material/MenuItem'; import { ApiResponseErrorState } from '../../ApiResponseErrorState'; import { EmptyState, getEmptyStateStyleSet } from '../../EmptyState'; import InReviewDashletGridUISkeleton from './LegacyInReviewDashletGridUISkeleton'; import LegacyInReviewDashletGridUI from './LegacyInReviewDashletGridUI'; import { contentEvent, deleteContentEvent, publishEvent, workflowEvent } from '../../../state/actions/system'; import { getHostToHostBus } from '../../../utils/subjects'; import { filter } from 'rxjs/operators'; import useLocale from '../../../hooks/useLocale'; import { createPresenceTable } from '../../../utils/array'; import useEnv from '../../../hooks/useEnv'; import { showItemMegaMenu } from '../../../state/actions/dialogs'; import { getNumOfMenuOptionsForItem, getSystemTypeFromPath } from '../../../utils/content'; import { ActionsBar } from '../../ActionsBar'; import { itemActionDispatcher } from '../../../utils/itemActions'; import translations from '../translations'; import useStyles from '../styles'; const dashletInitialPreferences = { numItems: 10, expanded: true }; export function LegacyInReviewDashlet() { var _a, _b; const [state, setState] = useSpreadState({ items: null, total: 0, fetching: false, error: null }); const { id: siteId, uuid } = useActiveSite(); const { classes } = useStyles(); const currentUser = useSelector((state) => state.user.username); const dashletPreferencesId = 'inReviewDashlet'; const [preferences, setPreferences] = useSpreadState( (_a = getStoredDashboardPreferences(currentUser, uuid, dashletPreferencesId)) !== null && _a !== void 0 ? _a : dashletInitialPreferences ); const [selectedLookup, setSelectedLookup] = useState({}); const locale = useLocale(); const dispatch = useDispatch(); const { formatMessage } = useIntl(); const { authoringBase } = useEnv(); const isAllChecked = useMemo(() => { var _a, _b; const nonDeletedItems = (_b = (_a = state.items) === null || _a === void 0 ? void 0 : _a.filter((item) => !item.stateMap.deleted)) !== null && _b !== void 0 ? _b : []; if (nonDeletedItems.length) { // Is there at least one (non deleted item) that's not checked? If so, they're NOT all checked. return !nonDeletedItems.some((item) => !selectedLookup[item.path]); } else { return false; } }, [state.items, selectedLookup]); const isIndeterminate = useMemo(() => { var _a, _b; return (_b = (_a = state.items) === null || _a === void 0 ? void 0 : _a.some((item) => selectedLookup[item.path] && !isAllChecked)) !== null && _b !== void 0 ? _b : false; }, [state.items, selectedLookup, isAllChecked]); const selectedItemsLength = useMemo(() => Object.values(selectedLookup).filter(Boolean).length, [selectedLookup]); const onToggleCheckedAll = () => { if (isAllChecked) { setSelectedLookup({}); } else { setSelectedLookup( Object.assign( Object.assign({}, selectedLookup), createPresenceTable( state.items.filter((item) => !item.stateMap.deleted), true, (item) => item.path ) ) ); } }; const handleItemChecked = (path) => { setSelectedLookup(Object.assign(Object.assign({}, selectedLookup), { [path]: !selectedLookup[path] })); }; const fetchPendingApproval = useCallback( (backgroundRefresh) => { if (!backgroundRefresh) { setState({ fetching: true }); } fetchPendingApprovalService(siteId, { limit: preferences.numItems, offset: 0 }).subscribe({ next(items) { setState( Object.assign({ items, total: items.total, error: null }, !backgroundRefresh && { fetching: false }) ); }, error(e) { setState(Object.assign({ error: e }, !backgroundRefresh && { fetching: false })); } }); }, [siteId, preferences.numItems, setState] ); const onNumItemsChange = (e) => { setPreferences({ numItems: e.target.value }); }; 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]); let selectedItems = []; selected.forEach((itemPath) => { const item = state.items.find((item) => itemPath === item.path); if (item) { selectedItems.push(item); } }); itemActionDispatcher({ site: siteId, item: selected.length > 1 ? selectedItems : selectedItems[0], option: option, authoringBase, dispatch, formatMessage }); } }; // region Effects useEffect(() => { setStoredDashboardPreferences(preferences, currentUser, uuid, dashletPreferencesId); }, [preferences, currentUser, uuid]); useEffect(() => { fetchPendingApproval(); }, [fetchPendingApproval]); // endregion // region Item Updates Propagation useEffect(() => { const events = [deleteContentEvent.type, workflowEvent.type, publishEvent.type, contentEvent.type]; const hostToHost$ = getHostToHostBus(); const subscription = hostToHost$.pipe(filter((e) => events.includes(e.type))).subscribe(({ type, payload }) => { if (type === deleteContentEvent.type) { setSelectedLookup({}); } fetchPendingApproval(true); }); return () => { subscription.unsubscribe(); }; }, [fetchPendingApproval, selectedLookup]); // endregion return React.createElement( LegacyDashletCard, { title: React.createElement( React.Fragment, null, React.createElement(FormattedMessage, { id: 'inReviewDashlet.dashletTitle', defaultMessage: 'In Review' }), ' (', state.total, ')' ), onToggleExpanded: () => setPreferences({ expanded: !preferences.expanded }), expanded: preferences.expanded, refreshDisabled: state.fetching, onRefresh: fetchPendingApproval, headerRightSection: React.createElement( React.Fragment, null, React.createElement( TextField, { label: React.createElement(FormattedMessage, { id: 'words.show', defaultMessage: 'Show' }), select: true, size: 'small', value: preferences.numItems, disabled: state.fetching, onChange: onNumItemsChange, onClick: (e) => e.stopPropagation() }, React.createElement(MenuItem, { value: 10 }, '10'), React.createElement(MenuItem, { value: 20 }, '20'), React.createElement(MenuItem, { value: 50 }, '50'), state.total > 0 && React.createElement( MenuItem, { value: state.total }, React.createElement(FormattedMessage, { id: 'words.all', defaultMessage: 'All' }), ' (', state.total, ')' ) ) ) }, state.error ? React.createElement(ApiResponseErrorState, { error: state.error }) : state.fetching ? React.createElement(InReviewDashletGridUISkeleton, { numOfItems: (_b = state.items) === null || _b === void 0 ? void 0 : _b.length }) : state.items ? state.items.length ? React.createElement( React.Fragment, null, (isIndeterminate || isAllChecked) && React.createElement(ActionsBar, { classes: { root: classes.actionsBarRoot, checkbox: classes.actionsBarCheckbox }, options: [ { id: 'approvePublish', label: formatMessage(translations.publish) }, { id: 'rejectPublish', label: formatMessage(translations.reject) }, { id: 'clear', label: formatMessage(translations.clear, { count: selectedItemsLength }) } ], isIndeterminate: isIndeterminate, isChecked: isAllChecked, onOptionClicked: onActionBarOptionClicked, onCheckboxChange: onToggleCheckedAll }), React.createElement(LegacyInReviewDashletGridUI, { items: state.items, locale: locale, onOptionsButtonClick: onItemMenuClick, selectedLookup: selectedLookup, isAllChecked: isAllChecked, isIndeterminate: isIndeterminate, onItemChecked: handleItemChecked, onClickSelectAll: onToggleCheckedAll }) ) : React.createElement(EmptyState, { title: React.createElement(FormattedMessage, { id: 'inReviewDashlet.emptyMessage', defaultMessage: 'No items in review' }), styles: Object.assign( Object.assign({}, getEmptyStateStyleSet('horizontal')), getEmptyStateStyleSet('image-sm') ) }) : React.createElement(React.Fragment, null) ); } export default LegacyInReviewDashlet;