@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
344 lines (342 loc) • 13.5 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, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import useStyles from './styles';
import { FormattedMessage, useIntl } from 'react-intl';
import { fetchLegacyGetGoLiveItems } from '../../../services/dashboard';
import AwaitingApprovalDashletGridUI from '../LegacyAwaitingApprovalDashletGrid';
import {
getNumOfMenuOptionsForItem,
getSystemTypeFromPath,
parseLegacyItemToDetailedItem
} from '../../../utils/content';
import LegacyDashletCard from '../LegacyDashletCard';
import AwaitingApprovalDashletSkeletonTable from '../LegacyAwaitingApprovalDashletGrid/AwaitingApprovalDashletSkeletonTable';
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 { useDispatch, useSelector } from 'react-redux';
import { getStoredDashboardPreferences, setStoredDashboardPreferences } from '../../../utils/state';
import { showItemMegaMenu } from '../../../state/actions/dialogs';
import ActionsBar from '../../ActionsBar';
import translations from './translations';
import { createPresenceTable } from '../../../utils/array';
import { itemActionDispatcher } from '../../../utils/itemActions';
import { useEnv } from '../../../hooks/useEnv';
import EmptyState, { getEmptyStateStyleSet } from '../../EmptyState';
import { useActiveSite } from '../../../hooks/useActiveSite';
import useItemsByPath from '../../../hooks/useItemsByPath';
import useFetchSandboxItems from '../../../hooks/useFetchSandboxItems';
import { ApiResponseErrorState } from '../../ApiResponseErrorState';
const dashletInitialPreferences = {
expanded: true,
showUnpublished: false
};
export function AwaitingApprovalDashlet() {
var _a;
const { id: siteId, uuid } = useActiveSite();
const { classes } = useStyles();
const [state, setState] = useState({
publishingTargetLookup: {},
itemsLookup: {},
parentItems: null,
total: null
});
const [selectedLookup, setSelectedLookup] = useState({});
const selectedLookupRef = useRef({});
selectedLookupRef.current = selectedLookup;
const [expandedLookup, setExpandedLookup] = useSpreadState({});
const [error, setError] = useState();
const currentUser = useSelector((state) => state.user.username);
const dashletPreferencesId = 'awaitingApprovalDashlet';
const [preferences, setPreferences] = useSpreadState(
(_a = getStoredDashboardPreferences(currentUser, uuid, dashletPreferencesId)) !== null && _a !== void 0
? _a
: dashletInitialPreferences
);
const [isFetching, setIsFetching] = useState(false);
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 itemsByPath = useItemsByPath();
useFetchSandboxItems(Object.keys(selectedLookup));
const refresh = useCallback(
(backgroundRefresh) => {
if (!backgroundRefresh) {
setIsFetching(true);
}
fetchLegacyGetGoLiveItems(siteId, 'eventDate', null, preferences.showUnpublished, null).subscribe(
(response) => {
const parentItems = [];
const itemsLookup = {};
const publishingTargetLookup = {};
const expandedLookup = {};
function getSubmittedItems(items, children) {
items.forEach((item) => {
if (item.contentType === 'folder') {
getSubmittedItems(item.children, children);
} else {
children.push(item);
}
});
}
response.documents.forEach((item) => {
if (item.children.length) {
expandedLookup[item.uri] = true;
const children = [];
getSubmittedItems(item.children, children);
parentItems.push({
label: item.name,
path: item.uri,
children: children.map((item) => {
publishingTargetLookup[item.uri] = item.submittedToEnvironment;
itemsLookup[item.uri] = parseLegacyItemToDetailedItem(item);
return item.uri;
})
});
}
});
setExpandedLookup(expandedLookup);
setState({
publishingTargetLookup,
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));
},
({ response }) => {
setError(response);
}
);
},
[setExpandedLookup, siteId, preferences.showUnpublished]
);
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 }) => {
switch (type) {
case deleteContentEvent.type:
case workflowEvent.type:
case publishEvent.type: {
refresh(true);
break;
}
}
});
return () => {
subscription.unsubscribe();
};
}, [refresh, selectedLookup, state.itemsLookup]);
// endregion
useEffect(() => {
setStoredDashboardPreferences(preferences, currentUser, uuid, dashletPreferencesId);
}, [preferences, currentUser, uuid]);
const onToggleCollapse = (e) => {
e.stopPropagation();
const lookup = {};
Object.keys(expandedLookup).forEach((path) => {
lookup[path] = showExpanded;
});
setExpandedLookup(lookup);
};
const onShowInProgress = (e) => {
e.stopPropagation();
setPreferences({ showUnpublished: !preferences.showUnpublished });
};
const onExpandedRow = (path, value) => {
setExpandedLookup({ [path]: value });
};
const onToggleCheckedAll = () => {
if (isAllChecked) {
setSelectedLookup({});
} else {
setSelectedLookup(
Object.assign(Object.assign({}, selectedLookup), createPresenceTable(Object.keys(state.itemsLookup), true))
);
}
};
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
});
}
};
return React.createElement(
LegacyDashletCard,
{
title: React.createElement(FormattedMessage, {
id: 'awaitingApprovalDashlet.dashletTitle',
defaultMessage: 'Items Waiting For Approval ({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(
Button,
{ disabled: isFetching, onClick: onShowInProgress },
preferences.showUnpublished
? React.createElement(FormattedMessage, {
id: 'awaitingApprovalDashlet.hideUnpublished',
defaultMessage: 'Hide Unpublished'
})
: React.createElement(FormattedMessage, {
id: 'awaitingApprovalDashlet.showUnpublished',
defaultMessage: 'Show Unpublished'
})
)
)
},
error
? React.createElement(ApiResponseErrorState, { error: error })
: isFetching
? React.createElement(AwaitingApprovalDashletSkeletonTable, {
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: '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(AwaitingApprovalDashletGridUI, {
items: state.parentItems,
expandedLookup: expandedLookup,
publishingTargetLookup: state.publishingTargetLookup,
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: 'awaitingApprovalDashlet.emptyMessage',
defaultMessage: 'No items are awaiting approval'
}),
styles: Object.assign(
Object.assign({}, getEmptyStateStyleSet('horizontal')),
getEmptyStateStyleSet('image-sm')
)
})
: React.createElement(React.Fragment, null)
);
}
export default AwaitingApprovalDashlet;