From 7f74d83b4d7737dc26a97ff21a6a7fa2c908ca23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B4mulo=20Penido?= Date: Tue, 22 Oct 2024 18:30:48 -0300 Subject: [PATCH] feat: add existing content to a collection --- .../LibraryAuthoringPage.tsx | 1 + .../add-content/AddContentContainer.tsx | 28 ++++++- .../add-content/PickLibraryContentModal.tsx | 74 +++++++++++++++++++ src/library-authoring/add-content/messages.ts | 20 +++++ src/library-authoring/common/context.tsx | 2 +- .../component-picker/ComponentPicker.tsx | 1 + .../component-picker/ComponentPickerModal.tsx | 41 ++++++++++ .../component-picker/index.ts | 3 +- .../library-sidebar/LibrarySidebar.tsx | 1 + .../library-sidebar/index.ts | 2 +- 10 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 src/library-authoring/add-content/PickLibraryContentModal.tsx create mode 100644 src/library-authoring/component-picker/ComponentPickerModal.tsx diff --git a/src/library-authoring/LibraryAuthoringPage.tsx b/src/library-authoring/LibraryAuthoringPage.tsx index d52cbbb014..95db2c9d58 100644 --- a/src/library-authoring/LibraryAuthoringPage.tsx +++ b/src/library-authoring/LibraryAuthoringPage.tsx @@ -36,6 +36,7 @@ import { import LibraryComponents from './components/LibraryComponents'; import LibraryCollections from './collections/LibraryCollections'; import LibraryHome from './LibraryHome'; +// eslint-disable-next-line import/no-cycle import { LibrarySidebar } from './library-sidebar'; import { SidebarBodyComponentId, useLibraryContext } from './common/context'; import messages from './messages'; diff --git a/src/library-authoring/add-content/AddContentContainer.tsx b/src/library-authoring/add-content/AddContentContainer.tsx index 92a43b2b9e..b284c665bf 100644 --- a/src/library-authoring/add-content/AddContentContainer.tsx +++ b/src/library-authoring/add-content/AddContentContainer.tsx @@ -3,6 +3,7 @@ import { useSelector } from 'react-redux'; import { Stack, Button, + useToggle, } from '@openedx/paragon'; import { useIntl } from '@edx/frontend-platform/i18n'; import { getConfig } from '@edx/frontend-platform'; @@ -11,6 +12,7 @@ import { AutoAwesome, BookOpen, Create, + Folder, ThumbUpOutline, Question, VideoCamera, @@ -25,6 +27,8 @@ import { getCanEdit } from '../../course-unit/data/selectors'; import { useCreateLibraryBlock, useLibraryPasteClipboard, useAddComponentsToCollection } from '../data/apiHooks'; import { useLibraryContext } from '../common/context'; import { canEditComponent } from '../components/ComponentEditorModal'; +// eslint-disable-next-line import/no-cycle +import { PickLibraryContentModal } from './PickLibraryContentModal'; import messages from './messages'; @@ -75,6 +79,8 @@ const AddContentContainer = () => { const canEdit = useSelector(getCanEdit); const { showPasteXBlock, sharedClipboardData } = useCopyToClipboard(canEdit); + const [isAddLibraryContentModalOpen, showAddLibraryContentModal, closeAddLibraryContentModal] = useToggle(); + const parsePasteErrorMsg = (error: any) => { let errMsg: string; try { @@ -94,6 +100,14 @@ const AddContentContainer = () => { icon: BookOpen, blockType: 'collection', }; + + const libraryContentButtonData = { + name: intl.formatMessage(messages.libraryContentButton), + disabled: false, + icon: Folder, + blockType: 'libraryContent', + }; + const contentTypes = [ { name: intl.formatMessage(messages.textTypeButton), @@ -186,6 +200,8 @@ const AddContentContainer = () => { onPaste(); } else if (blockType === 'collection') { openCreateCollectionModal(); + } else if (blockType === 'libraryContent') { + showAddLibraryContentModal(); } else { onCreateBlock(blockType); } @@ -197,7 +213,17 @@ const AddContentContainer = () => { return ( - {!collectionId && } + {collectionId ? ( + <> + + + + ) : ( + + )}
{/* Note: for MVP we are hiding the unuspported types, not just disabling them. */} {contentTypes.filter(ct => !ct.disabled).map((contentType) => ( diff --git a/src/library-authoring/add-content/PickLibraryContentModal.tsx b/src/library-authoring/add-content/PickLibraryContentModal.tsx new file mode 100644 index 0000000000..cf61dcb4f7 --- /dev/null +++ b/src/library-authoring/add-content/PickLibraryContentModal.tsx @@ -0,0 +1,74 @@ +import React, { useCallback, useContext, useState } from 'react'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; +import { ActionRow, Button } from '@openedx/paragon'; +import { useParams } from 'react-router-dom'; + +import { ToastContext } from '../../generic/toast-context'; +import { type SelectedComponent, useLibraryContext } from '../common/context'; +// eslint-disable-next-line import/no-cycle +import { ComponentPickerModal } from '../component-picker'; +import { useAddComponentsToCollection } from '../data/apiHooks'; +import messages from './messages'; + +interface PickLibraryContentModalFooterProps { + onSubmit: () => void; + selectedComponents: SelectedComponent[]; +} + +const PickLibraryContentModalFooter: React.FC = ({ + onSubmit, + selectedComponents, +}) => ( + + + + + +); + +interface PickLibraryContentModalProps { + isOpen: boolean; + onClose: () => void; +} + +// eslint-disable-next-line import/prefer-default-export +export const PickLibraryContentModal: React.FC = ({ + isOpen, + onClose, +}) => { + const intl = useIntl(); + + const { collectionId } = useParams(); + const { + libraryId, + } = useLibraryContext(); + + const updateComponentsMutation = useAddComponentsToCollection(libraryId, collectionId); + + const { showToast } = useContext(ToastContext); + + const [selectedComponents, setSelectedComponents] = useState([]); + + const onSubmit = useCallback(() => { + const usageKeys = selectedComponents.map(({ usageKey }) => usageKey); + updateComponentsMutation.mutateAsync(usageKeys) + .then(() => { + showToast(intl.formatMessage(messages.successAssociateComponentMessage)); + onClose(); + }) + .catch(() => { + showToast(intl.formatMessage(messages.errorAssociateComponentMessage)); + }); + }, [selectedComponents]); + + return ( + } + /> + ); +}; diff --git a/src/library-authoring/add-content/messages.ts b/src/library-authoring/add-content/messages.ts index 6a8946aae5..60e619720b 100644 --- a/src/library-authoring/add-content/messages.ts +++ b/src/library-authoring/add-content/messages.ts @@ -6,6 +6,21 @@ const messages = defineMessages({ defaultMessage: 'Collection', description: 'Content of button to create a Collection.', }, + libraryContentButton: { + id: 'course-authoring.library-authoring.add-content.buttons.library-content', + defaultMessage: 'Existing Library Content', + description: 'Content of button to add existing library content to a collection.', + }, + addToCollectionButton: { + id: 'course-authoring.library-authoring.add-content.buttons.library-content.add-to-collection', + defaultMessage: 'Add to Collection', + description: 'Button to add library content to a collection.', + }, + selectedComponents: { + id: 'course-authoring.library-authoring.add-content.selected-components', + defaultMessage: '{count} Selected Component(s)', + description: 'Title for selected components in library.', + }, textTypeButton: { id: 'course-authoring.library-authoring.add-content.buttons.types.text', defaultMessage: 'Text', @@ -51,6 +66,11 @@ const messages = defineMessages({ defaultMessage: 'There was an error creating the content.', description: 'Message when creation of content in library is on error', }, + successAssociateComponentMessage: { + id: 'course-authoring.library-authoring.associate-collection-content.success.text', + defaultMessage: 'Content linked successfully.', + description: 'Message when linking of content to a collection in library is success', + }, errorAssociateComponentMessage: { id: 'course-authoring.library-authoring.associate-collection-content.error.text', defaultMessage: 'There was an error linking the content to this collection.', diff --git a/src/library-authoring/common/context.tsx b/src/library-authoring/common/context.tsx index b16082bc0f..267f1e44b1 100644 --- a/src/library-authoring/common/context.tsx +++ b/src/library-authoring/common/context.tsx @@ -9,7 +9,7 @@ import React, { import type { ContentLibrary } from '../data/api'; import { useContentLibrary } from '../data/apiHooks'; -interface SelectedComponent { +export interface SelectedComponent { usageKey: string; blockType: string; } diff --git a/src/library-authoring/component-picker/ComponentPicker.tsx b/src/library-authoring/component-picker/ComponentPicker.tsx index 48e1ebbe82..e8e736019f 100644 --- a/src/library-authoring/component-picker/ComponentPicker.tsx +++ b/src/library-authoring/component-picker/ComponentPicker.tsx @@ -8,6 +8,7 @@ import { LibraryProvider, useLibraryContext, } from '../common/context'; +// eslint-disable-next-line import/no-cycle import LibraryAuthoringPage from '../LibraryAuthoringPage'; import LibraryCollectionPage from '../collections/LibraryCollectionPage'; import SelectLibrary from './SelectLibrary'; diff --git a/src/library-authoring/component-picker/ComponentPickerModal.tsx b/src/library-authoring/component-picker/ComponentPickerModal.tsx new file mode 100644 index 0000000000..0decbb3065 --- /dev/null +++ b/src/library-authoring/component-picker/ComponentPickerModal.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { StandardModal } from '@openedx/paragon'; + +import type { ComponentSelectionChangedEvent } from '../common/context'; +// eslint-disable-next-line import/no-cycle +import { ComponentPicker } from './ComponentPicker'; + +interface ComponentPickerModalProps { + isOpen: boolean; + onClose: () => void; + onChangeComponentSelection: ComponentSelectionChangedEvent; + footerNode?: React.ReactNode; +} + +// eslint-disable-next-line import/prefer-default-export +export const ComponentPickerModal: React.FC = ({ + 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 458946220c..7c88bed932 100644 --- a/src/library-authoring/component-picker/index.ts +++ b/src/library-authoring/component-picker/index.ts @@ -1,2 +1,3 @@ -// eslint-disable-next-line import/prefer-default-export +// eslint-disable-next-line import/no-cycle export { ComponentPicker } from './ComponentPicker'; +export { ComponentPickerModal } from './ComponentPickerModal'; diff --git a/src/library-authoring/library-sidebar/LibrarySidebar.tsx b/src/library-authoring/library-sidebar/LibrarySidebar.tsx index be9c33c426..ed1507b1fb 100644 --- a/src/library-authoring/library-sidebar/LibrarySidebar.tsx +++ b/src/library-authoring/library-sidebar/LibrarySidebar.tsx @@ -7,6 +7,7 @@ import { import { Close } from '@openedx/paragon/icons'; import { useIntl } from '@edx/frontend-platform/i18n'; +// eslint-disable-next-line import/no-cycle import { AddContentContainer, AddContentHeader } from '../add-content'; import { CollectionInfo, CollectionInfoHeader } from '../collections'; import { SidebarBodyComponentId, useLibraryContext } from '../common/context'; diff --git a/src/library-authoring/library-sidebar/index.ts b/src/library-authoring/library-sidebar/index.ts index 087b1e1d9d..e5137ebb5d 100644 --- a/src/library-authoring/library-sidebar/index.ts +++ b/src/library-authoring/library-sidebar/index.ts @@ -1,2 +1,2 @@ -// eslint-disable-next-line import/prefer-default-export +// eslint-disable-next-line import/prefer-default-export, import/no-cycle export { default as LibrarySidebar } from './LibrarySidebar';