Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/container-comparison/CompareContainersWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { LoadingSpinner } from '@src/generic/Loading';
import { useContainer, useContainerChildren } from '@src/library-authoring/data/apiHooks';
import { BoldText } from '@src/utils';

import { Container, LibraryBlockMetadata } from '@src/library-authoring/data/api';
import ChildrenPreview from './ChildrenPreview';
import ContainerRow from './ContainerRow';
import { useCourseContainerChildren } from './data/apiHooks';
Expand Down Expand Up @@ -60,7 +61,7 @@ const CompareContainersWidgetInner = ({
data: libData,
isError: isLibError,
error: libError,
} = useContainerChildren(state === 'removed' ? undefined : upstreamBlockId, true);
} = useContainerChildren<Container | LibraryBlockMetadata>(state === 'removed' ? undefined : upstreamBlockId, true);
const {
data: containerData,
isError: isContainerTitleError,
Expand Down
29 changes: 18 additions & 11 deletions src/library-authoring/common/context/SidebarContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface DefaultTabs {
export interface SidebarItemInfo {
type: SidebarBodyItemId;
id: string;
index?: number;
}

export enum SidebarActions {
Expand All @@ -88,7 +89,7 @@ export type SidebarContextData = {
openCollectionInfoSidebar: (collectionId: string) => void;
openComponentInfoSidebar: (usageKey: string) => void;
openContainerInfoSidebar: (usageKey: string) => void;
openItemSidebar: (selectedItemId: string, type: SidebarBodyItemId) => void;
openItemSidebar: (selectedItemId: string, type: SidebarBodyItemId, index?: number) => void;
sidebarItemInfo?: SidebarItemInfo;
sidebarAction: SidebarActions;
setSidebarAction: (action: SidebarActions) => void;
Expand Down Expand Up @@ -154,35 +155,38 @@ export const SidebarProvider = ({
setSidebarItemInfo({ id: '', type: SidebarBodyItemId.Info });
}, []);

const openComponentInfoSidebar = useCallback((usageKey: string) => {
const openComponentInfoSidebar = useCallback((usageKey: string, index?: number) => {
setSidebarItemInfo({
id: usageKey,
type: SidebarBodyItemId.ComponentInfo,
index,
});
}, []);

const openCollectionInfoSidebar = useCallback((newCollectionId: string) => {
const openCollectionInfoSidebar = useCallback((newCollectionId: string, index?: number) => {
setSidebarItemInfo({
id: newCollectionId,
type: SidebarBodyItemId.CollectionInfo,
index,
});
}, []);

const openContainerInfoSidebar = useCallback((usageKey: string) => {
const openContainerInfoSidebar = useCallback((usageKey: string, index?: number) => {
setSidebarItemInfo({
id: usageKey,
type: SidebarBodyItemId.ContainerInfo,
index,
});
}, []);

const { navigateTo } = useLibraryRoutes();
const openItemSidebar = useCallback((selectedItemId: string, type: SidebarBodyItemId) => {
navigateTo({ selectedItemId });
setSidebarItemInfo({ id: selectedItemId, type });
const openItemSidebar = useCallback((selectedItemId: string, type: SidebarBodyItemId, index?: number) => {
navigateTo({ selectedItemId, index });
setSidebarItemInfo({ id: selectedItemId, type, index });
}, [navigateTo, setSidebarItemInfo]);

// Set the initial sidebar state based on the URL parameters and context.
const { selectedItemId } = useParams();
const { selectedItemId, index: indexParam } = useParams();
const { collectionId, containerId } = useLibraryContext();
const { componentPickerMode } = useComponentPickerContext();

Expand All @@ -198,12 +202,15 @@ export const SidebarProvider = ({

// Handle selected item id changes
if (selectedItemId) {
// if a item is selected that means we have list of items displayed
// which means we can get the index from url and set it.
const indexNumber = indexParam ? Number(indexParam) : undefined;
if (selectedItemId.startsWith('lct:')) {
openContainerInfoSidebar(selectedItemId);
openContainerInfoSidebar(selectedItemId, indexNumber);
} else if (selectedItemId.startsWith('lb:')) {
openComponentInfoSidebar(selectedItemId);
openComponentInfoSidebar(selectedItemId, indexNumber);
} else {
openCollectionInfoSidebar(selectedItemId);
openCollectionInfoSidebar(selectedItemId, indexNumber);
}
} else if (collectionId) {
openCollectionInfoSidebar(collectionId);
Expand Down
4 changes: 3 additions & 1 deletion src/library-authoring/component-info/ComponentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ const ComponentActions = ({
const [isPublisherOpen, openPublisher, closePublisher] = useToggle(false);
const canEdit = canEditComponent(componentId);

const { sidebarItemInfo } = useSidebarContext();

if (isPublisherOpen) {
return (
<ComponentPublisher
Expand Down Expand Up @@ -141,7 +143,7 @@ const ComponentActions = ({
)}
</div>
<div className="mt-2">
<ComponentMenu usageKey={componentId} />
<ComponentMenu usageKey={componentId} index={sidebarItemInfo?.index} />
</div>
</div>
);
Expand Down
20 changes: 13 additions & 7 deletions src/library-authoring/components/ComponentMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@ import { useClipboard } from '@src/generic/clipboard';
import { getBlockType } from '@src/generic/key-utils';
import { ToastContext } from '@src/generic/toast-context';

import { useLibraryContext } from '../common/context/LibraryContext';
import { SidebarActions, SidebarBodyItemId, useSidebarContext } from '../common/context/SidebarContext';
import { useRemoveItemsFromCollection } from '../data/apiHooks';
import { useLibraryContext } from '@src/library-authoring/common/context/LibraryContext';
import { SidebarActions, SidebarBodyItemId, useSidebarContext } from '@src/library-authoring/common/context/SidebarContext';
import { useRemoveItemsFromCollection } from '@src/library-authoring/data/apiHooks';
import containerMessages from '@src/library-authoring/containers/messages';
import { useLibraryRoutes } from '@src/library-authoring/routes';
import { useRunOnNextRender } from '@src/utils';
import { canEditComponent } from './ComponentEditorModal';
import ComponentDeleter from './ComponentDeleter';
import ComponentRemover from './ComponentRemover';
import messages from './messages';
import containerMessages from '../containers/messages';
import { useLibraryRoutes } from '../routes';
import { useRunOnNextRender } from '../../utils';

export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
interface Props {
usageKey: string;
index?: number;
}

export const ComponentMenu = ({ usageKey, index }: Props) => {
const intl = useIntl();
const {
libraryId,
Expand Down Expand Up @@ -135,6 +140,7 @@ export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
{isRemoveModalOpen && (
<ComponentRemover
usageKey={usageKey}
index={index}
close={closeRemoveModal}
/>
)}
Expand Down
87 changes: 64 additions & 23 deletions src/library-authoring/components/ComponentRemover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,101 @@ import { Warning } from '@openedx/paragon/icons';

import DeleteModal from '@src/generic/delete-modal/DeleteModal';
import { ToastContext } from '@src/generic/toast-context';
import { useLibraryContext } from '../common/context/LibraryContext';
import { useSidebarContext } from '../common/context/SidebarContext';
import { useLibraryContext } from '@src/library-authoring/common/context/LibraryContext';
import { useSidebarContext } from '@src/library-authoring/common/context/SidebarContext';
import {
useContainer,
useRemoveContainerChildren,
useAddItemsToContainer,
useLibraryBlockMetadata,
} from '../data/apiHooks';
useContainerChildren,
useUpdateContainerChildren,
} from '@src/library-authoring/data/apiHooks';
import { LibraryBlockMetadata } from '@src/library-authoring/data/api';
import messages from './messages';

interface Props {
usageKey: string;
index?: number;
close: () => void;
}

const ComponentRemover = ({ usageKey, close }: Props) => {
const ComponentRemover = ({ usageKey, index, close }: Props) => {
const intl = useIntl();
const { sidebarItemInfo, closeLibrarySidebar } = useSidebarContext();
const { containerId } = useLibraryContext();
const { containerId, showOnlyPublished } = useLibraryContext();
const { showToast } = useContext(ToastContext);

const removeContainerItemMutation = useRemoveContainerChildren(containerId);
const addItemToContainerMutation = useAddItemsToContainer(containerId);
const updateContainerChildrenMutation = useUpdateContainerChildren(containerId);
const { data: container, isPending: isPendingParentContainer } = useContainer(containerId);
const { data: component, isPending } = useLibraryBlockMetadata(usageKey);
// Use update api for children if duplicates are present to avoid removing all instances of the child
const { data: children } = useContainerChildren<LibraryBlockMetadata>(containerId, showOnlyPublished);
const childrenUsageIds = children?.map((child) => child.id);
const hasDuplicates = (childrenUsageIds?.filter((child) => child === usageKey).length || 0) > 1;

// istanbul ignore if: loading state
if (isPending || isPendingParentContainer) {
// Only show the modal when all data is ready
return null;
}

const restoreComponent = () => {
// istanbul ignore if: this should never happen
if (!childrenUsageIds) {
return;
}
updateContainerChildrenMutation.mutateAsync(childrenUsageIds).then(() => {
showToast(intl.formatMessage(messages.undoRemoveComponentFromContainerToastSuccess));
}).catch(() => {
showToast(intl.formatMessage(messages.undoRemoveComponentFromContainerToastFailed));
});
};

const showSuccessToast = () => {
showToast(
intl.formatMessage(messages.removeComponentFromContainerSuccess),
{
label: intl.formatMessage(messages.undoRemoveComponentFromContainerToastAction),
onClick: restoreComponent,
},
);
};

const showFailureToast = () => showToast(intl.formatMessage(messages.removeComponentFromContainerFailure));

const removeFromContainer = () => {
const restoreComponent = () => {
addItemToContainerMutation.mutateAsync([usageKey]).then(() => {
showToast(intl.formatMessage(messages.undoRemoveComponentFromContainerToastSuccess));
}).catch(() => {
showToast(intl.formatMessage(messages.undoRemoveComponentFromContainerToastFailed));
});
};
removeContainerItemMutation.mutateAsync([usageKey]).then(() => {
if (sidebarItemInfo?.id === usageKey) {
// Close sidebar if current component is open
closeLibrarySidebar();
}
showToast(
intl.formatMessage(messages.removeComponentFromContainerSuccess),
{
label: intl.formatMessage(messages.undoRemoveComponentFromContainerToastAction),
onClick: restoreComponent,
},
);
showSuccessToast();
}).catch(() => {
showFailureToast();
});

close();
};

const excludeOneInstance = () => {
if (!childrenUsageIds || typeof index === 'undefined') {
return;
}
const updatedKeys = childrenUsageIds.filter((childId, idx) => childId !== usageKey || idx !== index);
updateContainerChildrenMutation.mutateAsync(updatedKeys).then(() => {
// istanbul ignore if
if (sidebarItemInfo?.id === usageKey && sidebarItemInfo?.index === index) {
// Close sidebar if current component is open
closeLibrarySidebar();
}
// Already tested as part of removeFromContainer
// istanbul ignore next
showSuccessToast();
}).catch(() => {
showToast(intl.formatMessage(messages.removeComponentFromContainerFailure));
// Already tested as part of removeFromContainer
// istanbul ignore next
showFailureToast();
});

close();
Expand All @@ -76,7 +117,7 @@ const ComponentRemover = ({ usageKey, close }: Props) => {
title={intl.formatMessage(messages.removeComponentWarningTitle)}
icon={Warning}
description={removeText}
onDeleteSubmit={removeFromContainer}
onDeleteSubmit={hasDuplicates ? excludeOneInstance : removeFromContainer}
btnLabel={intl.formatMessage(messages.componentRemoveButtonLabel)}
buttonVariant="primary"
/>
Expand Down
18 changes: 10 additions & 8 deletions src/library-authoring/containers/ContainerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,24 @@ import { type ContainerHit, Highlight, PublishStatus } from '@src/search-manager
import { ToastContext } from '@src/generic/toast-context';
import { useRunOnNextRender } from '@src/utils';

import { useComponentPickerContext } from '../common/context/ComponentPickerContext';
import { useLibraryContext } from '../common/context/LibraryContext';
import { SidebarActions, SidebarBodyItemId, useSidebarContext } from '../common/context/SidebarContext';
import { useRemoveItemsFromCollection } from '../data/apiHooks';
import { useLibraryRoutes } from '../routes';
import { useComponentPickerContext } from '@src/library-authoring/common/context/ComponentPickerContext';
import { useLibraryContext } from '@src/library-authoring/common/context/LibraryContext';
import { SidebarActions, SidebarBodyItemId, useSidebarContext } from '@src/library-authoring/common/context/SidebarContext';
import { useRemoveItemsFromCollection } from '@src/library-authoring/data/apiHooks';
import { useLibraryRoutes } from '@src/library-authoring/routes';
import BaseCard from '@src/library-authoring/components/BaseCard';
import AddComponentWidget from '@src/library-authoring/components/AddComponentWidget';
import messages from './messages';
import ContainerDeleter from './ContainerDeleter';
import ContainerRemover from './ContainerRemover';
import BaseCard from '../components/BaseCard';
import AddComponentWidget from '../components/AddComponentWidget';

type ContainerMenuProps = {
containerKey: string;
displayName: string;
index?: number;
};

export const ContainerMenu = ({ containerKey, displayName } : ContainerMenuProps) => {
export const ContainerMenu = ({ containerKey, displayName, index } : ContainerMenuProps) => {
const intl = useIntl();
const { libraryId, collectionId, containerId } = useLibraryContext();
const {
Expand Down Expand Up @@ -144,6 +145,7 @@ export const ContainerMenu = ({ containerKey, displayName } : ContainerMenuProps
close={cancelRemove}
containerKey={containerKey}
displayName={displayName}
index={index}
/>
)}
</>
Expand Down
Loading