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