UNPKG

@craftercms/studio-ui

Version:

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

197 lines (195 loc) 8.25 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 React, { useEffect, useState } from 'react'; import { useActiveSiteId } from '../../hooks/useActiveSiteId'; import { useDispatch } from 'react-redux'; import { fetchDeleteDependencies, fetchDeleteDependenciesComplete, showEditDialog, updateDeleteDialog } from '../../state/actions/dialogs'; import { deleteItems } from '../../services/content'; import { DeleteDialogUI } from './DeleteDialogUI'; import { useSelection } from '../../hooks/useSelection'; import { createPresenceTable } from '../../utils/array'; import { isBlank } from '../../utils/string'; function createCheckedList(selectedItems, excludedPaths) { return Object.entries(selectedItems) .filter( ([path, isChecked]) => isChecked && !(excludedPaths === null || excludedPaths === void 0 ? void 0 : excludedPaths.includes(path)) ) .map(([path]) => path); } function createCheckedLookup(items, setChecked = true) { const isString = typeof items[0] === 'string'; return items.reduce((checked, item) => { // @ts-ignore - `isString` above pre-checks the type, typescript doesn't realise this is safe by this point. checked[isString ? item : item.path] = setChecked; return checked; }, {}); } export function DeleteDialogContainer(props) { const { items, onClose, isSubmitting, onSuccess, isFetching, childItems, dependentItems, error } = props; const [comment, setComment] = useState(''); const [submitError, setSubmitError] = useState(null); const site = useActiveSiteId(); const isCommentRequired = useSelection((state) => state.uiConfig.publishing.deleteCommentRequired); const [selectedItems, setSelectedItems] = useState({}); const dispatch = useDispatch(); const [submitDisabled, setSubmitDisabled] = useState(true); const [confirmChecked, setConfirmChecked] = useState(false); const authoringBase = useSelection((state) => state.env.authoringBase); const onSubmit = () => { const paths = createCheckedList(selectedItems); dispatch(updateDeleteDialog({ isSubmitting: true })); deleteItems(site, paths, comment).subscribe({ next() { dispatch(updateDeleteDialog({ isSubmitting: false, hasPendingChanges: false })); onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess({ items: paths.map((path) => items.find((item) => item.path === path)) }); }, error({ response }) { dispatch(updateDeleteDialog({ isSubmitting: false })); setSubmitError(response); } }); }; const onCloseButtonClick = (e) => onClose(e, null); const onCommentChange = (e) => { dispatch(updateDeleteDialog({ hasPendingChanges: true })); setComment(e.target.value); }; const fetchOrCleanDependencies = (nextChecked) => { let paths = createCheckedList(nextChecked); if (paths.length) { dispatch(fetchDeleteDependencies({ paths })); } else { dispatch(fetchDeleteDependenciesComplete({ dependentItems: [], childItems: [] })); } }; const onItemClicked = (e, path) => { let nextChecked = Object.assign(Object.assign({}, selectedItems), { [path]: !selectedItems[path] }); // Clean the state, only keep checked items !nextChecked[path] && delete nextChecked[path]; // If there aren't any checked main items, uncheck everything. const checkedMainItems = createCheckedList(nextChecked, dependentItems); checkedMainItems.length === 0 && (nextChecked = {}); fetchOrCleanDependencies(nextChecked); setSelectedItems(nextChecked); }; const onSelectAllClicked = () => { const setChecked = Boolean(items.find((item) => !selectedItems[item.path])); // If the "select all" checkbox is working to check all, then clean all `false`s and // check all main items. Otherwise, if it's working to uncheck all, everything should get // unchecked (both main & dependant items). const nextChecked = setChecked ? Object.assign( Object.assign( {}, createPresenceTable( createCheckedList( selectedItems, items.map((item) => item.path) ) ) ), createCheckedLookup(items, setChecked) ) : {}; fetchOrCleanDependencies(nextChecked); setSelectedItems(nextChecked); }; const onSelectAllDependantClicked = () => { const setChecked = Boolean(dependentItems.find((path) => !selectedItems[path])); // Clean up all set to `false` from the selected lookup. const cleanLookup = createPresenceTable(createCheckedList(selectedItems, dependentItems)); const nextChecked = Object.assign( Object.assign({}, cleanLookup), setChecked && createCheckedLookup(dependentItems, setChecked) ); setSelectedItems(nextChecked); }; const onConfirmChange = (e) => { setConfirmChecked(e.target.checked); }; const onEditDependantClick = (e, path) => { let paths = createCheckedList(selectedItems, dependentItems); // We don't have a good way of knowing if the dependant item cleared it's dependency and if it's checked, it // needs to get removed from selectedItems after the edit is complete and the item is not even listed as a dependency. // Until we find a better way around that, will uncheck when the edit button is pressed. selectedItems[path] && onItemClicked(null, path); dispatch(showEditDialog({ path, authoringBase, site, onSaveSuccess: fetchDeleteDependencies({ paths }) })); }; useEffect(() => { if (items.length) { const nextChecked = createPresenceTable(items, true, (item) => item.path); setSelectedItems(nextChecked); dispatch(fetchDeleteDependencies({ paths: items.map((i) => i.path) })); } }, [dispatch, items]); useEffect(() => { setSubmitDisabled( isSubmitting || Object.values(selectedItems).length === 0 || (isCommentRequired && isBlank(comment)) || !confirmChecked ); }, [isSubmitting, comment, isCommentRequired, selectedItems, confirmChecked]); return React.createElement(DeleteDialogUI, { items: items, childItems: childItems, dependentItems: dependentItems, selectedItems: selectedItems, error: error, submitError: submitError, isFetching: isFetching, comment: comment, onCommentChange: onCommentChange, isDisabled: isSubmitting, isSubmitting: isSubmitting, onSubmit: onSubmit, onCloseButtonClick: onCloseButtonClick, isCommentRequired: isCommentRequired, isSubmitButtonDisabled: submitDisabled, onItemClicked: onItemClicked, onSelectAllClicked: onSelectAllClicked, onSelectAllDependantClicked: onSelectAllDependantClicked, onConfirmDeleteChange: onConfirmChange, isConfirmDeleteChecked: confirmChecked, onEditDependantClick: onEditDependantClick }); } export default DeleteDialogContainer;