@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
308 lines (306 loc) • 11.7 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-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;