diff --git a/src/library-authoring/LibraryAuthoringPage.test.tsx b/src/library-authoring/LibraryAuthoringPage.test.tsx
index 981a8af60c..70853f18b6 100644
--- a/src/library-authoring/LibraryAuthoringPage.test.tsx
+++ b/src/library-authoring/LibraryAuthoringPage.test.tsx
@@ -89,11 +89,12 @@ describe('', () => {
// We have to replace the query (search keywords) in the mock results with the actual query,
// because otherwise Instantsearch will update the UI and change the query,
// leading to unexpected results in the test cases.
- mockResult.results[0].query = query;
+ const newMockResult = { ...mockResult };
+ newMockResult.results[0].query = query;
// And fake the required '_formatted' fields; it contains the highlighting ... around matched words
// eslint-disable-next-line no-underscore-dangle, no-param-reassign
- mockResult.results[0]?.hits.forEach((hit) => { hit._formatted = { ...hit }; });
- return mockResult;
+ newMockResult.results[0]?.hits.forEach((hit) => { hit._formatted = { ...hit }; });
+ return newMockResult;
});
});
@@ -458,7 +459,7 @@ describe('', () => {
});
it('should open and close the component sidebar', async () => {
- const mockResult0 = mockResult.results[0].hits[0];
+ const mockResult0 = { ...mockResult }.results[0].hits[0];
const displayName = 'Introduction to Testing';
expect(mockResult0.display_name).toStrictEqual(displayName);
await renderLibraryPage();
@@ -478,6 +479,25 @@ describe('', () => {
await waitFor(() => expect(screen.queryByTestId('library-sidebar')).not.toBeInTheDocument());
});
+ it('should open component sidebar, showing manage tab on clicking add to collection menu item', async () => {
+ const mockResult0 = { ...mockResult }.results[0].hits[0];
+ const displayName = 'Introduction to Testing';
+ expect(mockResult0.display_name).toStrictEqual(displayName);
+ await renderLibraryPage();
+
+ // Open menu
+ fireEvent.click(screen.getAllByTestId('component-card-menu-toggle')[0]);
+ // Click add to collection
+ fireEvent.click(screen.getByRole('button', { name: 'Add to collection' }));
+
+ const sidebar = screen.getByTestId('library-sidebar');
+
+ const { getByRole, queryByText } = within(sidebar);
+
+ await waitFor(() => expect(queryByText(displayName)).toBeInTheDocument());
+ expect(getByRole('tab', { selected: true })).toHaveTextContent('Manage');
+ });
+
it('should open and close the collection sidebar', async () => {
await renderLibraryPage();
diff --git a/src/library-authoring/LibraryAuthoringPage.tsx b/src/library-authoring/LibraryAuthoringPage.tsx
index 786c976196..e546dff26b 100644
--- a/src/library-authoring/LibraryAuthoringPage.tsx
+++ b/src/library-authoring/LibraryAuthoringPage.tsx
@@ -69,12 +69,12 @@ const HeaderActions = () => {
openAddContentSidebar,
openInfoSidebar,
closeLibrarySidebar,
- sidebarBodyComponent,
+ sidebarComponentInfo,
readOnly,
} = useLibraryContext();
const infoSidebarIsOpen = () => (
- sidebarBodyComponent === SidebarBodyComponentId.Info
+ sidebarComponentInfo?.type === SidebarBodyComponentId.Info
);
const handleOnClickInfoSidebar = () => {
@@ -148,7 +148,7 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
libraryData,
isLoadingLibraryData,
componentPickerMode,
- sidebarBodyComponent,
+ sidebarComponentInfo,
openInfoSidebar,
} = useLibraryContext();
@@ -261,7 +261,7 @@ const LibraryAuthoringPage = ({ returnToLibrarySelection }: LibraryAuthoringPage
{!componentPickerMode && }
- {!!sidebarBodyComponent && (
+ {!!sidebarComponentInfo?.type && (
diff --git a/src/library-authoring/collections/CollectionDetails.test.tsx b/src/library-authoring/collections/CollectionDetails.test.tsx
index 5ae9a2e72b..e2651a1395 100644
--- a/src/library-authoring/collections/CollectionDetails.test.tsx
+++ b/src/library-authoring/collections/CollectionDetails.test.tsx
@@ -10,7 +10,7 @@ import {
waitFor,
within,
} from '../../testUtils';
-import { LibraryProvider } from '../common/context';
+import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
import * as api from '../data/api';
import { mockContentLibrary, mockGetCollectionMetadata } from '../data/api.mocks';
import CollectionDetails from './CollectionDetails';
@@ -30,7 +30,13 @@ const library = mockContentLibrary.libraryData;
const render = () => baseRender(, {
extraWrapper: ({ children }) => (
-
+
{ children }
),
diff --git a/src/library-authoring/collections/CollectionDetails.tsx b/src/library-authoring/collections/CollectionDetails.tsx
index 73f9f69593..07a883dea5 100644
--- a/src/library-authoring/collections/CollectionDetails.tsx
+++ b/src/library-authoring/collections/CollectionDetails.tsx
@@ -37,7 +37,8 @@ const BlockCount = ({
};
const CollectionStatsWidget = () => {
- const { libraryId, sidebarCollectionId: collectionId } = useLibraryContext();
+ const { libraryId, sidebarComponentInfo } = useLibraryContext();
+ const collectionId = sidebarComponentInfo?.id;
const { data: blockTypes } = useGetBlockTypes([
`context_key = "${libraryId}"`,
@@ -98,10 +99,11 @@ const CollectionDetails = () => {
const { showToast } = useContext(ToastContext);
const {
libraryId,
- sidebarCollectionId: collectionId,
+ sidebarComponentInfo,
readOnly,
} = useLibraryContext();
+ const collectionId = sidebarComponentInfo?.id;
// istanbul ignore next: This should never happen
if (!collectionId) {
throw new Error('collectionId is required');
diff --git a/src/library-authoring/collections/CollectionInfo.tsx b/src/library-authoring/collections/CollectionInfo.tsx
index b548c6b2a3..a3098dc178 100644
--- a/src/library-authoring/collections/CollectionInfo.tsx
+++ b/src/library-authoring/collections/CollectionInfo.tsx
@@ -22,20 +22,21 @@ const CollectionInfo = () => {
libraryId,
collectionId,
setCollectionId,
- sidebarCollectionId,
+ sidebarComponentInfo,
componentPickerMode,
} = useLibraryContext();
- const url = `/library/${libraryId}/collection/${sidebarCollectionId}/`;
- const urlMatch = useMatch(url);
-
- const showOpenCollectionButton = !urlMatch && collectionId !== sidebarCollectionId;
-
+ const sidebarCollectionId = sidebarComponentInfo?.id;
// istanbul ignore if: this should never happen
if (!sidebarCollectionId) {
throw new Error('sidebarCollectionId is required');
}
+ const url = `/library/${libraryId}/collection/${sidebarCollectionId}/`;
+ const urlMatch = useMatch(url);
+
+ const showOpenCollectionButton = !urlMatch && collectionId !== sidebarCollectionId;
+
const collectionUsageKey = buildCollectionUsageKey(libraryId, sidebarCollectionId);
const handleOpenCollection = useCallback(() => {
diff --git a/src/library-authoring/collections/CollectionInfoHeader.test.tsx b/src/library-authoring/collections/CollectionInfoHeader.test.tsx
index 1272e594dd..7e1b7af374 100644
--- a/src/library-authoring/collections/CollectionInfoHeader.test.tsx
+++ b/src/library-authoring/collections/CollectionInfoHeader.test.tsx
@@ -8,7 +8,7 @@ import {
screen,
waitFor,
} from '../../testUtils';
-import { LibraryProvider } from '../common/context';
+import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
import { mockContentLibrary, mockGetCollectionMetadata } from '../data/api.mocks';
import * as api from '../data/api';
import CollectionInfoHeader from './CollectionInfoHeader';
@@ -28,7 +28,13 @@ const { collectionId } = mockGetCollectionMetadata;
const render = (libraryId: string = mockLibraryId) => baseRender(, {
extraWrapper: ({ children }) => (
-
+
{ children }
),
diff --git a/src/library-authoring/collections/CollectionInfoHeader.tsx b/src/library-authoring/collections/CollectionInfoHeader.tsx
index 58db6c0e32..83be4c2139 100644
--- a/src/library-authoring/collections/CollectionInfoHeader.tsx
+++ b/src/library-authoring/collections/CollectionInfoHeader.tsx
@@ -19,10 +19,11 @@ const CollectionInfoHeader = () => {
const {
libraryId,
- sidebarCollectionId: collectionId,
+ sidebarComponentInfo,
readOnly,
} = useLibraryContext();
+ const collectionId = sidebarComponentInfo?.id;
// istanbul ignore if: this should never happen
if (!collectionId) {
throw new Error('collectionId is required');
diff --git a/src/library-authoring/collections/LibraryCollectionPage.tsx b/src/library-authoring/collections/LibraryCollectionPage.tsx
index 8caac5a8c6..22692c5b5d 100644
--- a/src/library-authoring/collections/LibraryCollectionPage.tsx
+++ b/src/library-authoring/collections/LibraryCollectionPage.tsx
@@ -104,7 +104,7 @@ const LibraryCollectionPage = () => {
}
const {
- sidebarBodyComponent,
+ sidebarComponentInfo,
openCollectionInfoSidebar,
componentPickerMode,
setCollectionId,
@@ -215,7 +215,7 @@ const LibraryCollectionPage = () => {
- {!!sidebarBodyComponent && (
+ {!!sidebarComponentInfo?.type && (
diff --git a/src/library-authoring/common/context.tsx b/src/library-authoring/common/context.tsx
index cc085ef77c..d564886209 100644
--- a/src/library-authoring/common/context.tsx
+++ b/src/library-authoring/common/context.tsx
@@ -16,6 +16,17 @@ export enum SidebarBodyComponentId {
CollectionInfo = 'collection-info',
}
+export enum SidebarAdditionalActions {
+ JumpToAddCollections = 'jump-to-add-collections',
+}
+
+export interface SidebarComponentInfo {
+ type: SidebarBodyComponentId;
+ id: string;
+ /** Additional action on Sidebar display */
+ additionalAction?: SidebarAdditionalActions;
+}
+
export interface LibraryContextData {
/** The ID of the current library */
libraryId: string;
@@ -27,12 +38,11 @@ export interface LibraryContextData {
// Whether we're in "component picker" mode
componentPickerMode: boolean;
// Sidebar stuff - only one sidebar is active at any given time:
- sidebarBodyComponent: SidebarBodyComponentId | null;
closeLibrarySidebar: () => void;
openAddContentSidebar: () => void;
openInfoSidebar: () => void;
- openComponentInfoSidebar: (usageKey: string) => void;
- sidebarComponentUsageKey?: string;
+ openComponentInfoSidebar: (usageKey: string, additionalAction?: SidebarAdditionalActions) => void;
+ sidebarComponentInfo?: SidebarComponentInfo;
// "Library Team" modal
isLibraryTeamModalOpen: boolean;
openLibraryTeamModal: () => void;
@@ -42,13 +52,13 @@ export interface LibraryContextData {
openCreateCollectionModal: () => void;
closeCreateCollectionModal: () => void;
// Current collection
- openCollectionInfoSidebar: (collectionId: string) => void;
- sidebarCollectionId?: string;
+ openCollectionInfoSidebar: (collectionId: string, additionalAction?: SidebarAdditionalActions) => void;
// Editor modal - for editing some component
/** If the editor is open and the user is editing some component, this is its usageKey */
componentBeingEdited: string | undefined;
openComponentEditor: (usageKey: string) => void;
closeComponentEditor: () => void;
+ resetSidebarAdditionalActions: () => void;
}
/**
@@ -70,9 +80,7 @@ interface LibraryProviderProps {
* XBlock) */
componentPickerMode?: boolean;
/** Only used for testing */
- initialSidebarComponentUsageKey?: string;
- /** Only used for testing */
- initialSidebarCollectionId?: string;
+ initialSidebarComponentInfo?: SidebarComponentInfo;
}
/**
@@ -83,49 +91,49 @@ export const LibraryProvider = ({
libraryId,
collectionId: collectionIdProp,
componentPickerMode = false,
- initialSidebarComponentUsageKey,
- initialSidebarCollectionId,
+ initialSidebarComponentInfo,
}: LibraryProviderProps) => {
const [collectionId, setCollectionId] = useState(collectionIdProp);
- const [sidebarBodyComponent, setSidebarBodyComponent] = useState(null);
- const [sidebarComponentUsageKey, setSidebarComponentUsageKey] = useState(
- initialSidebarComponentUsageKey,
+ const [sidebarComponentInfo, setSidebarComponentInfo] = useState(
+ initialSidebarComponentInfo,
);
- const [sidebarCollectionId, setSidebarCollectionId] = useState(initialSidebarCollectionId);
const [isLibraryTeamModalOpen, openLibraryTeamModal, closeLibraryTeamModal] = useToggle(false);
const [isCreateCollectionModalOpen, openCreateCollectionModal, closeCreateCollectionModal] = useToggle(false);
const [componentBeingEdited, openComponentEditor] = useState();
const closeComponentEditor = useCallback(() => openComponentEditor(undefined), []);
- const resetSidebar = useCallback(() => {
- setSidebarComponentUsageKey(undefined);
- setSidebarCollectionId(undefined);
- setSidebarBodyComponent(null);
+ /** Helper function to consume addtional action once performed.
+ Required to redo the action.
+ */
+ const resetSidebarAdditionalActions = useCallback(() => {
+ setSidebarComponentInfo((prev) => (prev && { ...prev, additionalAction: undefined }));
}, []);
const closeLibrarySidebar = useCallback(() => {
- resetSidebar();
+ setSidebarComponentInfo(undefined);
}, []);
const openAddContentSidebar = useCallback(() => {
- resetSidebar();
- setSidebarBodyComponent(SidebarBodyComponentId.AddContent);
+ setSidebarComponentInfo({ id: '', type: SidebarBodyComponentId.AddContent });
}, []);
const openInfoSidebar = useCallback(() => {
- resetSidebar();
- setSidebarBodyComponent(SidebarBodyComponentId.Info);
+ setSidebarComponentInfo({ id: '', type: SidebarBodyComponentId.Info });
}, []);
- const openComponentInfoSidebar = useCallback(
- (usageKey: string) => {
- resetSidebar();
- setSidebarComponentUsageKey(usageKey);
- setSidebarBodyComponent(SidebarBodyComponentId.ComponentInfo);
- },
- [],
- );
- const openCollectionInfoSidebar = useCallback((newCollectionId: string) => {
- resetSidebar();
- setSidebarCollectionId(newCollectionId);
- setSidebarBodyComponent(SidebarBodyComponentId.CollectionInfo);
+ const openComponentInfoSidebar = useCallback((usageKey: string, additionalAction?: SidebarAdditionalActions) => {
+ setSidebarComponentInfo({
+ id: usageKey,
+ type: SidebarBodyComponentId.ComponentInfo,
+ additionalAction,
+ });
+ }, []);
+ const openCollectionInfoSidebar = useCallback((
+ newCollectionId: string,
+ additionalAction?: SidebarAdditionalActions,
+ ) => {
+ setSidebarComponentInfo({
+ id: newCollectionId,
+ type: SidebarBodyComponentId.CollectionInfo,
+ additionalAction,
+ });
}, []);
const { data: libraryData, isLoading: isLoadingLibraryData } = useContentLibrary(libraryId);
@@ -140,12 +148,11 @@ export const LibraryProvider = ({
readOnly,
isLoadingLibraryData,
componentPickerMode,
- sidebarBodyComponent,
closeLibrarySidebar,
openAddContentSidebar,
openInfoSidebar,
openComponentInfoSidebar,
- sidebarComponentUsageKey,
+ sidebarComponentInfo,
isLibraryTeamModalOpen,
openLibraryTeamModal,
closeLibraryTeamModal,
@@ -153,10 +160,10 @@ export const LibraryProvider = ({
openCreateCollectionModal,
closeCreateCollectionModal,
openCollectionInfoSidebar,
- sidebarCollectionId,
componentBeingEdited,
openComponentEditor,
closeComponentEditor,
+ resetSidebarAdditionalActions,
}), [
libraryId,
collectionId,
@@ -165,12 +172,11 @@ export const LibraryProvider = ({
readOnly,
isLoadingLibraryData,
componentPickerMode,
- sidebarBodyComponent,
closeLibrarySidebar,
openAddContentSidebar,
openInfoSidebar,
openComponentInfoSidebar,
- sidebarComponentUsageKey,
+ sidebarComponentInfo,
isLibraryTeamModalOpen,
openLibraryTeamModal,
closeLibraryTeamModal,
@@ -178,10 +184,10 @@ export const LibraryProvider = ({
openCreateCollectionModal,
closeCreateCollectionModal,
openCollectionInfoSidebar,
- sidebarCollectionId,
componentBeingEdited,
openComponentEditor,
closeComponentEditor,
+ resetSidebarAdditionalActions,
]);
return (
diff --git a/src/library-authoring/component-info/ComponentAdvancedInfo.test.tsx b/src/library-authoring/component-info/ComponentAdvancedInfo.test.tsx
index 7c52d1f3a3..773a2faaff 100644
--- a/src/library-authoring/component-info/ComponentAdvancedInfo.test.tsx
+++ b/src/library-authoring/component-info/ComponentAdvancedInfo.test.tsx
@@ -12,7 +12,7 @@ import {
mockXBlockAssets,
mockXBlockOLX,
} from '../data/api.mocks';
-import { LibraryProvider } from '../common/context';
+import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
import { ComponentAdvancedInfo } from './ComponentAdvancedInfo';
mockContentLibrary.applyMock();
@@ -28,7 +28,13 @@ const render = (
,
{
extraWrapper: ({ children }: { children: React.ReactNode }) => (
-
+
{children}
),
diff --git a/src/library-authoring/component-info/ComponentAdvancedInfo.tsx b/src/library-authoring/component-info/ComponentAdvancedInfo.tsx
index 6fac7da4d5..4039fb44e5 100644
--- a/src/library-authoring/component-info/ComponentAdvancedInfo.tsx
+++ b/src/library-authoring/component-info/ComponentAdvancedInfo.tsx
@@ -22,8 +22,9 @@ import messages from './messages';
const ComponentAdvancedInfoInner: React.FC> = () => {
const intl = useIntl();
- const { readOnly, sidebarComponentUsageKey: usageKey } = useLibraryContext();
+ const { readOnly, sidebarComponentInfo } = useLibraryContext();
+ const usageKey = sidebarComponentInfo?.id;
// istanbul ignore if: this should never happen in production
if (!usageKey) {
throw new Error('sidebarComponentUsageKey is required to render ComponentAdvancedInfo');
diff --git a/src/library-authoring/component-info/ComponentDetails.test.tsx b/src/library-authoring/component-info/ComponentDetails.test.tsx
index b4a6a307ee..514e66bdf8 100644
--- a/src/library-authoring/component-info/ComponentDetails.test.tsx
+++ b/src/library-authoring/component-info/ComponentDetails.test.tsx
@@ -9,7 +9,7 @@ import {
mockXBlockAssets,
mockXBlockOLX,
} from '../data/api.mocks';
-import { LibraryProvider } from '../common/context';
+import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
import ComponentDetails from './ComponentDetails';
mockContentLibrary.applyMock();
@@ -21,7 +21,13 @@ const { libraryId: mockLibraryId } = mockContentLibrary;
const render = (usageKey: string) => baseRender(, {
extraWrapper: ({ children }) => (
-
+
{children}
),
diff --git a/src/library-authoring/component-info/ComponentDetails.tsx b/src/library-authoring/component-info/ComponentDetails.tsx
index 52834d1a7f..0bac9d3234 100644
--- a/src/library-authoring/component-info/ComponentDetails.tsx
+++ b/src/library-authoring/component-info/ComponentDetails.tsx
@@ -10,7 +10,9 @@ import { ComponentAdvancedInfo } from './ComponentAdvancedInfo';
import messages from './messages';
const ComponentDetails = () => {
- const { sidebarComponentUsageKey: usageKey } = useLibraryContext();
+ const { sidebarComponentInfo } = useLibraryContext();
+
+ const usageKey = sidebarComponentInfo?.id;
// istanbul ignore if: this should never happen
if (!usageKey) {
diff --git a/src/library-authoring/component-info/ComponentInfo.test.tsx b/src/library-authoring/component-info/ComponentInfo.test.tsx
index 5fbad3c474..f0f094fedd 100644
--- a/src/library-authoring/component-info/ComponentInfo.test.tsx
+++ b/src/library-authoring/component-info/ComponentInfo.test.tsx
@@ -6,7 +6,7 @@ import {
} from '../../testUtils';
import { mockContentLibrary, mockLibraryBlockMetadata } from '../data/api.mocks';
import { mockBroadcastChannel } from '../../generic/data/api.mock';
-import { LibraryProvider } from '../common/context';
+import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
import ComponentInfo from './ComponentInfo';
mockBroadcastChannel();
@@ -25,7 +25,10 @@ const withLibraryId = (libraryId: string, sidebarComponentUsageKey: string) => (
extraWrapper: ({ children }: { children: React.ReactNode }) => (
{children}
diff --git a/src/library-authoring/component-info/ComponentInfo.tsx b/src/library-authoring/component-info/ComponentInfo.tsx
index 4cbb35598f..74268936c2 100644
--- a/src/library-authoring/component-info/ComponentInfo.tsx
+++ b/src/library-authoring/component-info/ComponentInfo.tsx
@@ -1,3 +1,4 @@
+import { useEffect, useState } from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
Button,
@@ -6,7 +7,7 @@ import {
Stack,
} from '@openedx/paragon';
-import { useLibraryContext } from '../common/context';
+import { SidebarAdditionalActions, useLibraryContext } from '../common/context';
import { ComponentMenu } from '../components';
import { canEditComponent } from '../components/ComponentEditorModal';
import ComponentDetails from './ComponentDetails';
@@ -19,12 +20,30 @@ const ComponentInfo = () => {
const intl = useIntl();
const {
- sidebarComponentUsageKey: usageKey,
+ sidebarComponentInfo,
readOnly,
openComponentEditor,
componentPickerMode,
+ resetSidebarAdditionalActions,
} = useLibraryContext();
+ const jumpToCollections = sidebarComponentInfo?.additionalAction === SidebarAdditionalActions.JumpToAddCollections;
+ // Show Manage tab if JumpToAddCollections action is set in sidebarComponentInfo
+ const [tab, setTab] = useState(jumpToCollections ? 'manage' : 'preview');
+ useEffect(() => {
+ if (jumpToCollections) {
+ setTab('manage');
+ }
+ }, [jumpToCollections]);
+
+ useEffect(() => {
+ // This is required to redo actions.
+ if (tab !== 'manage') {
+ resetSidebarAdditionalActions();
+ }
+ }, [tab]);
+
+ const usageKey = sidebarComponentInfo?.id;
// istanbul ignore if: this should never happen
if (!usageKey) {
throw new Error('usageKey is required');
@@ -65,7 +84,8 @@ const ComponentInfo = () => {
setTab(k)}
>
diff --git a/src/library-authoring/component-info/ComponentInfoHeader.test.tsx b/src/library-authoring/component-info/ComponentInfoHeader.test.tsx
index de32c80158..fe55839859 100644
--- a/src/library-authoring/component-info/ComponentInfoHeader.test.tsx
+++ b/src/library-authoring/component-info/ComponentInfoHeader.test.tsx
@@ -9,7 +9,7 @@ import {
} from '../../testUtils';
import { mockContentLibrary } from '../data/api.mocks';
import { getXBlockFieldsApiUrl } from '../data/api';
-import { LibraryProvider } from '../common/context';
+import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
import ComponentInfoHeader from './ComponentInfoHeader';
const { libraryId: mockLibraryId, libraryIdReadOnly } = mockContentLibrary;
@@ -24,7 +24,13 @@ const xBlockFields = {
const render = (libraryId: string = mockLibraryId) => baseRender(, {
extraWrapper: ({ children }) => (
-
+
{children}
),
diff --git a/src/library-authoring/component-info/ComponentInfoHeader.tsx b/src/library-authoring/component-info/ComponentInfoHeader.tsx
index 0456aa3d1c..5c27071255 100644
--- a/src/library-authoring/component-info/ComponentInfoHeader.tsx
+++ b/src/library-authoring/component-info/ComponentInfoHeader.tsx
@@ -18,10 +18,11 @@ const ComponentInfoHeader = () => {
const [inputIsActive, setIsActive] = useState(false);
const {
- sidebarComponentUsageKey: usageKey,
+ sidebarComponentInfo,
readOnly,
} = useLibraryContext();
+ const usageKey = sidebarComponentInfo?.id;
// istanbul ignore next
if (!usageKey) {
throw new Error('usageKey is required');
diff --git a/src/library-authoring/component-info/ComponentManagement.test.tsx b/src/library-authoring/component-info/ComponentManagement.test.tsx
index ac081ebad6..93a5872df2 100644
--- a/src/library-authoring/component-info/ComponentManagement.test.tsx
+++ b/src/library-authoring/component-info/ComponentManagement.test.tsx
@@ -7,7 +7,7 @@ import {
screen,
waitFor,
} from '../../testUtils';
-import { LibraryProvider } from '../common/context';
+import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
import { mockContentLibrary, mockLibraryBlockMetadata } from '../data/api.mocks';
import ComponentManagement from './ComponentManagement';
@@ -37,7 +37,13 @@ const matchInnerText = (nodeName: string, textToMatch: string) => (_: string, el
const render = (usageKey: string, libraryId?: string) => baseRender(, {
extraWrapper: ({ children }) => (
-
+
{children}
),
diff --git a/src/library-authoring/component-info/ComponentManagement.tsx b/src/library-authoring/component-info/ComponentManagement.tsx
index 18de562704..0cfb1c14c9 100644
--- a/src/library-authoring/component-info/ComponentManagement.tsx
+++ b/src/library-authoring/component-info/ComponentManagement.tsx
@@ -1,10 +1,12 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Collapsible, Icon, Stack } from '@openedx/paragon';
-import { BookOpen, Tag } from '@openedx/paragon/icons';
+import {
+ BookOpen, ExpandLess, ExpandMore, Tag,
+} from '@openedx/paragon/icons';
-import { useLibraryContext } from '../common/context';
+import { SidebarAdditionalActions, useLibraryContext } from '../common/context';
import { useLibraryBlockMetadata } from '../data/apiHooks';
import StatusWidget from '../generic/status-widget';
import messages from './messages';
@@ -14,8 +16,28 @@ import ManageCollections from './ManageCollections';
const ComponentManagement = () => {
const intl = useIntl();
- const { sidebarComponentUsageKey: usageKey, readOnly, isLoadingLibraryData } = useLibraryContext();
+ const {
+ sidebarComponentInfo, readOnly, resetSidebarAdditionalActions, isLoadingLibraryData,
+ } = useLibraryContext();
+ const jumpToCollections = sidebarComponentInfo?.additionalAction === SidebarAdditionalActions.JumpToAddCollections;
+ const [tagsCollapseIsOpen, setTagsCollapseOpen] = React.useState(!jumpToCollections);
+ const [collectionsCollapseIsOpen, setCollectionsCollapseOpen] = React.useState(true);
+ useEffect(() => {
+ if (jumpToCollections) {
+ setTagsCollapseOpen(false);
+ setCollectionsCollapseOpen(true);
+ }
+ }, [jumpToCollections, tagsCollapseIsOpen, collectionsCollapseIsOpen]);
+
+ useEffect(() => {
+ // This is required to redo actions.
+ if (tagsCollapseIsOpen || !collectionsCollapseIsOpen) {
+ resetSidebarAdditionalActions();
+ }
+ }, [tagsCollapseIsOpen, collectionsCollapseIsOpen]);
+
+ const usageKey = sidebarComponentInfo?.id;
// istanbul ignore if: this should never happen
if (!usageKey) {
throw new Error('usageKey is required');
@@ -61,35 +83,57 @@ const ComponentManagement = () => {
/>
{[true, 'true'].includes(getConfig().ENABLE_TAGGING_TAXONOMY_PAGES)
&& (
-
-
- {intl.formatMessage(messages.manageTabTagsTitle, { count: tagsCount })}
-
- )}
- className="border-0"
- >
-
-
+
+ setTagsCollapseOpen((prev) => !prev)}
+ className="collapsible-trigger d-flex justify-content-between p-2"
+ >
+
+
+ {intl.formatMessage(messages.manageTabTagsTitle, { count: tagsCount })}
+
+
+
+
+
+
+
+
+
+
+
+
)}
-
+ setCollectionsCollapseOpen((prev) => !prev)}
+ className="collapsible-trigger d-flex justify-content-between p-2"
+ >
{intl.formatMessage(messages.manageTabCollectionsTitle, { count: collectionsCount })}
- )}
- className="border-0"
- >
-
-
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/src/library-authoring/component-info/ComponentPreview.test.tsx b/src/library-authoring/component-info/ComponentPreview.test.tsx
index fea05e65e6..cf71dab382 100644
--- a/src/library-authoring/component-info/ComponentPreview.test.tsx
+++ b/src/library-authoring/component-info/ComponentPreview.test.tsx
@@ -4,7 +4,7 @@ import {
render as baseRender,
screen,
} from '../../testUtils';
-import { LibraryProvider } from '../common/context';
+import { LibraryProvider, SidebarBodyComponentId } from '../common/context';
import { mockContentLibrary, mockLibraryBlockMetadata } from '../data/api.mocks';
import ComponentPreview from './ComponentPreview';
@@ -21,7 +21,10 @@ const render = () => baseRender(, {
extraWrapper: ({ children }) => (
{ children }
diff --git a/src/library-authoring/component-info/ComponentPreview.tsx b/src/library-authoring/component-info/ComponentPreview.tsx
index 69eb597d7d..a7448f3b8f 100644
--- a/src/library-authoring/component-info/ComponentPreview.tsx
+++ b/src/library-authoring/component-info/ComponentPreview.tsx
@@ -33,8 +33,9 @@ const ComponentPreview = () => {
const intl = useIntl();
const [isModalOpen, openModal, closeModal] = useToggle();
- const { sidebarComponentUsageKey: usageKey } = useLibraryContext();
+ const { sidebarComponentInfo } = useLibraryContext();
+ const usageKey = sidebarComponentInfo?.id;
// istanbul ignore if: this should never happen
if (!usageKey) {
throw new Error('usageKey is required');
diff --git a/src/library-authoring/component-info/ManageCollections.tsx b/src/library-authoring/component-info/ManageCollections.tsx
index 22cc151cbf..10b1643cb0 100644
--- a/src/library-authoring/component-info/ManageCollections.tsx
+++ b/src/library-authoring/component-info/ManageCollections.tsx
@@ -1,4 +1,4 @@
-import { useContext, useState } from 'react';
+import { useContext, useEffect, useState } from 'react';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import {
Button, Icon, Scrollable, SelectableBox, Stack, StatefulButton, useCheckboxSetValues,
@@ -15,7 +15,7 @@ import messages from './messages';
import { useUpdateComponentCollections } from '../data/apiHooks';
import { ToastContext } from '../../generic/toast-context';
import { CollectionMetadata } from '../data/api';
-import { useLibraryContext } from '../common/context';
+import { SidebarAdditionalActions, useLibraryContext } from '../common/context';
interface ManageCollectionsProps {
usageKey: string;
@@ -193,9 +193,24 @@ const ComponentCollections = ({ collections, onManageClick }: {
};
const ManageCollections = ({ usageKey, collections }: ManageCollectionsProps) => {
- const [editing, setEditing] = useState(false);
+ const { sidebarComponentInfo, resetSidebarAdditionalActions } = useLibraryContext();
+ const jumpToCollections = sidebarComponentInfo?.additionalAction === SidebarAdditionalActions.JumpToAddCollections;
+ const [editing, setEditing] = useState(jumpToCollections);
const collectionNames = collections.map((collection) => collection.title);
+ useEffect(() => {
+ if (jumpToCollections) {
+ setEditing(true);
+ }
+ }, [sidebarComponentInfo]);
+
+ useEffect(() => {
+ // This is required to redo actions.
+ if (!editing) {
+ resetSidebarAdditionalActions();
+ }
+ }, [editing]);
+
if (editing) {
return (
', () => {
initializeMocks();
postMessageSpy = jest.spyOn(window.parent, 'postMessage');
- mockSearchResult(mockResult);
+ mockSearchResult({ ...mockResult });
});
it('should pick component using the component card button', async () => {
diff --git a/src/library-authoring/components/CollectionCard.tsx b/src/library-authoring/components/CollectionCard.tsx
index 8181b35e24..3908321449 100644
--- a/src/library-authoring/components/CollectionCard.tsx
+++ b/src/library-authoring/components/CollectionCard.tsx
@@ -27,7 +27,7 @@ const CollectionMenu = ({ collectionHit } : CollectionMenuProps) => {
const { showToast } = useContext(ToastContext);
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false);
const [confirmBtnState, setConfirmBtnState] = useState('default');
- const { closeLibrarySidebar, sidebarCollectionId } = useLibraryContext();
+ const { closeLibrarySidebar, sidebarComponentInfo } = useLibraryContext();
const restoreCollectionMutation = useRestoreCollection(collectionHit.contextKey, collectionHit.blockId);
const restoreCollection = useCallback(() => {
@@ -42,7 +42,7 @@ const CollectionMenu = ({ collectionHit } : CollectionMenuProps) => {
const deleteCollectionMutation = useDeleteCollection(collectionHit.contextKey, collectionHit.blockId);
const deleteCollection = useCallback(() => {
setConfirmBtnState('pending');
- if (sidebarCollectionId === collectionHit.blockId) {
+ if (sidebarComponentInfo?.id === collectionHit.blockId) {
// Close sidebar if current collection is open to avoid displaying
// deleted collection in sidebar
closeLibrarySidebar();
@@ -62,7 +62,7 @@ const CollectionMenu = ({ collectionHit } : CollectionMenuProps) => {
setConfirmBtnState('default');
closeDeleteModal();
});
- }, [sidebarCollectionId]);
+ }, [sidebarComponentInfo?.id]);
return (
<>
diff --git a/src/library-authoring/components/ComponentCard.tsx b/src/library-authoring/components/ComponentCard.tsx
index 03b6b3aaa9..2d61b858cc 100644
--- a/src/library-authoring/components/ComponentCard.tsx
+++ b/src/library-authoring/components/ComponentCard.tsx
@@ -14,7 +14,7 @@ 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 { SidebarAdditionalActions, useLibraryContext } from '../common/context';
import { useRemoveComponentsFromCollection } from '../data/apiHooks';
import BaseComponentCard from './BaseComponentCard';
import { canEditComponent } from './ComponentEditorModal';
@@ -30,7 +30,8 @@ export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
const {
libraryId,
collectionId,
- sidebarComponentUsageKey,
+ sidebarComponentInfo,
+ openComponentInfoSidebar,
openComponentEditor,
closeLibrarySidebar,
} = useLibraryContext();
@@ -52,7 +53,7 @@ export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
const removeFromCollection = () => {
removeComponentsMutation.mutateAsync([usageKey]).then(() => {
- if (sidebarComponentUsageKey === usageKey) {
+ if (sidebarComponentInfo?.id === usageKey) {
// Close sidebar if current component is open
closeLibrarySidebar();
}
@@ -62,6 +63,10 @@ export const ComponentMenu = ({ usageKey }: { usageKey: string }) => {
});
};
+ const showManageCollections = () => {
+ openComponentInfoSidebar(usageKey, SidebarAdditionalActions.JumpToAddCollections);
+ };
+
return (
{
)}
-
+
diff --git a/src/library-authoring/components/ComponentDeleter.tsx b/src/library-authoring/components/ComponentDeleter.tsx
index 53d52e8a4e..b71f139ae4 100644
--- a/src/library-authoring/components/ComponentDeleter.tsx
+++ b/src/library-authoring/components/ComponentDeleter.tsx
@@ -35,9 +35,10 @@ interface Props {
const ComponentDeleter = ({ usageKey, ...props }: Props) => {
const intl = useIntl();
const {
- sidebarComponentUsageKey,
+ sidebarComponentInfo,
closeLibrarySidebar,
} = useLibraryContext();
+ const sidebarComponentUsageKey = sidebarComponentInfo?.id;
const deleteComponentMutation = useDeleteLibraryBlock();
const doDelete = React.useCallback(() => {
diff --git a/src/library-authoring/library-sidebar/LibrarySidebar.tsx b/src/library-authoring/library-sidebar/LibrarySidebar.tsx
index c877c77c97..be9c33c426 100644
--- a/src/library-authoring/library-sidebar/LibrarySidebar.tsx
+++ b/src/library-authoring/library-sidebar/LibrarySidebar.tsx
@@ -18,7 +18,7 @@ import messages from '../messages';
* Sidebar container for library pages.
*
* It's designed to "squash" the page when open.
- * Uses `sidebarBodyComponent` of the `context` to
+ * Uses `sidebarComponentInfo.type` of the `context` to
* choose which component is rendered.
* You can add more components in `bodyComponentMap`.
* Use the returned actions to open and close this sidebar.
@@ -26,7 +26,7 @@ import messages from '../messages';
const LibrarySidebar = () => {
const intl = useIntl();
const {
- sidebarBodyComponent,
+ sidebarComponentInfo,
closeLibrarySidebar,
} = useLibraryContext();
@@ -46,8 +46,8 @@ const LibrarySidebar = () => {
unknown: null,
};
- const buildBody = () : React.ReactNode => bodyComponentMap[sidebarBodyComponent || 'unknown'];
- const buildHeader = (): React.ReactNode => headerComponentMap[sidebarBodyComponent || 'unknown'];
+ const buildBody = () : React.ReactNode => bodyComponentMap[sidebarComponentInfo?.type || 'unknown'];
+ const buildHeader = (): React.ReactNode => headerComponentMap[sidebarComponentInfo?.type || 'unknown'];
return (