UNPKG

@craftercms/studio-ui

Version:

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

361 lines (359 loc) 12.8 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 { getItemViewOption, getValidatedSelectionState, isPage, previewPage, useSelectionOptions, useSpreadStateWithSelected } from '../SiteDashboard/utils'; import DashletCard from '../DashletCard/DashletCard'; import palette from '../../styles/palette'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import React, { useCallback, useEffect } from 'react'; import { DashletEmptyMessage, DashletItemOptions, getItemSkeleton, List, ListItemIcon, Pager } from '../DashletCard/dashletCommons'; import useActiveSiteId from '../../hooks/useActiveSiteId'; import { fetchScheduled } from '../../services/dashboard'; import RefreshRounded from '@mui/icons-material/RefreshRounded'; import Checkbox from '@mui/material/Checkbox'; import ListItemText from '@mui/material/ListItemText'; import { LIVE_COLOUR, STAGING_COLOUR } from '../ItemPublishingTargetIcon/styles'; import ItemDisplay from '../ItemDisplay'; import ListItemButton from '@mui/material/ListItemButton'; import { asLocalizedDateTime } from '../../utils/datetime'; import useLocale from '../../hooks/useLocale'; import { ActionsBar } from '../ActionsBar'; import { UNDEFINED } from '../../utils/constants'; import { itemActionDispatcher } from '../../utils/itemActions'; import { useDispatch } from 'react-redux'; import useEnv from '../../hooks/useEnv'; import { deleteContentEvent, publishEvent, workflowEvent } from '../../state/actions/system'; import { getHostToHostBus } from '../../utils/subjects'; import { filter } from 'rxjs/operators'; import useSpreadState from '../../hooks/useSpreadState'; import { LoadingIconButton } from '../LoadingIconButton'; import Box from '@mui/material/Box'; import DashletFilter from '../ActivityDashlet/DashletFilter'; import useDashletFilterState from '../../hooks/useDashletFilterState'; import useUpdateRefs from '../../hooks/useUpdateRefs'; const messages = defineMessages({ staging: { id: 'words.staging', defaultMessage: 'Staging' }, live: { id: 'words.live', defaultMessage: 'Live' } }); export function ScheduledDashlet(props) { const { borderLeftColor = palette.blue.tint, onMinimize } = props; const site = useActiveSiteId(); const locale = useLocale(); const { formatMessage } = useIntl(); const { authoringBase } = useEnv(); const dispatch = useDispatch(); const [ { loading, loadingSkeleton, total, items, isAllSelected, hasSelected, selected, selectedCount, limit, offset, sortOrder, sortBy }, setState, onSelectItem, onSelectAll, isSelected ] = useSpreadStateWithSelected({ loading: false, loadingSkeleton: true, items: null, total: null, selected: {}, isAllSelected: false, hasSelected: false, limit: 50, offset: 0, sortBy: 'dateScheduled', sortOrder: 'asc' }); const currentPage = offset / limit; const totalPages = total ? Math.ceil(total / limit) : 0; const [itemsById, setItemsById] = useSpreadState({}); const selectedItems = Object.values(itemsById)?.filter((item) => selected[item.id]) ?? []; const selectionOptions = useSelectionOptions(selectedItems, formatMessage, selectedCount); const filterState = useDashletFilterState('scheduledDashlet'); const refs = useUpdateRefs({ items, currentPage, filterState, loadPagesUntil: null }); const loadPage = useCallback( (pageNumber, itemTypes, backgroundRefresh) => { const newOffset = pageNumber * limit; setState({ loading: true, loadingSkeleton: !backgroundRefresh }); fetchScheduled(site, { limit, offset: newOffset, itemType: refs.current.filterState.selectedTypes, sortBy, sortOrder }).subscribe((items) => { setState({ items, total: items.total, offset: newOffset, loading: false }); }); }, [limit, setState, site, sortBy, sortOrder, refs] ); const onOptionClicked = (option) => { // Clear selection setState({ selectedCount: 0, isAllSelected: false, selected: {}, hasSelected: false }); if (option !== 'clear') { return itemActionDispatcher({ site, authoringBase, dispatch, formatMessage, option, item: selectedItems.length > 1 ? selectedItems : selectedItems[0] }); } }; const onItemClick = (e, item) => { if (isPage(item.systemType)) { e.stopPropagation(); previewPage(site, authoringBase, item, dispatch, onMinimize); } else if (item.availableActionsMap.view) { e.stopPropagation(); itemActionDispatcher({ site, authoringBase, dispatch, formatMessage, option: getItemViewOption(item), item }); } }; const loadPagesUntil = useCallback( (pageNumber, itemTypes, backgroundRefresh) => { setState({ loading: true, loadingSkeleton: !backgroundRefresh, ...(!loadingSkeleton && { items: null }) }); const totalLimit = pageNumber * limit; fetchScheduled(site, { limit: totalLimit + limit, offset: 0, itemType: refs.current.filterState.selectedTypes, sortBy, sortOrder }).subscribe((scheduledItems) => { const validatedState = getValidatedSelectionState(scheduledItems, selected, limit); setItemsById(validatedState.itemsById); setState(validatedState.state); }); }, [limit, selected, setState, site, setItemsById, loadingSkeleton, sortBy, sortOrder, refs] ); refs.current.loadPagesUntil = loadPagesUntil; const onRefresh = () => { loadPagesUntil(currentPage, filterState.selectedTypes, true); }; useEffect(() => { if (items) { const itemsObj = {}; items.forEach((item) => { itemsObj[item.id] = item; }); setItemsById(itemsObj); } }, [items, setItemsById]); useEffect(() => { loadPage(0); }, [loadPage]); useEffect(() => { // To avoid re-fetching when it first loads if (refs.current.items) { refs.current.loadPagesUntil(refs.current.currentPage, filterState.selectedTypes); } }, [filterState?.selectedTypes, refs]); // region Item Updates Propagation useEffect(() => { const events = [workflowEvent.type, publishEvent.type, deleteContentEvent.type]; const hostToHost$ = getHostToHostBus(); const subscription = hostToHost$.pipe(filter((e) => events.includes(e.type))).subscribe(({ type, payload }) => { loadPagesUntil(currentPage, filterState.selectedTypes, true); }); return () => { subscription.unsubscribe(); }; }, [currentPage, loadPagesUntil, filterState?.selectedTypes]); // endregion return React.createElement( DashletCard, { ...props, borderLeftColor: borderLeftColor, title: React.createElement(FormattedMessage, { id: 'scheduledDashlet.widgetTitle', defaultMessage: 'Scheduled for Publish' }), headerAction: React.createElement( LoadingIconButton, { onClick: onRefresh, loading: loading }, React.createElement(RefreshRounded, null) ), actionsBar: React.createElement(ActionsBar, { disabled: loading, isChecked: isAllSelected, isIndeterminate: hasSelected && !isAllSelected, onCheckboxChange: onSelectAll, onOptionClicked: onOptionClicked, options: selectionOptions?.concat([ ...(selectedCount > 0 ? [ { id: 'clear', label: formatMessage( { defaultMessage: 'Clear {count} selected' }, { count: selectedCount } ) } ] : []) ]), noSelectionContent: React.createElement(DashletFilter, { selectedKeys: filterState.selectedKeys, onChange: filterState.onChange }), buttonProps: { size: 'small' }, sxs: { root: { flexGrow: 1 }, container: { bgcolor: hasSelected ? 'action.selected' : UNDEFINED }, checkbox: { padding: '5px', borderRadius: 0 }, button: { minWidth: 50 } } }), footer: Boolean(items?.length) && React.createElement(Pager, { totalPages: totalPages, totalItems: total, currentPage: currentPage, rowsPerPage: limit, onPagePickerChange: (page) => loadPage(page, filterState.selectedTypes), onPageChange: (page) => loadPage(page, filterState.selectedTypes), onRowsPerPageChange: (rowsPerPage) => setState({ limit: rowsPerPage }) }), sxs: { actionsBar: { padding: 0 }, content: { padding: 0 }, footer: { justifyContent: 'space-between' } } }, loading && loadingSkeleton && getItemSkeleton({ numOfItems: 3, showAvatar: false, showCheckbox: true }), items && React.createElement( List, { sx: { pb: 0 } }, items.map((item, index) => React.createElement( ListItemButton, { key: index, onClick: (e) => onSelectItem(e, item), sx: { pt: 0, pb: 0 } }, React.createElement( ListItemIcon, null, React.createElement(Checkbox, { edge: 'start', checked: isSelected(item), onChange: (e) => onSelectItem(e, item) }) ), React.createElement(ListItemText, { primary: React.createElement(ItemDisplay, { item: item, titleDisplayProp: 'path', onClick: (e) => isPage(item.systemType) || item.availableActionsMap.view ? onItemClick(e, item) : null, showNavigableAsLinks: isPage(item.systemType) || item.availableActionsMap.view }), secondary: React.createElement(FormattedMessage, { defaultMessage: 'Approved by {name} for <render_target>{publishingTarget}</render_target> at {date}', values: { name: item.sandbox?.submitter?.username ?? item.sandbox?.modifier?.username, publishingTarget: item.stateMap.submittedToLive ? 'live' : 'staging', render_target(target) { return React.createElement( Box, { component: 'span', color: target[0] === 'live' ? LIVE_COLOUR : STAGING_COLOUR }, messages[target[0]] ? formatMessage(messages[target[0]]).toLowerCase() : target[0] ); }, date: asLocalizedDateTime( item.stateMap.submittedToLive ? item.live.dateScheduled : item.staging.dateScheduled, locale.localeCode, locale.dateTimeFormatOptions ) } }) }), React.createElement(DashletItemOptions, { path: item.path }) ) ) ), total === 0 && React.createElement( DashletEmptyMessage, null, React.createElement(FormattedMessage, { defaultMessage: 'There are no items scheduled for publish' }) ) ); } export default ScheduledDashlet;