diff --git a/src/course-unit/CourseUnit.test.jsx b/src/course-unit/CourseUnit.test.jsx
index 5f569ef3d1..26e8befef3 100644
--- a/src/course-unit/CourseUnit.test.jsx
+++ b/src/course-unit/CourseUnit.test.jsx
@@ -282,7 +282,7 @@ describe('', () => {
await waitFor(() => {
const problemButton = getByRole('button', {
- name: new RegExp(`${addComponentMessages.buttonText.defaultMessage} Problem`, 'i'),
+ name: new RegExp(`problem ${addComponentMessages.buttonText.defaultMessage} Problem`, 'i'),
});
userEvent.click(problemButton);
diff --git a/src/course-unit/add-component/AddComponent.jsx b/src/course-unit/add-component/AddComponent.jsx
index 6abb3d647c..1a0676239c 100644
--- a/src/course-unit/add-component/AddComponent.jsx
+++ b/src/course-unit/add-component/AddComponent.jsx
@@ -2,13 +2,14 @@ import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { useIntl } from '@edx/frontend-platform/i18n';
-import { useToggle } from '@openedx/paragon';
+import { StandardModal, useToggle } from '@openedx/paragon';
import { getCourseSectionVertical } from '../data/selectors';
import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants';
import ComponentModalView from './add-component-modals/ComponentModalView';
import AddComponentButton from './add-component-btn';
import messages from './messages';
+import { ComponentPicker } from '../../library-authoring/component-picker';
const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
const navigate = useNavigate();
@@ -17,6 +18,17 @@ const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
const [isOpenHtml, openHtml, closeHtml] = useToggle(false);
const [isOpenOpenAssessment, openOpenAssessment, closeOpenAssessment] = useToggle(false);
const { componentTemplates } = useSelector(getCourseSectionVertical);
+ const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle();
+
+ const handleLibraryV2Selection = (selection) => {
+ handleCreateNewCourseXBlock({
+ type: COMPONENT_TYPES.libraryV2,
+ category: selection.blockType,
+ parentLocator: blockId,
+ libraryContentKey: selection.usageKey,
+ });
+ closeAddLibraryContentModal();
+ };
const handleCreateNewXBlock = (type, moduleName) => {
switch (type) {
@@ -38,6 +50,9 @@ const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
case COMPONENT_TYPES.itembank:
handleCreateNewCourseXBlock({ type, category: 'itembank', parentLocator: blockId });
break;
+ case COMPONENT_TYPES.libraryV2:
+ showAddLibraryContentModal();
+ break;
case COMPONENT_TYPES.advanced:
handleCreateNewCourseXBlock({
type: moduleName, category: moduleName, parentLocator: blockId,
@@ -122,6 +137,18 @@ const AddComponent = ({ blockId, handleCreateNewCourseXBlock }) => {
);
})}
+
+
+
);
};
diff --git a/src/course-unit/add-component/AddComponent.test.jsx b/src/course-unit/add-component/AddComponent.test.jsx
index 237cd77f46..9cc27acf55 100644
--- a/src/course-unit/add-component/AddComponent.test.jsx
+++ b/src/course-unit/add-component/AddComponent.test.jsx
@@ -23,6 +23,11 @@ let axiosMock;
const blockId = '123';
const handleCreateNewCourseXBlockMock = jest.fn();
+// Mock ComponentPicker to call onComponentSelected on load
+jest.mock('../../library-authoring/component-picker', () => ({
+ ComponentPicker: (props) => props.onComponentSelected({ usageKey: 'test-usage-key', blockType: 'html' }),
+}));
+
const renderComponent = (props) => render(
@@ -61,7 +66,11 @@ describe('', () => {
expect(getByRole('heading', { name: messages.title.defaultMessage })).toBeInTheDocument();
Object.keys(componentTemplates).forEach((component) => {
const btn = getByRole('button', {
- name: new RegExp(`${messages.buttonText.defaultMessage} ${componentTemplates[component].display_name}`, 'i'),
+ name: new RegExp(
+ `${componentTemplates[component].type
+ } ${messages.buttonText.defaultMessage} ${componentTemplates[component].display_name}`,
+ 'i',
+ ),
});
expect(btn).toBeInTheDocument();
if (component.beta) {
@@ -115,7 +124,11 @@ describe('', () => {
}
return expect(getByRole('button', {
- name: new RegExp(`${messages.buttonText.defaultMessage} ${componentTemplates[component].display_name}`, 'i'),
+ name: new RegExp(
+ `${componentTemplates[component].type
+ } ${messages.buttonText.defaultMessage} ${componentTemplates[component].display_name}`,
+ 'i',
+ ),
})).toBeInTheDocument();
});
});
@@ -180,7 +193,7 @@ describe('', () => {
const { getByRole } = renderComponent();
const discussionButton = getByRole('button', {
- name: new RegExp(`${messages.buttonText.defaultMessage} Problem`, 'i'),
+ name: new RegExp(`problem ${messages.buttonText.defaultMessage} Problem`, 'i'),
});
userEvent.click(discussionButton);
@@ -399,6 +412,22 @@ describe('', () => {
});
});
+ it('shows library picker on clicking v2 library content btn', async () => {
+ const { findByRole } = renderComponent();
+ const libBtn = await findByRole('button', {
+ name: new RegExp(`${messages.buttonText.defaultMessage} Library content`, 'i'),
+ });
+
+ userEvent.click(libBtn);
+ expect(handleCreateNewCourseXBlockMock).toHaveBeenCalled();
+ expect(handleCreateNewCourseXBlockMock).toHaveBeenCalledWith({
+ type: COMPONENT_TYPES.libraryV2,
+ parentLocator: '123',
+ category: 'html',
+ libraryContentKey: 'test-usage-key',
+ });
+ });
+
describe('component support label', () => {
it('component support label is hidden if component support legend is disabled', async () => {
const supportLevels = ['fs', 'ps'];
diff --git a/src/course-unit/data/api.js b/src/course-unit/data/api.js
index 6e35b70a3f..02b7ab89f2 100644
--- a/src/course-unit/data/api.js
+++ b/src/course-unit/data/api.js
@@ -63,9 +63,16 @@ export async function getCourseSectionVerticalData(unitId) {
* @param {string} [options.displayName] - The display name.
* @param {string} [options.boilerplate] - The boilerplate.
* @param {string} [options.stagedContent] - The staged content.
+ * @param {string} [options.libraryContentKey] - component key from library if being imported.
*/
export async function createCourseXblock({
- type, category, parentLocator, displayName, boilerplate, stagedContent,
+ type,
+ category,
+ parentLocator,
+ displayName,
+ boilerplate,
+ stagedContent,
+ libraryContentKey,
}) {
const body = {
type,
@@ -74,6 +81,7 @@ export async function createCourseXblock({
parent_locator: parentLocator,
display_name: displayName,
staged_content: stagedContent,
+ library_content_key: libraryContentKey,
};
const { data } = await getAuthenticatedHttpClient()
diff --git a/src/library-authoring/LibraryLayout.tsx b/src/library-authoring/LibraryLayout.tsx
index 09cb45b3d3..04bfc25c64 100644
--- a/src/library-authoring/LibraryLayout.tsx
+++ b/src/library-authoring/LibraryLayout.tsx
@@ -10,7 +10,7 @@ import { LibraryProvider } from './common/context';
import { CreateCollectionModal } from './create-collection';
import { LibraryTeamModal } from './library-team';
import LibraryCollectionPage from './collections/LibraryCollectionPage';
-import { ComponentPickerModal } from './component-picker';
+import { ComponentPicker } from './component-picker';
import { ComponentEditorModal } from './components/ComponentEditorModal';
const LibraryLayout = () => {
@@ -32,9 +32,9 @@ const LibraryLayout = () => {
collectionId={collectionId}
/** The component picker modal to use. We need to pass it as a reference instead of
* directly importing it to avoid the import cycle:
- * ComponentPickerModal > ComponentPicker > LibraryAuthoringPage/LibraryCollectionPage >
- * Sidebar > AddContentContainer > ComponentPickerModal */
- componentPickerModal={ComponentPickerModal}
+ * ComponentPicker > LibraryAuthoringPage/LibraryCollectionPage >
+ * Sidebar > AddContentContainer > ComponentPicker */
+ componentPicker={ComponentPicker}
>
{
collectionId,
openCreateCollectionModal,
openComponentEditor,
- componentPickerModal,
+ componentPicker,
} = useLibraryContext();
const createBlockMutation = useCreateLibraryBlock();
const updateComponentsMutation = useAddComponentsToCollection(libraryId, collectionId);
@@ -239,7 +239,7 @@ const AddContentContainer = () => {
return (
{collectionId ? (
- componentPickerModal && (
+ componentPicker && (
<>
baseRender(
{children}
diff --git a/src/library-authoring/add-content/PickLibraryContentModal.tsx b/src/library-authoring/add-content/PickLibraryContentModal.tsx
index f8a8ac3965..ef08b68ec0 100644
--- a/src/library-authoring/add-content/PickLibraryContentModal.tsx
+++ b/src/library-authoring/add-content/PickLibraryContentModal.tsx
@@ -1,6 +1,6 @@
import React, { useCallback, useContext, useState } from 'react';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
-import { ActionRow, Button } from '@openedx/paragon';
+import { ActionRow, Button, StandardModal } from '@openedx/paragon';
import { ToastContext } from '../../generic/toast-context';
import { type SelectedComponent, useLibraryContext } from '../common/context';
@@ -41,14 +41,14 @@ export const PickLibraryContentModal: React.FC = (
libraryId,
collectionId,
/** We need to get it as a reference instead of directly importing it to avoid the import cycle:
- * ComponentPickerModal > ComponentPicker > LibraryAuthoringPage/LibraryCollectionPage >
- * Sidebar > AddContentContainer > ComponentPickerModal */
- componentPickerModal: ComponentPickerModal,
+ * ComponentPicker > LibraryAuthoringPage/LibraryCollectionPage >
+ * Sidebar > AddContentContainer > ComponentPicker */
+ componentPicker: ComponentPicker,
} = useLibraryContext();
// istanbul ignore if: this should never happen
- if (!collectionId || !ComponentPickerModal) {
- throw new Error('libraryId and componentPickerModal are required');
+ if (!collectionId || !ComponentPicker) {
+ throw new Error('libraryId and componentPicker are required');
}
const updateComponentsMutation = useAddComponentsToCollection(libraryId, collectionId);
@@ -70,12 +70,19 @@ export const PickLibraryContentModal: React.FC = (
}, [selectedComponents]);
return (
- }
- />
+ >
+
+
);
};
diff --git a/src/library-authoring/common/context.tsx b/src/library-authoring/common/context.tsx
index be229065f5..4e3ac78cc5 100644
--- a/src/library-authoring/common/context.tsx
+++ b/src/library-authoring/common/context.tsx
@@ -6,7 +6,7 @@ import React, {
useState,
} from 'react';
-import type { ComponentPickerModal } from '../component-picker';
+import type { ComponentPicker } from '../component-picker';
import type { ContentLibrary } from '../data/api';
import { useContentLibrary } from '../data/apiHooks';
@@ -27,9 +27,9 @@ type NoComponentPickerType = {
restrictToLibrary?: never;
/** The component picker modal to use. We need to pass it as a reference instead of
* directly importing it to avoid the import cycle:
- * ComponentPickerModal > ComponentPicker > LibraryAuthoringPage/LibraryCollectionPage >
- * Sidebar > AddContentContainer > ComponentPickerModal */
- componentPickerModal?: typeof ComponentPickerModal;
+ * ComponentPicker > LibraryAuthoringPage/LibraryCollectionPage >
+ * Sidebar > AddContentContainer > ComponentPicker */
+ componentPicker?: typeof ComponentPicker;
};
type ComponentPickerSingleType = {
@@ -39,7 +39,7 @@ type ComponentPickerSingleType = {
addComponentToSelectedComponents?: never;
removeComponentFromSelectedComponents?: never;
restrictToLibrary: boolean;
- componentPickerModal?: never;
+ componentPicker?: never;
};
type ComponentPickerMultipleType = {
@@ -49,7 +49,7 @@ type ComponentPickerMultipleType = {
addComponentToSelectedComponents: ComponentSelectedEvent;
removeComponentFromSelectedComponents: ComponentSelectedEvent;
restrictToLibrary: boolean;
- componentPickerModal?: never;
+ componentPicker?: never;
};
type ComponentPickerType = NoComponentPickerType | ComponentPickerSingleType | ComponentPickerMultipleType;
@@ -121,7 +121,7 @@ type NoComponentPickerProps = {
onComponentSelected?: never;
onChangeComponentSelection?: never;
restrictToLibrary?: never;
- componentPickerModal?: typeof ComponentPickerModal;
+ componentPicker?: typeof ComponentPicker;
};
export type ComponentPickerSingleProps = {
@@ -129,7 +129,7 @@ export type ComponentPickerSingleProps = {
onComponentSelected: ComponentSelectedEvent;
onChangeComponentSelection?: never;
restrictToLibrary?: boolean;
- componentPickerModal?: never;
+ componentPicker?: never;
};
export type ComponentPickerMultipleProps = {
@@ -137,7 +137,7 @@ export type ComponentPickerMultipleProps = {
onComponentSelected?: never;
onChangeComponentSelection?: ComponentSelectionChangedEvent;
restrictToLibrary?: boolean;
- componentPickerModal?: never;
+ componentPicker?: never;
};
type ComponentPickerProps = NoComponentPickerProps | ComponentPickerSingleProps | ComponentPickerMultipleProps;
@@ -150,7 +150,7 @@ type LibraryProviderProps = {
showOnlyPublished?: boolean;
/** Only used for testing */
initialSidebarComponentInfo?: SidebarComponentInfo;
- componentPickerModal?: typeof ComponentPickerModal;
+ componentPicker?: typeof ComponentPicker;
} & ComponentPickerProps;
/**
@@ -166,7 +166,7 @@ export const LibraryProvider = ({
onChangeComponentSelection,
showOnlyPublished = false,
initialSidebarComponentInfo,
- componentPickerModal,
+ componentPicker,
}: LibraryProviderProps) => {
const [collectionId, setCollectionId] = useState(collectionIdProp);
const [sidebarComponentInfo, setSidebarComponentInfo] = useState(
@@ -276,7 +276,7 @@ export const LibraryProvider = ({
if (!componentPickerMode) {
return {
...contextValue,
- componentPickerModal,
+ componentPicker,
};
}
if (componentPickerMode === 'single') {
@@ -329,7 +329,7 @@ export const LibraryProvider = ({
openComponentEditor,
closeComponentEditor,
resetSidebarAdditionalActions,
- componentPickerModal,
+ componentPicker,
]);
return (
diff --git a/src/library-authoring/component-picker/ComponentPicker.tsx b/src/library-authoring/component-picker/ComponentPicker.tsx
index 8f8cc77bd1..553b2e3d97 100644
--- a/src/library-authoring/component-picker/ComponentPicker.tsx
+++ b/src/library-authoring/component-picker/ComponentPicker.tsx
@@ -35,7 +35,7 @@ const defaultSelectionChangedCallback: ComponentSelectionChangedEvent = (selecti
window.parent.postMessage({ type: 'pickerSelectionChanged', selections }, '*');
};
-type ComponentPickerProps = { libraryId?: string } & (
+type ComponentPickerProps = { libraryId?: string, showOnlyPublished?: boolean } & (
{
componentPickerMode?: 'single',
onComponentSelected?: ComponentSelectedEvent,
@@ -51,6 +51,7 @@ type ComponentPickerProps = { libraryId?: string } & (
export const ComponentPicker: React.FC = ({
/** Restrict the component picker to a specific library */
libraryId,
+ showOnlyPublished,
componentPickerMode = 'single',
/** This default callback is used to send the selected component back to the parent window,
* when the component picker is used in an iframe.
@@ -99,7 +100,7 @@ export const ComponentPicker: React.FC = ({
diff --git a/src/library-authoring/component-picker/ComponentPickerModal.tsx b/src/library-authoring/component-picker/ComponentPickerModal.tsx
deleted file mode 100644
index 5d6e6ae30b..0000000000
--- a/src/library-authoring/component-picker/ComponentPickerModal.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import React from 'react';
-import { StandardModal } from '@openedx/paragon';
-
-import type { ComponentSelectionChangedEvent } from '../common/context';
-import { ComponentPicker } from './ComponentPicker';
-
-interface ComponentPickerModalProps {
- libraryId?: string;
- isOpen: boolean;
- onClose: () => void;
- onChangeComponentSelection: ComponentSelectionChangedEvent;
- footerNode?: React.ReactNode;
-}
-
-// eslint-disable-next-line import/prefer-default-export
-export const ComponentPickerModal: React.FC = ({
- libraryId,
- isOpen,
- onClose,
- onChangeComponentSelection,
- footerNode,
-}) => {
- if (!isOpen) {
- return null;
- }
-
- return (
-
-
-
- );
-};
diff --git a/src/library-authoring/component-picker/index.ts b/src/library-authoring/component-picker/index.ts
index 5ffe86d0f8..24d8920e03 100644
--- a/src/library-authoring/component-picker/index.ts
+++ b/src/library-authoring/component-picker/index.ts
@@ -1,2 +1,2 @@
+/* eslint-disable import/prefer-default-export */
export { ComponentPicker } from './ComponentPicker';
-export { ComponentPickerModal } from './ComponentPickerModal';