feat: Unit align sidebar [FC-0114] (#2856)
Implements the align sidebar in the unit page for the unit and components
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { initializeMocks, render, screen } from '@src/testUtils';
|
||||
import { render, screen, initializeMocks } from '@src/testUtils';
|
||||
|
||||
import * as CourseAuthoringContext from '@src/CourseAuthoringContext';
|
||||
import * as CourseDetailsApi from '@src/data/apiHooks';
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { SchoolOutline } from '@openedx/paragon/icons';
|
||||
import { ContentTagsDrawer } from '@src/content-tags-drawer';
|
||||
import { useContentData } from '@src/content-tags-drawer/data/apiHooks';
|
||||
import { useCourseAuthoringContext } from '@src/CourseAuthoringContext';
|
||||
import { SidebarTitle } from '@src/generic/sidebar';
|
||||
import { AlignSidebar } from '@src/generic/sidebar/AlignSidebar';
|
||||
import { useOutlineSidebarContext } from './OutlineSidebarContext';
|
||||
|
||||
/**
|
||||
* Align sidebar for course or selected containers.
|
||||
*/
|
||||
export const OutlineAlignSidebar = () => {
|
||||
const {
|
||||
courseId,
|
||||
@@ -24,20 +25,14 @@ export const OutlineAlignSidebar = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SidebarTitle
|
||||
title={
|
||||
contentData && 'displayName' in contentData
|
||||
? contentData.displayName
|
||||
: contentData?.courseDisplayNameWithDefault || ''
|
||||
}
|
||||
icon={SchoolOutline}
|
||||
onBackBtnClick={(sidebarContentId !== courseId) ? handleBack : undefined}
|
||||
/>
|
||||
<ContentTagsDrawer
|
||||
id={sidebarContentId}
|
||||
variant="component"
|
||||
/>
|
||||
</div>
|
||||
<AlignSidebar
|
||||
title={
|
||||
contentData && 'displayName' in contentData
|
||||
? contentData.displayName
|
||||
: contentData?.courseDisplayNameWithDefault || ''
|
||||
}
|
||||
contentId={sidebarContentId}
|
||||
onBackBtnClick={(sidebarContentId !== courseId) ? handleBack : undefined}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2928,6 +2928,22 @@ describe('<CourseUnit />', () => {
|
||||
expect(await screen.findByText('Access: 3 Groups')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens the align sidebar on postMessage event', async () => {
|
||||
setConfig({
|
||||
...getConfig(),
|
||||
ENABLE_TAGGING_TAXONOMY_PAGES: 'true',
|
||||
ENABLE_UNIT_PAGE_NEW_DESIGN: 'true',
|
||||
});
|
||||
|
||||
render(<RootWrapper />);
|
||||
|
||||
await screen.findByTitle(xblockContainerIframeMessages.xblockIframeTitle.defaultMessage);
|
||||
|
||||
simulatePostMessageEvent(messageTypes.openManageTags, { contentId: blockId });
|
||||
|
||||
await screen.findByText('Align');
|
||||
});
|
||||
|
||||
describe('Add sidebar', () => {
|
||||
let user;
|
||||
|
||||
|
||||
39
src/course-unit/unit-sidebar/UnitAlignSidebar.test.tsx
Normal file
39
src/course-unit/unit-sidebar/UnitAlignSidebar.test.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { render, screen, initializeMocks } from '@src/testUtils';
|
||||
import { UnitAlignSidebar } from './UnitAlignSidebar';
|
||||
import { UnitSidebarProvider } from './UnitSidebarContext';
|
||||
|
||||
jest.mock('@src/content-tags-drawer', () => ({
|
||||
ContentTagsDrawer: jest.fn(({ id, variant }) => (
|
||||
<div data-testid="content-tags-drawer">
|
||||
drawer-mock-{id}-{variant}
|
||||
</div>
|
||||
)),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useParams: () => ({ blockId: 'unit-id-1' }),
|
||||
}));
|
||||
|
||||
const renderComponent = () => render(
|
||||
<UnitSidebarProvider readOnly={false}>
|
||||
<UnitAlignSidebar />
|
||||
</UnitSidebarProvider>,
|
||||
);
|
||||
|
||||
describe('OutlineAlignSidebar', () => {
|
||||
beforeEach(() => {
|
||||
initializeMocks();
|
||||
});
|
||||
|
||||
it('renders ContentTagsDrawer with the correct id and variant', () => {
|
||||
renderComponent();
|
||||
|
||||
const drawer = screen.getByTestId('content-tags-drawer');
|
||||
|
||||
expect(drawer).toBeInTheDocument();
|
||||
expect(drawer).toHaveTextContent(
|
||||
'drawer-mock-unit-id-1-component',
|
||||
);
|
||||
});
|
||||
});
|
||||
36
src/course-unit/unit-sidebar/UnitAlignSidebar.tsx
Normal file
36
src/course-unit/unit-sidebar/UnitAlignSidebar.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useContentData } from '@src/content-tags-drawer/data/apiHooks';
|
||||
import { AlignSidebar } from '@src/generic/sidebar/AlignSidebar';
|
||||
import { useCallback } from 'react';
|
||||
import { useUnitSidebarContext } from './UnitSidebarContext';
|
||||
|
||||
/**
|
||||
* Align sidebar for unit or selected components.
|
||||
*/
|
||||
export const UnitAlignSidebar = () => {
|
||||
const { blockId } = useParams();
|
||||
const { currentComponentId, setCurrentPageKey } = useUnitSidebarContext();
|
||||
|
||||
const sidebarContentId = currentComponentId || blockId;
|
||||
|
||||
const {
|
||||
data: contentData,
|
||||
} = useContentData(sidebarContentId);
|
||||
|
||||
const handleBack = useCallback(() => {
|
||||
// Set the align sidebar without current component to back
|
||||
// to unit align sidebar.
|
||||
setCurrentPageKey('align');
|
||||
}, [setCurrentPageKey]);
|
||||
|
||||
return (
|
||||
<AlignSidebar
|
||||
title={
|
||||
contentData && 'displayName' in contentData
|
||||
? contentData.displayName : ''
|
||||
}
|
||||
contentId={sidebarContentId || ''}
|
||||
onBackBtnClick={currentComponentId ? handleBack : undefined}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -3,15 +3,20 @@ import {
|
||||
} from 'react';
|
||||
import { SidebarPage } from '@src/generic/sidebar';
|
||||
import { useToggle } from '@openedx/paragon';
|
||||
import { useStateWithUrlSearchParam } from '@src/hooks';
|
||||
|
||||
export type UnitSidebarPageKeys = 'info' | 'add';
|
||||
export type UnitSidebarPageKeys = 'info' | 'add' | 'align';
|
||||
export type UnitSidebarPages = Record<UnitSidebarPageKeys, SidebarPage>;
|
||||
|
||||
interface UnitSidebarContextData {
|
||||
currentPageKey: UnitSidebarPageKeys;
|
||||
setCurrentPageKey: (pageKey: UnitSidebarPageKeys) => void;
|
||||
setCurrentPageKey: (pageKey: UnitSidebarPageKeys, componentId?: string) => void;
|
||||
currentTabKey?: string;
|
||||
setCurrentTabKey: (tabKey: string | undefined) => void;
|
||||
// The Id of the component used in the current sidebar page
|
||||
// The component is not necessarily selected to open a selected sidebar.
|
||||
// Example: Align sidebar
|
||||
currentComponentId?: string;
|
||||
isOpen: boolean;
|
||||
open: () => void;
|
||||
toggle: () => void;
|
||||
@@ -27,14 +32,23 @@ export const UnitSidebarProvider = ({
|
||||
children?: React.ReactNode,
|
||||
readOnly: boolean,
|
||||
}) => {
|
||||
const [currentPageKey, setCurrentPageKeyState] = useState<UnitSidebarPageKeys>('info');
|
||||
const [currentPageKey, setCurrentPageKeyState] = useStateWithUrlSearchParam<UnitSidebarPageKeys>(
|
||||
'info',
|
||||
'sidebar',
|
||||
(value: string) => value as UnitSidebarPageKeys,
|
||||
(value: UnitSidebarPageKeys) => value,
|
||||
);
|
||||
const [currentTabKey, setCurrentTabKey] = useState<string>();
|
||||
const [currentComponentId, setCurrentComponentId] = useState<string>();
|
||||
const [isOpen, open,, toggle] = useToggle(true);
|
||||
|
||||
const setCurrentPageKey = useCallback(/* istanbul ignore next */ (pageKey: UnitSidebarPageKeys) => {
|
||||
// Reset tab
|
||||
const setCurrentPageKey = useCallback(/* istanbul ignore next */ (
|
||||
pageKey: UnitSidebarPageKeys,
|
||||
componentId?: string,
|
||||
) => {
|
||||
setCurrentTabKey(undefined);
|
||||
setCurrentPageKeyState(pageKey);
|
||||
setCurrentComponentId(componentId);
|
||||
open();
|
||||
}, [open]);
|
||||
|
||||
@@ -44,6 +58,7 @@ export const UnitSidebarProvider = ({
|
||||
setCurrentPageKey,
|
||||
currentTabKey,
|
||||
setCurrentTabKey,
|
||||
currentComponentId,
|
||||
isOpen,
|
||||
open,
|
||||
toggle,
|
||||
@@ -54,6 +69,7 @@ export const UnitSidebarProvider = ({
|
||||
setCurrentPageKey,
|
||||
currentTabKey,
|
||||
setCurrentTabKey,
|
||||
currentComponentId,
|
||||
isOpen,
|
||||
open,
|
||||
toggle,
|
||||
|
||||
@@ -6,6 +6,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Info',
|
||||
description: 'Label of the button for the Info sidebar',
|
||||
},
|
||||
sidebarButtonAlign: {
|
||||
id: 'course-authoring.unit-page.sidebar.info.sidebar-button-align',
|
||||
defaultMessage: 'Align',
|
||||
description: 'Label of the button for the Align sidebar',
|
||||
},
|
||||
sidebarButtonAdd: {
|
||||
id: 'course-authoring.unit-page.sidebar.add.sidebar-button-add',
|
||||
defaultMessage: 'Add',
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Info, Plus } from '@openedx/paragon/icons';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { Info, Tag, Plus } from '@openedx/paragon/icons';
|
||||
import { SidebarPage } from '@src/generic/sidebar';
|
||||
import messages from './messages';
|
||||
import { UnitInfoSidebar } from './unit-info/UnitInfoSidebar';
|
||||
import { UnitAlignSidebar } from './UnitAlignSidebar';
|
||||
import { AddSidebar } from './AddSidebar';
|
||||
import { useUnitSidebarContext } from './UnitSidebarContext';
|
||||
|
||||
@@ -18,6 +20,7 @@ export type UnitSidebarPages = {
|
||||
* if you want to use the context in the sidebar pages.
|
||||
*/
|
||||
export const useUnitSidebarPages = (): UnitSidebarPages => {
|
||||
const showAlignSidebar = getConfig().ENABLE_TAGGING_TAXONOMY_PAGES === 'true';
|
||||
const { readOnly } = useUnitSidebarContext();
|
||||
return {
|
||||
info: {
|
||||
@@ -32,5 +35,12 @@ export const useUnitSidebarPages = (): UnitSidebarPages => {
|
||||
title: messages.sidebarButtonAdd,
|
||||
},
|
||||
}),
|
||||
...(showAlignSidebar && {
|
||||
align: {
|
||||
component: UnitAlignSidebar,
|
||||
icon: Tag,
|
||||
title: messages.sidebarButtonAlign,
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -39,6 +39,8 @@ import {
|
||||
AccessManagedXBlockDataTypes,
|
||||
} from './types';
|
||||
import { formatAccessManagedXBlockData, getIframeUrl, getLegacyEditModalUrl } from './utils';
|
||||
import { useUnitSidebarContext } from '../unit-sidebar/UnitSidebarContext';
|
||||
import { isUnitPageNewDesignEnabled } from '../utils';
|
||||
|
||||
const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
courseId,
|
||||
@@ -51,6 +53,7 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
const { setCurrentPageKey } = useUnitSidebarContext();
|
||||
|
||||
// Useful to reload iframe
|
||||
const [iframeKey, setIframeKey] = useState(0);
|
||||
@@ -175,8 +178,13 @@ const XBlockContainerIframe: FC<XBlockContainerIframeProps> = ({
|
||||
};
|
||||
|
||||
const handleOpenManageTagsModal = (id: string) => {
|
||||
setConfigureXBlockId(id);
|
||||
openManageTagsModal();
|
||||
if (isUnitPageNewDesignEnabled()) {
|
||||
setCurrentPageKey('align', id);
|
||||
} else {
|
||||
// Legacy manage tags modal
|
||||
setConfigureXBlockId(id);
|
||||
openManageTagsModal();
|
||||
}
|
||||
};
|
||||
|
||||
const handleShowProcessingNotification = (variant: string) => {
|
||||
|
||||
@@ -285,7 +285,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.icon-with-border-chapter {
|
||||
.icon-with-border-section {
|
||||
border: 1px solid var(--content-library-container-section-color);
|
||||
|
||||
.pgn__icon {
|
||||
@@ -293,7 +293,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.icon-with-border-sequential {
|
||||
.icon-with-border-subsection {
|
||||
border: 1px solid var(--content-library-container-subsection-color);
|
||||
|
||||
.pgn__icon {
|
||||
@@ -301,7 +301,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.icon-with-border-vertical {
|
||||
.icon-with-border-unit {
|
||||
border: 1px solid var(--content-library-container-unit-color);
|
||||
|
||||
.pgn__icon {
|
||||
|
||||
27
src/generic/sidebar/AlignSidebar.tsx
Normal file
27
src/generic/sidebar/AlignSidebar.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { ContentTagsDrawer } from '@src/content-tags-drawer';
|
||||
import { SchoolOutline } from '@openedx/paragon/icons';
|
||||
import { SidebarTitle } from './SidebarTitle';
|
||||
|
||||
export interface AlignSidebarProps {
|
||||
contentId: string;
|
||||
title: string;
|
||||
onBackBtnClick?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sidebar that renders Align Sidebar (manage tags sidebar)
|
||||
* for the given content.
|
||||
*/
|
||||
export const AlignSidebar = ({ contentId, title, onBackBtnClick }: AlignSidebarProps) => (
|
||||
<div>
|
||||
<SidebarTitle
|
||||
title={title}
|
||||
icon={SchoolOutline}
|
||||
onBackBtnClick={onBackBtnClick}
|
||||
/>
|
||||
<ContentTagsDrawer
|
||||
id={contentId}
|
||||
variant="component"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
Reference in New Issue
Block a user