Skip to content

Commit

Permalink
feat: add existing content to a collection
Browse files Browse the repository at this point in the history
  • Loading branch information
rpenido committed Oct 22, 2024
1 parent 6ebde8b commit 7f74d83
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/library-authoring/LibraryAuthoringPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
28 changes: 27 additions & 1 deletion src/library-authoring/add-content/AddContentContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -11,6 +12,7 @@ import {
AutoAwesome,
BookOpen,
Create,
Folder,
ThumbUpOutline,
Question,
VideoCamera,
Expand All @@ -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';

Expand Down Expand Up @@ -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 {
Expand All @@ -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),
Expand Down Expand Up @@ -186,6 +200,8 @@ const AddContentContainer = () => {
onPaste();
} else if (blockType === 'collection') {
openCreateCollectionModal();
} else if (blockType === 'libraryContent') {
showAddLibraryContentModal();
} else {
onCreateBlock(blockType);
}
Expand All @@ -197,7 +213,17 @@ const AddContentContainer = () => {

return (
<Stack direction="vertical">
{!collectionId && <AddContentButton contentType={collectionButtonData} onCreateContent={onCreateContent} />}
{collectionId ? (
<>
<AddContentButton contentType={libraryContentButtonData} onCreateContent={onCreateContent} />
<PickLibraryContentModal
isOpen={isAddLibraryContentModalOpen}
onClose={closeAddLibraryContentModal}
/>
</>
) : (
<AddContentButton contentType={collectionButtonData} onCreateContent={onCreateContent} />
)}
<hr className="w-100 bg-gray-500" />
{/* Note: for MVP we are hiding the unuspported types, not just disabling them. */}
{contentTypes.filter(ct => !ct.disabled).map((contentType) => (
Expand Down
74 changes: 74 additions & 0 deletions src/library-authoring/add-content/PickLibraryContentModal.tsx
Original file line number Diff line number Diff line change
@@ -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<PickLibraryContentModalFooterProps> = ({
onSubmit,
selectedComponents,
}) => (
<ActionRow>
<FormattedMessage {...messages.selectedComponents} values={{ count: selectedComponents.length }} />
<ActionRow.Spacer />
<Button variant="primary" onClick={onSubmit}>
<FormattedMessage {...messages.addToCollectionButton} />
</Button>
</ActionRow>
);

interface PickLibraryContentModalProps {
isOpen: boolean;
onClose: () => void;
}

// eslint-disable-next-line import/prefer-default-export
export const PickLibraryContentModal: React.FC<PickLibraryContentModalProps> = ({
isOpen,
onClose,
}) => {
const intl = useIntl();

const { collectionId } = useParams();
const {
libraryId,
} = useLibraryContext();

const updateComponentsMutation = useAddComponentsToCollection(libraryId, collectionId);

const { showToast } = useContext(ToastContext);

const [selectedComponents, setSelectedComponents] = useState<SelectedComponent[]>([]);

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 (
<ComponentPickerModal
isOpen={isOpen}
onClose={onClose}
onChangeComponentSelection={setSelectedComponents}
footerNode={<PickLibraryContentModalFooter onSubmit={onSubmit} selectedComponents={selectedComponents} />}
/>
);
};
20 changes: 20 additions & 0 deletions src/library-authoring/add-content/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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.',
Expand Down
2 changes: 1 addition & 1 deletion src/library-authoring/common/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions src/library-authoring/component-picker/ComponentPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
41 changes: 41 additions & 0 deletions src/library-authoring/component-picker/ComponentPickerModal.tsx
Original file line number Diff line number Diff line change
@@ -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<ComponentPickerModalProps> = ({
isOpen,
onClose,
onChangeComponentSelection,
footerNode,
}) => {
if (!isOpen) {
return null;
}

return (
<StandardModal
title="Select components"
isOpen={isOpen}
onClose={onClose}
isOverflowVisible={false}
size="xl"
footerNode={footerNode}
>
<ComponentPicker
componentPickerMode="multiple"
onChangeComponentSelection={onChangeComponentSelection}
/>
</StandardModal>
);
};
3 changes: 2 additions & 1 deletion src/library-authoring/component-picker/index.ts
Original file line number Diff line number Diff line change
@@ -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';
1 change: 1 addition & 0 deletions src/library-authoring/library-sidebar/LibrarySidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 1 addition & 1 deletion src/library-authoring/library-sidebar/index.ts
Original file line number Diff line number Diff line change
@@ -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';

0 comments on commit 7f74d83

Please sign in to comment.