Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/course-outline/outline-sidebar/OutlineSidebarContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ interface OutlineSidebarContextData {
currentFlow?: OutlineFlow;
startCurrentFlow: (flow: OutlineFlow) => void;
stopCurrentFlow: () => void;
currentTabKey?: string;
setCurrentTabKey: (tabKey: string | undefined) => void;
isOpen: boolean;
open: () => void;
toggle: () => void;
Expand All @@ -42,6 +44,15 @@ interface OutlineSidebarContextData {
sectionId?: string,
index?: number,
) => void;
/**
* Opens the sidebar for a new container and keeps the current sidebar page
*/
openContainerSidebar: (
containerId: string,
subsectionId?: string,
sectionId?: string,
index?: number,
) => void;
clearSelection: () => void;
/** Stores last section that allows adding subsections inside it. */
lastEditableSection?: XBlock;
Expand Down Expand Up @@ -92,6 +103,7 @@ export const OutlineSidebarProvider = ({ children }: { children?: React.ReactNod
stopCurrentFlow,
] = useToggleWithValue<OutlineFlow>();
const [isOpen, open, , toggle] = useToggle(true);
const [currentTabKey, setCurrentTabKey] = useState<string>();

/**
* Use this to store the selected container's information and should always contain full ancestor info.
Expand Down Expand Up @@ -119,6 +131,8 @@ export const OutlineSidebarProvider = ({ children }: { children?: React.ReactNod

const setCurrentPageKey = useCallback((pageKey: OutlineSidebarPageKeys) => {
setCurrentPageKeyState(pageKey);
// Reset tab
setCurrentTabKey(undefined);
stopCurrentFlow();
open();
}, [open, stopCurrentFlow]);
Expand All @@ -138,6 +152,20 @@ export const OutlineSidebarProvider = ({ children }: { children?: React.ReactNod
setCurrentPageKey('info');
}, [setSelectedContainerState, setCurrentPageKey]);

const openContainerSidebar = useCallback((
containerId: string,
subsectionId?: string,
sectionId?: string,
index?: number,
) => {
setSelectedContainerState({
currentId: containerId,
subsectionId,
sectionId,
index,
});
}, [setSelectedContainerState]);

const clearSelection = useCallback(() => {
setSelectedContainerState(undefined);
}, [selectedContainerState]);
Expand Down Expand Up @@ -193,12 +221,15 @@ export const OutlineSidebarProvider = ({ children }: { children?: React.ReactNod
currentFlow,
startCurrentFlow,
stopCurrentFlow,
currentTabKey,
setCurrentTabKey,
isOpen,
open,
toggle,
selectedContainerState,
setSelectedContainerState,
openContainerInfoSidebar,
openContainerSidebar,
clearSelection,
lastEditableSection,
lastEditableSubsection,
Expand All @@ -211,12 +242,15 @@ export const OutlineSidebarProvider = ({ children }: { children?: React.ReactNod
currentFlow,
startCurrentFlow,
stopCurrentFlow,
currentTabKey,
setCurrentTabKey,
isOpen,
open,
toggle,
selectedContainerState,
setSelectedContainerState,
openContainerInfoSidebar,
openContainerSidebar,
clearSelection,
lastEditableSection,
lastEditableSubsection,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { useCourseAuthoringContext } from '@src/CourseAuthoringContext';

import { useCourseDetails } from '@src/course-outline/data/apiHooks';
import messages from '../messages';
import { useOutlineSidebarContext } from '../OutlineSidebarContext';
import { useEffect } from 'react';

const DetailsTab = () => {
const intl = useIntl();
Expand Down Expand Up @@ -151,6 +153,14 @@ export const CourseInfoSidebar = () => {
const intl = useIntl();
const { courseId } = useCourseAuthoringContext();
const { data: courseDetails } = useCourseDetails(courseId);
const { currentTabKey, setCurrentTabKey } = useOutlineSidebarContext();

useEffect(() => {
if (!currentTabKey) {
// Set default Tab key
setCurrentTabKey('info');
}
}, [currentTabKey, setCurrentTabKey]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should store this in the URL or in the #hash at some point in the future? I generally find that better for web UI state like which tab/modal is active. No need to change now though.


return (
<>
Expand All @@ -163,6 +173,8 @@ export const CourseInfoSidebar = () => {
className="my-2 mx-n3.5"
id="course-info-tabs"
mountOnEnter
activeKey={currentTabKey}
onSelect={setCurrentTabKey}
>
<Tab eventKey="info" title={intl.formatMessage(messages.infoTabText)}>
<DetailsTab />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ describe('InfoSidebar component', () => {
.reply(200, data);
renderComponent();
expect(await screen.findByText('unit name')).toBeInTheDocument();
// The Details tab is not active by default (preview is); click it to load its content
await user.click(screen.getByRole('tab', { name: /Details/i }));
expect(await screen.findByText('Unit Content Summary')).toBeInTheDocument();
const btn = await screen.findByRole('button', { name: 'Publish Changes (Draft)' });
expect(btn).toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
import { useState } from 'react';
import { useEffect } from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Tab, Tabs } from '@openedx/paragon';
import { useNavigate } from 'react-router-dom';

import { getItemIcon } from '@src/generic/block-type-utils';

import { SidebarTitle } from '@src/generic/sidebar';

import { useCourseItemData } from '@src/course-outline/data/apiHooks';
import Loading from '@src/generic/Loading';
import { useCourseAuthoringContext } from '@src/CourseAuthoringContext';
import { useCourseOutlineContext } from '@src/course-outline/CourseOutlineContext';
import { useOutlineSidebarContext } from '@src/course-outline/outline-sidebar/OutlineSidebarContext';
import { getLibraryId } from '@src/generic/key-utils';
import { SectionSettings } from '@src/course-outline/outline-sidebar/info-sidebar/SectionSettings';
import { canMoveSection } from '@src/course-outline/drag-helper/utils';

import { InfoSection } from './InfoSection';
import messages from '../messages';
import { PublishButon } from './PublishButon';
import { canMoveSection } from '@src/course-outline/drag-helper/utils';

export const SectionSidebar = () => {
const intl = useIntl();
const navigate = useNavigate();
const [tab, setTab] = useState<'info' | 'settings'>('info');
const { clearSelection, selectedContainerState, setSelectedContainerState } = useOutlineSidebarContext();
const { sectionId = '', index } = selectedContainerState ?? {};
const { data: sectionData, isLoading } = useCourseItemData(sectionId);

const { openUnlinkModal } = useCourseAuthoringContext();
const {
openPublishModal,
Expand All @@ -34,6 +30,26 @@ export const SectionSidebar = () => {
updateSectionOrderByIndex,
openDeleteModal,
} = useCourseOutlineContext();
const {
clearSelection,
currentTabKey,
setCurrentTabKey,
selectedContainerState,
setSelectedContainerState,
} = useOutlineSidebarContext();
const availableTabs = {
info: 'info',
settings: 'settings',
};

useEffect(() => {
if (!currentTabKey || !Object.values(availableTabs).includes(currentTabKey)) {
// Set default Tab key
setCurrentTabKey('info');
}
}, [currentTabKey, setCurrentTabKey]);
const { sectionId = '', index } = selectedContainerState ?? {};
const { data: sectionData, isLoading } = useCourseItemData(sectionId);

const handlePublish = () => {
if (sectionData?.hasChanges) {
Expand Down Expand Up @@ -87,15 +103,15 @@ export const SectionSidebar = () => {
variant="tabs"
className="my-2 mx-n3.5"
id="add-content-tabs"
activeKey={tab}
onSelect={setTab}
activeKey={currentTabKey}
onSelect={setCurrentTabKey}
mountOnEnter
>
<Tab eventKey="info" title={intl.formatMessage(messages.infoTabText)}>
<Tab eventKey={availableTabs.info} title={intl.formatMessage(messages.infoTabText)}>
<InfoSection itemId={sectionId} />
</Tab>
<Tab
eventKey="settings"
eventKey={availableTabs.settings}
title={intl.formatMessage(messages.settingsTabText)}
>
<SectionSettings key={sectionId} sectionId={sectionId} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect } from 'react';
import { isEmpty } from 'lodash';

import { useIntl } from '@edx/frontend-platform/i18n';
Expand All @@ -24,10 +24,29 @@ import { SubsectionSettings } from './SubsectionSettings';
export const SubsectionSidebar = () => {
const intl = useIntl();
const navigate = useNavigate();
const [tab, setTab] = useState<'info' | 'settings'>('info');
const { clearSelection, selectedContainerState, setSelectedContainerState } = useOutlineSidebarContext();

const {
clearSelection,
currentTabKey,
setCurrentTabKey,
selectedContainerState,
setSelectedContainerState,
} = useOutlineSidebarContext();
const { subsectionId = '', index } = selectedContainerState ?? {};

const { data: subsectionData, isLoading } = useCourseItemData(subsectionId);

const availableTabs = {
info: 'info',
settings: 'settings',
};

useEffect(() => {
if (!currentTabKey || !Object.values(availableTabs).includes(currentTabKey)) {
// Set default Tab key
setCurrentTabKey('info');
}
}, [currentTabKey, setCurrentTabKey]);
const { data: section } = useCourseItemData<XBlock>(selectedContainerState?.sectionId);
const { openUnlinkModal } = useCourseAuthoringContext();
const {
Expand Down Expand Up @@ -140,14 +159,14 @@ export const SubsectionSidebar = () => {
variant="tabs"
className="my-2 mx-n3.5"
id="add-content-tabs"
activeKey={tab}
onSelect={setTab}
activeKey={currentTabKey}
onSelect={setCurrentTabKey}
mountOnEnter
>
<Tab eventKey="info" title={intl.formatMessage(messages.infoTabText)}>
<Tab eventKey={availableTabs.info} title={intl.formatMessage(messages.infoTabText)}>
<InfoSection itemId={subsectionId} />
</Tab>
<Tab eventKey="settings" title={intl.formatMessage(messages.settingsTabText)}>
<Tab eventKey={availableTabs.settings} title={intl.formatMessage(messages.settingsTabText)}>
{/* key is required to reset local state of tab */}
<SubsectionSettings key={subsectionId} subsectionId={subsectionId} />
</Tab>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ describe('UnitSidebar', () => {
selectedContainerState: { currentId: 'unit-1', sectionId: 's1', subsectionId: 'ss1' },
clearSelection: jest.fn(),
setSelectedContainerState: jest.fn(),
currentTabKey: 'info',
setCurrentTabKey: jest.fn(),
});
authoring.useCourseAuthoringContext.mockReturnValue({
openPublishModal: jest.fn(),
Expand Down Expand Up @@ -97,6 +99,8 @@ describe('UnitSidebar', () => {
selectedContainerState: { currentId: 'unit-2', sectionId: 's1', subsectionId: 'ss1' },
clearSelection: jest.fn(),
setSelectedContainerState: jest.fn(),
currentTabKey: 'info',
setCurrentTabKey: jest.fn(),
});
apiHooks.useCourseItemData.mockReturnValue({
data: {
Expand All @@ -121,6 +125,8 @@ describe('UnitSidebar', () => {
selectedContainerState: { currentId: 'unit-3', sectionId: 's1', subsectionId: 'ss1' },
clearSelection: jest.fn(),
setSelectedContainerState: jest.fn(),
currentTabKey: 'preview',
setCurrentTabKey: jest.fn(),
});
apiHooks.useCourseItemData.mockReturnValue({
data: {
Expand All @@ -143,6 +149,8 @@ describe('UnitSidebar', () => {
selectedContainerState: { currentId: 'unit-4', sectionId: 's1', subsectionId: 'ss1' },
clearSelection: jest.fn(),
setSelectedContainerState: jest.fn(),
currentTabKey: 'settings',
setCurrentTabKey: jest.fn(),
});
apiHooks.useCourseItemData.mockReturnValue({
data: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext, useState } from 'react';
import { useEffect, useContext } from 'react';
import { isEmpty } from 'lodash';

import { useIntl } from '@edx/frontend-platform/i18n';
Expand Down Expand Up @@ -69,13 +69,31 @@ const UnitSettingsTab = ({ unitId }: Props) => {
export const UnitSidebar = () => {
const intl = useIntl();
const navigate = useNavigate();
const [tab, setTab] = useState<'preview' | 'info' | 'settings'>('info');
const { selectedContainerState, clearSelection, setSelectedContainerState } = useOutlineSidebarContext();
const {
selectedContainerState,
clearSelection,
currentTabKey,
setCurrentTabKey,
setSelectedContainerState,
} = useOutlineSidebarContext();
const {
currentId: unitId = /* istanbul ignore next */ '',
index,
} = selectedContainerState ?? {};
const { data: unitData, isPending } = useCourseItemData(unitId);
const availableTabs = {
preview: 'preview',
info: 'info',
settings: 'settings',
};

useEffect(() => {
if (!currentTabKey || !Object.values(availableTabs).includes(currentTabKey)) {
// Set default Tab key
setCurrentTabKey('preview');
}
}, [currentTabKey, setCurrentTabKey]);

const { data: section } = useCourseItemData<XBlock>(selectedContainerState?.sectionId);
const { data: subsection } = useCourseItemData<XBlock>(selectedContainerState?.subsectionId);
const { getUnitUrl, courseId, openUnlinkModal } = useCourseAuthoringContext();
Expand Down Expand Up @@ -239,12 +257,12 @@ export const UnitSidebar = () => {
variant="tabs"
className="my-2 mx-n3.5"
id="unit-content-tabs"
activeKey={tab}
onSelect={setTab}
activeKey={currentTabKey}
onSelect={setCurrentTabKey}
mountOnEnter
>
<Tab
eventKey="preview"
eventKey={availableTabs.preview}
title={intl.formatMessage(messages.previewTabText)}
// To make sure that data is fresh
unmountOnExit
Expand All @@ -260,10 +278,10 @@ export const UnitSidebar = () => {
/>
</IframeProvider>
</Tab>
<Tab eventKey="info" title={intl.formatMessage(messages.infoTabText)}>
<Tab eventKey={availableTabs.info} title={intl.formatMessage(messages.infoTabText)}>
<InfoSection itemId={unitId} />
</Tab>
<Tab eventKey="settings" title={intl.formatMessage(messages.settingsTabText)}>
<Tab eventKey={availableTabs.settings} title={intl.formatMessage(messages.settingsTabText)}>
<UnitSettingsTab unitId={unitId} />
</Tab>
</Tabs>
Expand Down
Loading