Files
frontend-app-authoring/src/library-authoring/components/ComponentCard.tsx
Braden MacDonald 6ae68bd122 feat: Menu option to delete a component + small fixes (#1408)
* feat: menu option to delete a component
* feat: close component sidebar if it's open when that component id deleted
* feat: hide unsupported block types from the "Add Content" menu
* fix: expand and internationalize the "component usage" text
2024-10-21 14:04:45 -07:00

154 lines
4.9 KiB
TypeScript

import { useContext, useState } from 'react';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import {
ActionRow,
Button,
Dropdown,
Icon,
IconButton,
useToggle,
} from '@openedx/paragon';
import { AddCircleOutline, MoreVert } from '@openedx/paragon/icons';
import { STUDIO_CLIPBOARD_CHANNEL } from '../../constants';
import { updateClipboard } from '../../generic/data/api';
import { ToastContext } from '../../generic/toast-context';
import { type ContentHit } from '../../search-manager';
import { useLibraryContext } from '../common/context';
import { useRemoveComponentsFromCollection } from '../data/apiHooks';
import BaseComponentCard from './BaseComponentCard';
import { canEditComponent } from './ComponentEditorModal';
import messages from './messages';
import ComponentDeleter from './ComponentDeleter';
type ComponentCardProps = {
contentHit: ContentHit,
};
export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
const intl = useIntl();
const {
libraryId,
collectionId,
sidebarComponentUsageKey,
openComponentEditor,
closeLibrarySidebar,
} = useLibraryContext();
const canEdit = usageKey && canEditComponent(usageKey);
const { showToast } = useContext(ToastContext);
const [clipboardBroadcastChannel] = useState(() => new BroadcastChannel(STUDIO_CLIPBOARD_CHANNEL));
const removeComponentsMutation = useRemoveComponentsFromCollection(libraryId, collectionId);
const [isConfirmingDelete, confirmDelete, cancelDelete] = useToggle(false);
const updateClipboardClick = () => {
updateClipboard(usageKey)
.then((clipboardData) => {
clipboardBroadcastChannel.postMessage(clipboardData);
showToast(intl.formatMessage(messages.copyToClipboardSuccess));
})
.catch(() => showToast(intl.formatMessage(messages.copyToClipboardError)));
};
const removeFromCollection = () => {
removeComponentsMutation.mutateAsync([usageKey]).then(() => {
if (sidebarComponentUsageKey === usageKey) {
// Close sidebar if current component is open
closeLibrarySidebar();
}
showToast(intl.formatMessage(messages.removeComponentSucess));
}).catch(() => {
showToast(intl.formatMessage(messages.removeComponentFailure));
});
};
return (
<Dropdown id="component-card-dropdown">
<Dropdown.Toggle
id="component-card-menu-toggle"
as={IconButton}
src={MoreVert}
iconAs={Icon}
variant="primary"
alt={intl.formatMessage(messages.componentCardMenuAlt)}
data-testid="component-card-menu-toggle"
/>
<Dropdown.Menu>
<Dropdown.Item {...(canEdit ? { onClick: () => openComponentEditor(usageKey) } : { disabled: true })}>
<FormattedMessage {...messages.menuEdit} />
</Dropdown.Item>
<Dropdown.Item onClick={updateClipboardClick}>
<FormattedMessage {...messages.menuCopyToClipboard} />
</Dropdown.Item>
<Dropdown.Item onClick={confirmDelete}>
<FormattedMessage {...messages.menuDelete} />
</Dropdown.Item>
{collectionId && (
<Dropdown.Item onClick={removeFromCollection}>
<FormattedMessage {...messages.menuRemoveFromCollection} />
</Dropdown.Item>
)}
<Dropdown.Item disabled>
<FormattedMessage {...messages.menuAddToCollection} />
</Dropdown.Item>
</Dropdown.Menu>
<ComponentDeleter usageKey={usageKey} isConfirmingDelete={isConfirmingDelete} cancelDelete={cancelDelete} />
</Dropdown>
);
};
const ComponentCard = ({ contentHit }: ComponentCardProps) => {
const {
openComponentInfoSidebar,
componentPickerMode,
} = useLibraryContext();
const {
blockType,
formatted,
tags,
usageKey,
} = contentHit;
const description: string = (/* eslint-disable */
blockType === 'html' ? formatted?.content?.htmlContent :
blockType === 'problem' ? formatted?.content?.capaContent :
undefined
) ?? '';/* eslint-enable */
const displayName = formatted?.displayName ?? '';
const handleAddComponentToCourse = () => {
window.parent.postMessage({
usageKey,
type: 'pickerComponentSelected',
category: blockType,
}, '*');
};
return (
<BaseComponentCard
componentType={blockType}
displayName={displayName}
description={description}
tags={tags}
actions={(
<ActionRow>
{componentPickerMode ? (
<Button
variant="outline-primary"
iconBefore={AddCircleOutline}
onClick={handleAddComponentToCourse}
>
<FormattedMessage {...messages.addComponentToCourseButtonTitle} />
</Button>
) : (
<ComponentMenu usageKey={usageKey} />
)}
</ActionRow>
)}
openInfoSidebar={() => openComponentInfoSidebar(usageKey)}
/>
);
};
export default ComponentCard;