fix: add components button in item bank children page (#2491)
`Add components` button in item bank children page was not working.
This commit is contained in:
@@ -62,6 +62,8 @@ export const COURSE_BLOCK_NAMES = ({
|
||||
libraryContent: { id: 'library_content', name: 'Library content' },
|
||||
splitTest: { id: 'split_test', name: 'Split Test' },
|
||||
component: { id: 'component', name: 'Component' },
|
||||
itembank: { id: 'itembank', name: 'Problem Bank' },
|
||||
legacyLibraryContent: { id: 'library_content', name: 'Randomized Content Block' },
|
||||
});
|
||||
|
||||
export const STUDIO_CLIPBOARD_CHANNEL = 'studio_clipboard_channel';
|
||||
|
||||
@@ -50,6 +50,7 @@ const CourseUnit = ({ courseId }) => {
|
||||
isUnitVerticalType,
|
||||
isUnitLibraryType,
|
||||
isSplitTestType,
|
||||
isProblemBankType,
|
||||
staticFileNotices,
|
||||
currentlyVisibleToStudents,
|
||||
unitXBlockActions,
|
||||
@@ -219,6 +220,7 @@ const CourseUnit = ({ courseId }) => {
|
||||
parentLocator={blockId}
|
||||
isSplitTestType={isSplitTestType}
|
||||
isUnitVerticalType={isUnitVerticalType}
|
||||
isProblemBankType={isProblemBankType}
|
||||
handleCreateNewCourseXBlock={handleCreateNewCourseXBlock}
|
||||
addComponentTemplateData={addComponentTemplateData}
|
||||
/>
|
||||
|
||||
@@ -14,7 +14,7 @@ import { fetchCourseSectionVerticalData } from '../data/thunk';
|
||||
import { getCourseSectionVerticalApiUrl } from '../data/api';
|
||||
import { courseSectionVerticalMock } from '../__mocks__';
|
||||
import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants';
|
||||
import AddComponent from './AddComponent';
|
||||
import AddComponent, { AddComponentProps } from './AddComponent';
|
||||
import messages from './messages';
|
||||
import { IframeProvider } from '../../generic/hooks/context/iFrameContext';
|
||||
import { messageTypes } from '../constants';
|
||||
@@ -56,13 +56,11 @@ jest.mock('../../generic/hooks/context/hooks', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const renderComponent = (props) => render(
|
||||
const renderComponent = (props?: AddComponentProps) => render(
|
||||
<IframeProvider>
|
||||
<AddComponent
|
||||
blockId={blockId}
|
||||
isUnitVerticalType
|
||||
parentLocator={blockId}
|
||||
addComponentTemplateData={{}}
|
||||
handleCreateNewCourseXBlock={handleCreateNewCourseXBlockMock}
|
||||
{...props}
|
||||
/>
|
||||
@@ -94,7 +92,7 @@ describe('<AddComponent />', () => {
|
||||
),
|
||||
});
|
||||
expect(btn).toBeInTheDocument();
|
||||
if (component.beta) {
|
||||
if (componentTemplates[component].beta) {
|
||||
expect(within(btn).queryByText('Beta')).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
@@ -7,28 +6,64 @@ import {
|
||||
ActionRow, Button, StandardModal, useToggle,
|
||||
} from '@openedx/paragon';
|
||||
|
||||
import { getCourseSectionVertical, getCourseUnitData } from '../data/selectors';
|
||||
import { useWaffleFlags } from '../../data/apiHooks';
|
||||
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';
|
||||
import { ContentType } from '../../library-authoring/routes';
|
||||
import { messageTypes } from '../constants';
|
||||
import { useIframe } from '../../generic/hooks/context/hooks';
|
||||
import { useEventListener } from '../../generic/hooks';
|
||||
import VideoSelectorPage from '../../editors/VideoSelectorPage';
|
||||
import EditorPage from '../../editors/EditorPage';
|
||||
import { useWaffleFlags } from '@src/data/apiHooks';
|
||||
import { COMPONENT_TYPES } from '@src/generic/block-type-utils/constants';
|
||||
import { ComponentPicker } from '@src/library-authoring/component-picker';
|
||||
import { ContentType } from '@src/library-authoring/routes';
|
||||
import { useIframe } from '@src/generic/hooks/context/hooks';
|
||||
import { useEventListener } from '@src/generic/hooks';
|
||||
import VideoSelectorPage from '@src/editors/VideoSelectorPage';
|
||||
import EditorPage from '@src/editors/EditorPage';
|
||||
import { SelectedComponent } from '@src/library-authoring';
|
||||
import { fetchCourseSectionVerticalData } from '../data/thunk';
|
||||
import { messageTypes } from '../constants';
|
||||
import messages from './messages';
|
||||
import AddComponentButton from './add-component-btn';
|
||||
import ComponentModalView from './add-component-modals/ComponentModalView';
|
||||
import { getCourseSectionVertical, getCourseUnitData } from '../data/selectors';
|
||||
|
||||
type ComponentTemplateData = {
|
||||
displayName: string,
|
||||
category?: string,
|
||||
type: string,
|
||||
beta?: boolean,
|
||||
templates: Array<{
|
||||
boilerplateName?: string,
|
||||
category?: string,
|
||||
displayName: string,
|
||||
supportLevel?: string | boolean,
|
||||
}>,
|
||||
supportLegend: {
|
||||
allowUnsupportedXblocks?: boolean,
|
||||
documentationLabel?: string,
|
||||
showLegend?: boolean,
|
||||
},
|
||||
};
|
||||
|
||||
export interface AddComponentProps {
|
||||
isSplitTestType?: boolean,
|
||||
isUnitVerticalType?: boolean,
|
||||
parentLocator: string,
|
||||
handleCreateNewCourseXBlock: (
|
||||
args: object,
|
||||
callback?: (args: { courseKey: string, locator: string }) => void
|
||||
) => void,
|
||||
isProblemBankType?: boolean,
|
||||
addComponentTemplateData?: {
|
||||
blockId: string,
|
||||
parentLocator?: string,
|
||||
model: ComponentTemplateData,
|
||||
},
|
||||
}
|
||||
|
||||
const AddComponent = ({
|
||||
parentLocator,
|
||||
isSplitTestType,
|
||||
isUnitVerticalType,
|
||||
isProblemBankType,
|
||||
addComponentTemplateData,
|
||||
handleCreateNewCourseXBlock,
|
||||
}) => {
|
||||
}: AddComponentProps) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -36,16 +71,16 @@ const AddComponent = ({
|
||||
const [isOpenHtml, openHtml, closeHtml] = useToggle(false);
|
||||
const [isOpenOpenAssessment, openOpenAssessment, closeOpenAssessment] = useToggle(false);
|
||||
const { componentTemplates = {} } = useSelector(getCourseSectionVertical);
|
||||
const blockId = addComponentTemplateData.parentLocator || parentLocator;
|
||||
const blockId = addComponentTemplateData?.parentLocator || parentLocator;
|
||||
const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle();
|
||||
const [isVideoSelectorModalOpen, showVideoSelectorModal, closeVideoSelectorModal] = useToggle();
|
||||
const [isXBlockEditorModalOpen, showXBlockEditorModal, closeXBlockEditorModal] = useToggle();
|
||||
|
||||
const [blockType, setBlockType] = useState(null);
|
||||
const [courseId, setCourseId] = useState(null);
|
||||
const [newBlockId, setNewBlockId] = useState(null);
|
||||
const [blockType, setBlockType] = useState<string | null>(null);
|
||||
const [courseId, setCourseId] = useState<string | null>(null);
|
||||
const [newBlockId, setNewBlockId] = useState<string | null>(null);
|
||||
const [isSelectLibraryContentModalOpen, showSelectLibraryContentModal, closeSelectLibraryContentModal] = useToggle();
|
||||
const [selectedComponents, setSelectedComponents] = useState([]);
|
||||
const [selectedComponents, setSelectedComponents] = useState<SelectedComponent[]>([]);
|
||||
const [usageId, setUsageId] = useState(null);
|
||||
const { sendMessageToIframe } = useIframe();
|
||||
const { useVideoGalleryFlow } = useWaffleFlags(courseId ?? undefined);
|
||||
@@ -84,7 +119,7 @@ const AddComponent = ({
|
||||
dispatch(fetchCourseSectionVerticalData(blockId, sequenceId));
|
||||
}, [closeXBlockEditorModal, closeVideoSelectorModal, sendMessageToIframe, blockId, sequenceId]);
|
||||
|
||||
const handleLibraryV2Selection = useCallback((selection) => {
|
||||
const handleLibraryV2Selection = useCallback((selection: SelectedComponent) => {
|
||||
handleCreateNewCourseXBlock({
|
||||
type: COMPONENT_TYPES.libraryV2,
|
||||
category: selection.blockType,
|
||||
@@ -94,7 +129,7 @@ const AddComponent = ({
|
||||
closeAddLibraryContentModal();
|
||||
}, [usageId]);
|
||||
|
||||
const handleCreateNewXBlock = (type, moduleName) => {
|
||||
const handleCreateNewXBlock = (type: string, moduleName?: string) => {
|
||||
switch (type) {
|
||||
case COMPONENT_TYPES.discussion:
|
||||
case COMPONENT_TYPES.dragAndDrop:
|
||||
@@ -156,16 +191,16 @@ const AddComponent = ({
|
||||
}
|
||||
};
|
||||
|
||||
if (isUnitVerticalType || isSplitTestType) {
|
||||
if (isUnitVerticalType || isSplitTestType || isProblemBankType) {
|
||||
return (
|
||||
<div className="py-4">
|
||||
{Object.keys(componentTemplates).length && isUnitVerticalType ? (
|
||||
<>
|
||||
<h5 className="h3 mb-4 text-center">{intl.formatMessage(messages.title)}</h5>
|
||||
<ul className="new-component-type list-unstyled m-0 d-flex flex-wrap justify-content-center">
|
||||
{componentTemplates.map((component) => {
|
||||
{componentTemplates.map((component: ComponentTemplateData) => {
|
||||
const { type, displayName, beta } = component;
|
||||
let modalParams;
|
||||
let modalParams: { open: () => void, close: () => void, isOpen: boolean };
|
||||
|
||||
if (!component.templates.length) {
|
||||
return null;
|
||||
@@ -268,7 +303,7 @@ const AddComponent = ({
|
||||
/>
|
||||
</div>
|
||||
</StandardModal>
|
||||
{isXBlockEditorModalOpen && (
|
||||
{isXBlockEditorModalOpen && courseId && blockType && newBlockId && (
|
||||
<div className="editor-page">
|
||||
<EditorPage
|
||||
courseId={courseId}
|
||||
@@ -288,32 +323,4 @@ const AddComponent = ({
|
||||
return null;
|
||||
};
|
||||
|
||||
AddComponent.propTypes = {
|
||||
isSplitTestType: PropTypes.bool.isRequired,
|
||||
isUnitVerticalType: PropTypes.bool.isRequired,
|
||||
parentLocator: PropTypes.string.isRequired,
|
||||
handleCreateNewCourseXBlock: PropTypes.func.isRequired,
|
||||
addComponentTemplateData: {
|
||||
blockId: PropTypes.string.isRequired,
|
||||
model: PropTypes.shape({
|
||||
displayName: PropTypes.string.isRequired,
|
||||
category: PropTypes.string,
|
||||
type: PropTypes.string.isRequired,
|
||||
templates: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
boilerplateName: PropTypes.string,
|
||||
category: PropTypes.string,
|
||||
displayName: PropTypes.string.isRequired,
|
||||
supportLevel: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
||||
}),
|
||||
),
|
||||
supportLegend: PropTypes.shape({
|
||||
allowUnsupportedXblocks: PropTypes.bool,
|
||||
documentationLabel: PropTypes.string,
|
||||
showLegend: PropTypes.bool,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
export default AddComponent;
|
||||
@@ -41,6 +41,7 @@ export const getXBlockSupportMessages = (intl) => ({
|
||||
|
||||
export const messageTypes = {
|
||||
refreshXBlock: 'refreshXBlock',
|
||||
refreshIframe: 'refreshIframe',
|
||||
showMoveXBlockModal: 'showMoveXBlockModal',
|
||||
completeXBlockMoving: 'completeXBlockMoving',
|
||||
rollbackMovedXBlock: 'rollbackMovedXBlock',
|
||||
|
||||
@@ -72,6 +72,10 @@ export const useCourseUnit = ({ courseId, blockId }) => {
|
||||
const isUnitVerticalType = unitCategory === COURSE_BLOCK_NAMES.vertical.id;
|
||||
const isUnitLibraryType = unitCategory === COURSE_BLOCK_NAMES.libraryContent.id;
|
||||
const isSplitTestType = unitCategory === COURSE_BLOCK_NAMES.splitTest.id;
|
||||
const isProblemBankType = [
|
||||
COURSE_BLOCK_NAMES.legacyLibraryContent.id,
|
||||
COURSE_BLOCK_NAMES.itembank.id,
|
||||
].includes(unitCategory);
|
||||
|
||||
const headerNavigationsActions = {
|
||||
handleViewLive: () => {
|
||||
@@ -254,6 +258,7 @@ export const useCourseUnit = ({ courseId, blockId }) => {
|
||||
isUnitVerticalType,
|
||||
isUnitLibraryType,
|
||||
isSplitTestType,
|
||||
isProblemBankType,
|
||||
sharedClipboardData,
|
||||
showPasteXBlock,
|
||||
showPasteUnit,
|
||||
|
||||
@@ -15,6 +15,7 @@ export type UseMessageHandlersTypes = {
|
||||
handleOpenManageTagsModal: (id: string) => void;
|
||||
handleShowProcessingNotification: (variant: string) => void;
|
||||
handleHideProcessingNotification: () => void;
|
||||
handleRefreshIframe: () => void;
|
||||
};
|
||||
|
||||
export type MessageHandlersTypes = Record<string, (payload: any) => void>;
|
||||
|
||||
@@ -31,6 +31,7 @@ export const useMessageHandlers = ({
|
||||
handleShowProcessingNotification,
|
||||
handleHideProcessingNotification,
|
||||
handleEditXBlock,
|
||||
handleRefreshIframe,
|
||||
}: UseMessageHandlersTypes): MessageHandlersTypes => {
|
||||
const { copyToClipboard } = useClipboard();
|
||||
|
||||
@@ -50,6 +51,7 @@ export const useMessageHandlers = ({
|
||||
[messageTypes.saveEditedXBlockData]: handleSaveEditedXBlockData,
|
||||
[messageTypes.studioAjaxError]: ({ error }) => handleResponseErrors(error, dispatch, updateSavingStatus),
|
||||
[messageTypes.refreshPositions]: handleFinishXBlockDragging,
|
||||
[messageTypes.refreshIframe]: handleRefreshIframe,
|
||||
[messageTypes.openManageTags]: (payload) => handleOpenManageTagsModal(payload.contentId),
|
||||
[messageTypes.addNewComponent]: () => handleShowProcessingNotification(NOTIFICATION_MESSAGES.adding),
|
||||
[messageTypes.pasteNewComponent]: () => handleShowProcessingNotification(NOTIFICATION_MESSAGES.pasting),
|
||||
|
||||
@@ -46,6 +46,8 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Useful to reload iframe
|
||||
const [iframeKey, setIframeKey] = useState(0);
|
||||
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useToggle(false);
|
||||
const [isUnlinkModalOpen, openUnlinkModal, closeUnlinkModal] = useToggle(false);
|
||||
const [isConfigureModalOpen, openConfigureModal, closeConfigureModal] = useToggle(false);
|
||||
@@ -182,6 +184,12 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
dispatch(hideProcessingNotification());
|
||||
};
|
||||
|
||||
const handleRefreshIframe = () => {
|
||||
// Updating iframeKey forces the iframe to re-render.
|
||||
/* istanbul ignore next */
|
||||
setIframeKey((prev) => prev + 1);
|
||||
};
|
||||
|
||||
const messageHandlers = useMessageHandlers({
|
||||
courseId,
|
||||
dispatch,
|
||||
@@ -199,6 +207,7 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
handleShowProcessingNotification,
|
||||
handleHideProcessingNotification,
|
||||
handleEditXBlock,
|
||||
handleRefreshIframe,
|
||||
});
|
||||
|
||||
useIframeMessages(messageHandlers);
|
||||
@@ -268,6 +277,7 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
/>
|
||||
) : null}
|
||||
<iframe
|
||||
key={iframeKey}
|
||||
ref={iframeRef}
|
||||
title={intl.formatMessage(messages.xblockIframeTitle)}
|
||||
name="xblock-iframe"
|
||||
|
||||
@@ -53,17 +53,10 @@ type ComponentPickerProps = {
|
||||
showOnlyPublished?: boolean,
|
||||
extraFilter?: string[],
|
||||
visibleTabs?: ContentType[],
|
||||
} & (
|
||||
{
|
||||
componentPickerMode?: 'single',
|
||||
onComponentSelected?: ComponentSelectedEvent,
|
||||
onChangeComponentSelection?: never,
|
||||
} | {
|
||||
componentPickerMode: 'multiple'
|
||||
onComponentSelected?: never,
|
||||
onChangeComponentSelection?: ComponentSelectionChangedEvent,
|
||||
}
|
||||
);
|
||||
componentPickerMode?: 'single' | 'multiple',
|
||||
onComponentSelected?: ComponentSelectedEvent,
|
||||
onChangeComponentSelection?: ComponentSelectionChangedEvent,
|
||||
};
|
||||
|
||||
export const ComponentPicker: React.FC<ComponentPickerProps> = ({
|
||||
/** Restrict the component picker to a specific library */
|
||||
|
||||
Reference in New Issue
Block a user