feat: Sync units in course outline [FC-0083] (#1850)

* Adds the sync button in unit cards in the course outline.
* Opens the compare previews.
* Functionality to sync units.
* Functionality to decline sync units.
This commit is contained in:
Chris Chávez
2025-04-24 15:18:04 -05:00
committed by GitHub
parent 9824502278
commit d62c4cf4f8
11 changed files with 367 additions and 75 deletions

View File

@@ -292,6 +292,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
],
},
@@ -675,6 +680,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_2dbb0072785e',
@@ -759,6 +769,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_98cf62510471',
@@ -843,6 +858,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_d32bf9b2242c',
@@ -927,6 +947,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@4e592689563243c484af947465eaef0d',
@@ -1011,6 +1036,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
],
},
@@ -1196,6 +1226,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_aae927868e55',
@@ -1280,6 +1315,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_c037f3757df1',
@@ -1364,6 +1404,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_bc69a47c6fae',
@@ -1448,6 +1493,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@8f89194410954e768bde1764985454a7',
@@ -1532,6 +1582,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
],
},
@@ -1717,6 +1772,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
],
},
@@ -1995,6 +2055,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_f04afeac0131',
@@ -2079,6 +2144,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@b6662b497c094bcc9b870d8270c90c93',
@@ -2163,6 +2233,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@f91d8d31f7cf48ce990f8d8745ae4cfa',
@@ -2247,6 +2322,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_ac391cde8a91',
@@ -2331,6 +2411,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_36e0beb03f0a',
@@ -2415,6 +2500,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@1b0e2c2c84884b95b1c99fb678cc964c',
@@ -2499,6 +2589,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@c7e98fd39a6944edb6b286c32e1150ff',
@@ -2583,6 +2678,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@vertical+block@d6eaa391d2be41dea20b8b1bfbcb1c45',
@@ -2667,6 +2767,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
],
},
@@ -2945,6 +3050,11 @@ module.exports = {
selectedPartitionIndex: -1,
selectedGroupsLabel: '',
},
upstreamInfo: {
readyToSync: false,
upstreamRef: undefined,
versionSynced: undefined,
},
},
],
},

View File

@@ -15,6 +15,7 @@ import {
import {
MoreVert as MoveVertIcon,
EditOutline as EditIcon,
Sync as SyncIcon,
} from '@openedx/paragon/icons';
import { useContentTagsCount } from '../../generic/data/apiHooks';
@@ -55,6 +56,8 @@ const CardHeader = ({
discussionsSettings,
parentInfo,
extraActionsComponent,
onClickSync,
readyToSync,
}) => {
const intl = useIntl();
const [searchParams] = useSearchParams();
@@ -130,8 +133,17 @@ const CardHeader = ({
) : (
<>
{titleComponent}
{readyToSync && (
<IconButton
className="item-card-button-icon"
data-testid={`${namePrefix}-sync-button`}
alt={intl.formatMessage(messages.readyToSyncButtonAlt)}
iconAs={SyncIcon}
onClick={onClickSync}
/>
)}
<IconButton
className="item-card-edit-icon"
className="item-card-button-icon"
data-testid={`${namePrefix}-edit-button`}
alt={intl.formatMessage(messages.altButtonEdit)}
iconAs={EditIcon}
@@ -259,6 +271,8 @@ CardHeader.defaultProps = {
parentInfo: {},
cardId: '',
extraActionsComponent: null,
readyToSync: false,
onClickSync: null,
};
CardHeader.propTypes = {
@@ -305,6 +319,8 @@ CardHeader.propTypes = {
// An optional component that is rendered before the dropdown. This is used by the Subsection
// and Unit card components to render their plugin slots.
extraActionsComponent: PropTypes.node,
onClickSync: PropTypes.func,
readyToSync: PropTypes.bool,
};
export default CardHeader;

View File

@@ -12,7 +12,7 @@
color: $black;
}
.item-card-edit-icon {
.item-card-button-icon {
opacity: 0;
transition: opacity .3s linear;
margin-right: .5rem;
@@ -23,7 +23,7 @@
}
&:hover {
.item-card-edit-icon {
.item-card-button-icon {
opacity: 1;
}
}

View File

@@ -368,4 +368,19 @@ describe('<CardHeader />', () => {
renderComponent();
expect(screen.queryByText('0')).not.toBeInTheDocument();
});
it('should render sync button when is ready to sync', () => {
const mockClickSync = jest.fn();
renderComponent({
readyToSync: true,
onClickSync: mockClickSync,
});
const syncButton = screen.getByRole('button', { name: /update available - click to sync/i });
expect(syncButton).toBeInTheDocument();
fireEvent.click(syncButton);
expect(mockClickSync).toHaveBeenCalled();
});
});

View File

@@ -77,6 +77,11 @@ const messages = defineMessages({
id: 'course-authoring.course-outline.card.menu.manageTags',
defaultMessage: 'Manage tags',
},
readyToSyncButtonAlt: {
id: 'course-authoring.course-outline.card.button.sync.alt',
defaultMessage: 'Update available - click to sync',
description: 'Alt text for the sync icon button.',
},
});
export default messages;

View File

@@ -1,5 +1,10 @@
// @ts-check
import React, { useEffect, useRef } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
} from 'react';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import { useToggle } from '@openedx/paragon';
@@ -8,6 +13,7 @@ import { useSearchParams } from 'react-router-dom';
import CourseOutlineUnitCardExtraActionsSlot from '../../plugin-slots/CourseOutlineUnitCardExtraActionsSlot';
import { setCurrentItem, setCurrentSection, setCurrentSubsection } from '../data/slice';
import { fetchCourseSectionQuery } from '../data/thunk';
import { RequestStatus } from '../../data/constants';
import { isUnitReadOnly } from '../../course-unit/data/utils';
import CardHeader from '../card-header/CardHeader';
@@ -16,6 +22,7 @@ import TitleLink from '../card-header/TitleLink';
import XBlockStatus from '../xblock-status/XBlockStatus';
import { getItemStatus, getItemStatusBorder, scrollToElement } from '../utils';
import { useClipboard } from '../../generic/clipboard';
import { PreviewLibraryXBlockChanges } from '../../course-unit/preview-changes';
const UnitCard = ({
unit,
@@ -41,6 +48,7 @@ const UnitCard = ({
const locatorId = searchParams.get('show');
const isScrolledToElement = locatorId === unit.id;
const [isFormOpen, openForm, closeForm] = useToggle(false);
const [isSyncModalOpen, openSyncModal, closeSyncModal] = useToggle(false);
const namePrefix = 'unit';
const { copyToClipboard } = useClipboard();
@@ -56,8 +64,22 @@ const UnitCard = ({
isHeaderVisible = true,
enableCopyPasteUnits = false,
discussionEnabled,
upstreamInfo,
} = unit;
const blockSyncData = useMemo(() => {
if (!upstreamInfo.readyToSync) {
return undefined;
}
return {
displayName,
downstreamBlockId: id,
upstreamBlockId: upstreamInfo.upstreamRef,
upstreamBlockVersionSynced: upstreamInfo.versionSynced,
isVertical: true,
};
}, [upstreamInfo]);
const readOnly = isUnitReadOnly(unit);
// re-create actions object for customizations
@@ -107,6 +129,10 @@ const UnitCard = ({
copyToClipboard(id);
};
const handleOnPostChangeSync = useCallback(async () => {
await dispatch(fetchCourseSectionQuery([section.id]));
}, [dispatch, section]);
const titleComponent = (
<TitleLink
title={displayName}
@@ -147,59 +173,69 @@ const UnitCard = ({
const isDraggable = actions.draggable && (actions.allowMoveUp || actions.allowMoveDown);
return (
<SortableItem
id={id}
category={category}
key={id}
isDraggable={isDraggable}
isDroppable={actions.childAddable}
componentStyle={{
background: '#fdfdfd',
...borderStyle,
}}
>
<div
className={`unit-card ${isScrolledToElement ? 'highlight' : ''}`}
data-testid="unit-card"
ref={currentRef}
<>
<SortableItem
id={id}
category={category}
key={id}
isDraggable={isDraggable}
isDroppable={actions.childAddable}
componentStyle={{
background: '#fdfdfd',
...borderStyle,
}}
>
<CardHeader
title={displayName}
status={unitStatus}
hasChanges={hasChanges}
cardId={id}
onClickMenuButton={handleClickMenuButton}
onClickPublish={onOpenPublishModal}
onClickConfigure={onOpenConfigureModal}
onClickEdit={openForm}
onClickDelete={onOpenDeleteModal}
onClickMoveUp={handleUnitMoveUp}
onClickMoveDown={handleUnitMoveDown}
isFormOpen={isFormOpen}
closeForm={closeForm}
onEditSubmit={handleEditSubmit}
isDisabledEditField={readOnly || savingStatus === RequestStatus.IN_PROGRESS}
onClickDuplicate={onDuplicateSubmit}
titleComponent={titleComponent}
namePrefix={namePrefix}
actions={actions}
isVertical
enableCopyPasteUnits={enableCopyPasteUnits}
onClickCopy={handleCopyClick}
discussionEnabled={discussionEnabled}
discussionsSettings={discussionsSettings}
parentInfo={parentInfo}
extraActionsComponent={extraActionsComponent}
/>
<div className="unit-card__content item-children" data-testid="unit-card__content">
<XBlockStatus
isSelfPaced={isSelfPaced}
isCustomRelativeDatesActive={isCustomRelativeDatesActive}
blockData={unit}
<div
className={`unit-card ${isScrolledToElement ? 'highlight' : ''}`}
data-testid="unit-card"
ref={currentRef}
>
<CardHeader
title={displayName}
status={unitStatus}
hasChanges={hasChanges}
cardId={id}
onClickMenuButton={handleClickMenuButton}
onClickPublish={onOpenPublishModal}
onClickConfigure={onOpenConfigureModal}
onClickEdit={openForm}
onClickDelete={onOpenDeleteModal}
onClickMoveUp={handleUnitMoveUp}
onClickMoveDown={handleUnitMoveDown}
onClickSync={openSyncModal}
isFormOpen={isFormOpen}
closeForm={closeForm}
onEditSubmit={handleEditSubmit}
isDisabledEditField={readOnly || savingStatus === RequestStatus.IN_PROGRESS}
onClickDuplicate={onDuplicateSubmit}
titleComponent={titleComponent}
namePrefix={namePrefix}
actions={actions}
isVertical
enableCopyPasteUnits={enableCopyPasteUnits}
onClickCopy={handleCopyClick}
discussionEnabled={discussionEnabled}
discussionsSettings={discussionsSettings}
parentInfo={parentInfo}
extraActionsComponent={extraActionsComponent}
readyToSync={upstreamInfo.readyToSync}
/>
<div className="unit-card__content item-children" data-testid="unit-card__content">
<XBlockStatus
isSelfPaced={isSelfPaced}
isCustomRelativeDatesActive={isCustomRelativeDatesActive}
blockData={unit}
/>
</div>
</div>
</div>
</SortableItem>
</SortableItem>
<PreviewLibraryXBlockChanges
blockData={blockSyncData}
isModalOpen={isSyncModalOpen}
closeModal={closeSyncModal}
postChange={handleOnPostChangeSync}
/>
</>
);
};
@@ -225,6 +261,11 @@ UnitCard.propTypes = {
isHeaderVisible: PropTypes.bool,
enableCopyPasteUnits: PropTypes.bool,
discussionEnabled: PropTypes.bool,
upstreamInfo: PropTypes.shape({
readyToSync: PropTypes.bool.isRequired,
upstreamRef: PropTypes.string.isRequired,
versionSynced: PropTypes.number.isRequired,
}).isRequired,
}).isRequired,
subsection: PropTypes.shape({
id: PropTypes.string.isRequired,

View File

@@ -1,5 +1,6 @@
import {
act, render, fireEvent, within,
act, render, fireEvent, within, screen,
waitFor,
} from '@testing-library/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { AppProvider } from '@edx/frontend-platform/react';
@@ -11,6 +12,17 @@ import UnitCard from './UnitCard';
import cardMessages from '../card-header/messages';
let store;
const mockUseAcceptLibraryBlockChanges = jest.fn();
const mockUseIgnoreLibraryBlockChanges = jest.fn();
jest.mock('../../course-unit/data/apiHooks', () => ({
useAcceptLibraryBlockChanges: () => ({
mutateAsync: mockUseAcceptLibraryBlockChanges,
}),
useIgnoreLibraryBlockChanges: () => ({
mutateAsync: mockUseIgnoreLibraryBlockChanges,
}),
}));
const section = {
id: '1',
@@ -43,6 +55,11 @@ const unit = {
duplicable: true,
},
isHeaderVisible: true,
upstreamInfo: {
readyToSync: true,
upstreamRef: 'lct:org1:lib1:unit:1',
versionSynced: 1,
},
};
const queryClient = new QueryClient();
@@ -147,4 +164,51 @@ describe('<UnitCard />', () => {
});
expect(queryByRole('status')).not.toBeInTheDocument();
});
it('should sync unit changes from upstream', async () => {
renderComponent();
expect(await screen.findByTestId('unit-card-header')).toBeInTheDocument();
// Click on sync button
const syncButton = screen.getByRole('button', { name: /update available - click to sync/i });
fireEvent.click(syncButton);
// Should open compare preview modal
expect(screen.getByRole('heading', { name: /preview changes: unit name/i })).toBeInTheDocument();
expect(screen.getByText('Preview not available')).toBeInTheDocument();
// Click on accept changes
const acceptChangesButton = screen.getByText(/accept changes/i);
fireEvent.click(acceptChangesButton);
await waitFor(() => expect(mockUseAcceptLibraryBlockChanges).toHaveBeenCalled());
});
it('should decline sync unit changes from upstream', async () => {
renderComponent();
expect(await screen.findByTestId('unit-card-header')).toBeInTheDocument();
// Click on sync button
const syncButton = screen.getByRole('button', { name: /update available - click to sync/i });
fireEvent.click(syncButton);
// Should open compare preview modal
expect(screen.getByRole('heading', { name: /preview changes: unit name/i })).toBeInTheDocument();
expect(screen.getByText('Preview not available')).toBeInTheDocument();
// Click on ignore changes
const ignoreChangesButton = screen.getByRole('button', { name: /ignore changes/i });
fireEvent.click(ignoreChangesButton);
// Should open the confirmation modal
expect(screen.getByRole('heading', { name: /ignore these changes\?/i })).toBeInTheDocument();
// Click on ignore button
const ignoreButton = screen.getByRole('button', { name: /ignore/i });
fireEvent.click(ignoreButton);
await waitFor(() => expect(mockUseIgnoreLibraryBlockChanges).toHaveBeenCalled());
});
});

View File

@@ -12,13 +12,13 @@ import IframePreviewLibraryXBlockChanges, { LibraryChangesMessageData } from '.'
import { messageTypes } from '../constants';
import { libraryBlockChangesUrl } from '../data/api';
import { ToastActionData } from '../../generic/toast-context';
import { getLibraryBlockMetadataUrl } from '../../library-authoring/data/api';
import { getLibraryBlockMetadataUrl, getLibraryContainerApiUrl } from '../../library-authoring/data/api';
const usageKey = 'some-id';
const defaultEventData: LibraryChangesMessageData = {
displayName: 'Test block',
downstreamBlockId: usageKey,
upstreamBlockId: 'some-lib-id',
upstreamBlockId: 'lct:org:lib1:unit:1',
upstreamBlockVersionSynced: 1,
isVertical: false,
};
@@ -87,6 +87,15 @@ describe('<IframePreviewLibraryXBlockChanges />', () => {
expect(await screen.findByText('Preview changes: Test block -> New test block')).toBeInTheDocument();
});
it('renders both new and old title if they are different on units', async () => {
axiosMock.onGet(getLibraryContainerApiUrl(defaultEventData.upstreamBlockId)).reply(200, {
displayName: 'New test Unit',
});
render({ ...defaultEventData, isVertical: true, displayName: 'Test Unit' });
expect(await screen.findByText('Preview changes: Test Unit -> New test Unit')).toBeInTheDocument();
});
it('accept changes works', async () => {
axiosMock.onPost(libraryBlockChangesUrl(usageKey)).reply(200, {});
render();

View File

@@ -14,7 +14,7 @@ import messages from './messages';
import { ToastContext } from '../../generic/toast-context';
import LoadingButton from '../../generic/loading-button';
import Loading from '../../generic/Loading';
import { useLibraryBlockMetadata } from '../../library-authoring/data/apiHooks';
import { useContainer, useLibraryBlockMetadata } from '../../library-authoring/data/apiHooks';
export interface LibraryChangesMessageData {
displayName: string,
@@ -48,14 +48,20 @@ export const PreviewLibraryXBlockChanges = ({
// ignore changes confirmation modal toggle.
const [isConfirmModalOpen, openConfirmModal, closeConfirmModal] = useToggle(false);
// TODO: Split into two different components to avoid making these two calls in which
// one will definitely fail
const { data: componentMetadata } = useLibraryBlockMetadata(blockData?.upstreamBlockId);
const { data: unitMetadata } = useContainer(blockData?.upstreamBlockId);
const metadata = blockData?.isVertical ? unitMetadata : componentMetadata;
const acceptChangesMutation = useAcceptLibraryBlockChanges();
const ignoreChangesMutation = useIgnoreLibraryBlockChanges();
const getTitle = useCallback(() => {
const oldName = blockData?.displayName;
const newName = componentMetadata?.displayName;
const newName = metadata?.displayName;
if (!oldName) {
if (blockData?.isVertical) {
@@ -67,7 +73,7 @@ export const PreviewLibraryXBlockChanges = ({
return intl.formatMessage(messages.title, { blockTitle: oldName });
}
return intl.formatMessage(messages.diffTitle, { oldName, newName });
}, [blockData, componentMetadata]);
}, [blockData, metadata]);
const getBody = useCallback(() => {
if (!blockData) {
@@ -78,6 +84,7 @@ export const PreviewLibraryXBlockChanges = ({
usageKey={blockData.upstreamBlockId}
oldVersion={blockData.upstreamBlockVersionSynced || 'published'}
newVersion="published"
isContainer={blockData.isVertical}
/>
);
}, [blockData]);

View File

@@ -6,10 +6,21 @@ import { LibraryBlock, type VersionSpec } from '../LibraryBlock';
import messages from './messages';
const PreviewNotAvailable = () => {
const intl = useIntl();
return (
<div className="d-flex mt-4 justify-content-center">
{intl.formatMessage(messages.previewNotAvailable)}
</div>
);
};
interface Props {
usageKey: string;
oldVersion?: VersionSpec;
newVersion?: VersionSpec;
isContainer?: boolean;
}
/**
@@ -20,7 +31,12 @@ interface Props {
* In the future, it would be better to have a way of highlighting the changes
* or showing a diff.
*/
const CompareChangesWidget = ({ usageKey, oldVersion = 'published', newVersion = 'draft' }: Props) => {
const CompareChangesWidget = ({
usageKey,
oldVersion = 'published',
newVersion = 'draft',
isContainer = false,
}: Props) => {
const intl = useIntl();
return (
@@ -28,24 +44,28 @@ const CompareChangesWidget = ({ usageKey, oldVersion = 'published', newVersion =
<Tabs variant="tabs" defaultActiveKey="new" id="preview-version-toggle" mountOnEnter>
<Tab eventKey="old" title={intl.formatMessage(messages.oldVersionTitle)}>
<div className="p-2 bg-white">
<IframeProvider>
<LibraryBlock
usageKey={usageKey}
version={oldVersion}
minHeight="50vh"
/>
</IframeProvider>
{isContainer ? (<PreviewNotAvailable />) : (
<IframeProvider>
<LibraryBlock
usageKey={usageKey}
version={oldVersion}
minHeight="50vh"
/>
</IframeProvider>
)}
</div>
</Tab>
<Tab eventKey="new" title={intl.formatMessage(messages.newVersionTitle)}>
<div className="p-2 bg-white">
<IframeProvider>
<LibraryBlock
usageKey={usageKey}
version={newVersion}
minHeight="50vh"
/>
</IframeProvider>
{isContainer ? (<PreviewNotAvailable />) : (
<IframeProvider>
<LibraryBlock
usageKey={usageKey}
version={newVersion}
minHeight="50vh"
/>
</IframeProvider>
)}
</div>
</Tab>
</Tabs>

View File

@@ -17,6 +17,11 @@ const messages = defineMessages({
defaultMessage: 'Compare Changes',
description: 'Title used for the compare changes dialog',
},
previewNotAvailable: {
id: 'course-authoring.library-authoring.component-comparison.preview-not-available',
defaultMessage: 'Preview not available',
description: 'Message shown when preview is not available.',
},
});
export default messages;