Files
frontend-app-authoring/src/library-authoring/components/CollectionCard.tsx
Daniel Valenzuela ae82486d72 fix: cannot open item sidebar after closing with 'X' button (#2241)
When closing an item (component, unit, section, container, etc) sidebar, and trying to reopen it inmediately by clicking the card, it was not opening because navigateTo was being used, but the URL already was the same you were being navigated to. So we also have to update the sidebar item info in the sidebar context in order for it to reopen properly.
2025-07-04 04:38:37 -05:00

178 lines
5.5 KiB
TypeScript

import { useCallback, useContext } from 'react';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import {
ActionRow,
Dropdown,
Icon,
IconButton,
useToggle,
} from '@openedx/paragon';
import { MoreVert } from '@openedx/paragon/icons';
import { type CollectionHit } from '../../search-manager';
import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
import { useLibraryContext } from '../common/context/LibraryContext';
import { SidebarBodyItemId, useSidebarContext } from '../common/context/SidebarContext';
import { useLibraryRoutes } from '../routes';
import BaseCard from './BaseCard';
import { ToastContext } from '../../generic/toast-context';
import { useDeleteCollection, useRestoreCollection } from '../data/apiHooks';
import DeleteModal from '../../generic/delete-modal/DeleteModal';
import messages from './messages';
type CollectionMenuProps = {
hit: CollectionHit,
};
const CollectionMenu = ({ hit } : CollectionMenuProps) => {
const intl = useIntl();
const { showToast } = useContext(ToastContext);
const { navigateTo } = useLibraryRoutes();
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false);
const { closeLibrarySidebar, sidebarItemInfo } = useSidebarContext();
const {
contextKey,
blockId,
type,
displayName,
} = hit;
const restoreCollectionMutation = useRestoreCollection(contextKey, blockId);
const restoreCollection = useCallback(() => {
restoreCollectionMutation.mutateAsync()
.then(() => {
showToast(intl.formatMessage(messages.undoDeleteCollectionToastMessage));
}).catch(() => {
showToast(intl.formatMessage(messages.undoDeleteCollectionToastFailed));
});
}, []);
const deleteCollectionMutation = useDeleteCollection(contextKey, blockId);
const deleteCollection = useCallback(async () => {
if (sidebarItemInfo?.id === blockId) {
// Close sidebar if current collection is open to avoid displaying
// deleted collection in sidebar
closeLibrarySidebar();
}
try {
await deleteCollectionMutation.mutateAsync();
showToast(
intl.formatMessage(messages.deleteCollectionSuccess),
{
label: intl.formatMessage(messages.undoDeleteCollectionToastAction),
onClick: restoreCollection,
},
);
} catch (e) {
showToast(intl.formatMessage(messages.deleteCollectionFailed));
} finally {
closeDeleteModal();
}
}, [sidebarItemInfo?.id]);
const openCollection = useCallback(() => {
navigateTo({ collectionId: blockId });
}, [blockId, navigateTo]);
return (
<>
<Dropdown id="collection-card-dropdown">
<Dropdown.Toggle
id="collection-card-menu-toggle"
as={IconButton}
src={MoreVert}
iconAs={Icon}
variant="primary"
alt={intl.formatMessage(messages.collectionCardMenuAlt)}
data-testid="collection-card-menu-toggle"
/>
<Dropdown.Menu>
<Dropdown.Item onClick={openCollection}>
<FormattedMessage {...messages.menuOpen} />
</Dropdown.Item>
<Dropdown.Item onClick={openDeleteModal}>
<FormattedMessage {...messages.deleteCollection} />
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<DeleteModal
isOpen={isDeleteModalOpen}
close={closeDeleteModal}
variant="warning"
category={type}
description={intl.formatMessage(messages.deleteCollectionConfirm, {
collectionTitle: displayName,
})}
onDeleteSubmit={deleteCollection}
/>
</>
);
};
type CollectionCardProps = {
hit: CollectionHit,
};
const CollectionCard = ({ hit } : CollectionCardProps) => {
const { componentPickerMode } = useComponentPickerContext();
const { setCollectionId, showOnlyPublished } = useLibraryContext();
const { openCollectionInfoSidebar, openItemSidebar, sidebarItemInfo } = useSidebarContext();
const {
type: itemType,
blockId: collectionId,
formatted,
tags,
numChildren,
published,
} = hit;
const numChildrenCount = showOnlyPublished ? (
published?.numChildren || 0
) : numChildren;
const { displayName = '', description = '' } = formatted;
const selected = sidebarItemInfo?.type === SidebarBodyItemId.CollectionInfo
&& sidebarItemInfo.id === collectionId;
const { navigateTo } = useLibraryRoutes();
const selectCollection = useCallback((e?: React.MouseEvent) => {
const doubleClicked = (e?.detail || 0) > 1;
if (!componentPickerMode) {
if (doubleClicked) {
navigateTo({ collectionId });
} else {
openItemSidebar(collectionId, SidebarBodyItemId.CollectionInfo);
}
// In component picker mode, we want to open the sidebar or the collection
// without changing the URL
} else if (doubleClicked) {
setCollectionId(collectionId);
} else {
openCollectionInfoSidebar(collectionId);
}
}, [collectionId, navigateTo, openItemSidebar, openCollectionInfoSidebar, setCollectionId, componentPickerMode]);
return (
<BaseCard
itemType={itemType}
displayName={displayName}
description={description}
tags={tags}
numChildren={numChildrenCount}
actions={!componentPickerMode && (
<ActionRow>
<CollectionMenu hit={hit} />
</ActionRow>
)}
onSelect={selectCollection}
selected={selected}
/>
);
};
export default CollectionCard;