Skip to content

Commit 9c5a4e4

Browse files
authored
fix: Resolve different issues and bugs (#2998)
Fixes: - #2826 (comment) - #2791 (comment) - #2907 (comment) - #2665 (comment) - #2629 (comment) - #2633 (comment)
1 parent 9c8e672 commit 9c5a4e4

25 files changed

Lines changed: 466 additions & 59 deletions

src/course-outline/outline-sidebar/OutlineSidebarContext.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ interface OutlineSidebarContextData {
3131
currentFlow?: OutlineFlow;
3232
startCurrentFlow: (flow: OutlineFlow) => void;
3333
stopCurrentFlow: () => void;
34+
currentTabKey?: string;
35+
setCurrentTabKey: (tabKey: string | undefined) => void;
3436
isOpen: boolean;
3537
open: () => void;
3638
toggle: () => void;
@@ -42,6 +44,15 @@ interface OutlineSidebarContextData {
4244
sectionId?: string,
4345
index?: number,
4446
) => void;
47+
/**
48+
* Opens the sidebar for a new container and keeps the current sidebar page
49+
*/
50+
openContainerSidebar: (
51+
containerId: string,
52+
subsectionId?: string,
53+
sectionId?: string,
54+
index?: number,
55+
) => void;
4556
clearSelection: () => void;
4657
/** Stores last section that allows adding subsections inside it. */
4758
lastEditableSection?: XBlock;
@@ -92,6 +103,7 @@ export const OutlineSidebarProvider = ({ children }: { children?: React.ReactNod
92103
stopCurrentFlow,
93104
] = useToggleWithValue<OutlineFlow>();
94105
const [isOpen, open, , toggle] = useToggle(true);
106+
const [currentTabKey, setCurrentTabKey] = useState<string>();
95107

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

120132
const setCurrentPageKey = useCallback((pageKey: OutlineSidebarPageKeys) => {
121133
setCurrentPageKeyState(pageKey);
134+
// Reset tab
135+
setCurrentTabKey(undefined);
122136
stopCurrentFlow();
123137
open();
124138
}, [open, stopCurrentFlow]);
@@ -138,6 +152,20 @@ export const OutlineSidebarProvider = ({ children }: { children?: React.ReactNod
138152
setCurrentPageKey('info');
139153
}, [setSelectedContainerState, setCurrentPageKey]);
140154

155+
const openContainerSidebar = useCallback((
156+
containerId: string,
157+
subsectionId?: string,
158+
sectionId?: string,
159+
index?: number,
160+
) => {
161+
setSelectedContainerState({
162+
currentId: containerId,
163+
subsectionId,
164+
sectionId,
165+
index,
166+
});
167+
}, [setSelectedContainerState]);
168+
141169
const clearSelection = useCallback(() => {
142170
setSelectedContainerState(undefined);
143171
}, [selectedContainerState]);
@@ -193,12 +221,15 @@ export const OutlineSidebarProvider = ({ children }: { children?: React.ReactNod
193221
currentFlow,
194222
startCurrentFlow,
195223
stopCurrentFlow,
224+
currentTabKey,
225+
setCurrentTabKey,
196226
isOpen,
197227
open,
198228
toggle,
199229
selectedContainerState,
200230
setSelectedContainerState,
201231
openContainerInfoSidebar,
232+
openContainerSidebar,
202233
clearSelection,
203234
lastEditableSection,
204235
lastEditableSubsection,
@@ -211,12 +242,15 @@ export const OutlineSidebarProvider = ({ children }: { children?: React.ReactNod
211242
currentFlow,
212243
startCurrentFlow,
213244
stopCurrentFlow,
245+
currentTabKey,
246+
setCurrentTabKey,
214247
isOpen,
215248
open,
216249
toggle,
217250
selectedContainerState,
218251
setSelectedContainerState,
219252
openContainerInfoSidebar,
253+
openContainerSidebar,
220254
clearSelection,
221255
lastEditableSection,
222256
lastEditableSubsection,

src/course-outline/outline-sidebar/info-sidebar/CourseInfoSidebar.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import { useCourseAuthoringContext } from '@src/CourseAuthoringContext';
1818

1919
import { useCourseDetails } from '@src/course-outline/data/apiHooks';
2020
import messages from '../messages';
21+
import { useOutlineSidebarContext } from '../OutlineSidebarContext';
22+
import { useEffect } from 'react';
2123

2224
const DetailsTab = () => {
2325
const intl = useIntl();
@@ -151,6 +153,14 @@ export const CourseInfoSidebar = () => {
151153
const intl = useIntl();
152154
const { courseId } = useCourseAuthoringContext();
153155
const { data: courseDetails } = useCourseDetails(courseId);
156+
const { currentTabKey, setCurrentTabKey } = useOutlineSidebarContext();
157+
158+
useEffect(() => {
159+
if (!currentTabKey) {
160+
// Set default Tab key
161+
setCurrentTabKey('info');
162+
}
163+
}, [currentTabKey, setCurrentTabKey]);
154164

155165
return (
156166
<>
@@ -163,6 +173,8 @@ export const CourseInfoSidebar = () => {
163173
className="my-2 mx-n3.5"
164174
id="course-info-tabs"
165175
mountOnEnter
176+
activeKey={currentTabKey}
177+
onSelect={setCurrentTabKey}
166178
>
167179
<Tab eventKey="info" title={intl.formatMessage(messages.infoTabText)}>
168180
<DetailsTab />

src/course-outline/outline-sidebar/info-sidebar/InfoSidebar.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ describe('InfoSidebar component', () => {
198198
.reply(200, data);
199199
renderComponent();
200200
expect(await screen.findByText('unit name')).toBeInTheDocument();
201+
// The Details tab is not active by default (preview is); click it to load its content
202+
await user.click(screen.getByRole('tab', { name: /Details/i }));
201203
expect(await screen.findByText('Unit Content Summary')).toBeInTheDocument();
202204
const btn = await screen.findByRole('button', { name: 'Publish Changes (Draft)' });
203205
expect(btn).toBeInTheDocument();

src/course-outline/outline-sidebar/info-sidebar/SectionInfoSidebar.tsx

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
1-
import { useState } from 'react';
1+
import { useEffect } from 'react';
22
import { useIntl } from '@edx/frontend-platform/i18n';
33
import { Tab, Tabs } from '@openedx/paragon';
44
import { useNavigate } from 'react-router-dom';
55

66
import { getItemIcon } from '@src/generic/block-type-utils';
7-
87
import { SidebarTitle } from '@src/generic/sidebar';
9-
108
import { useCourseItemData } from '@src/course-outline/data/apiHooks';
119
import Loading from '@src/generic/Loading';
1210
import { useCourseAuthoringContext } from '@src/CourseAuthoringContext';
1311
import { useCourseOutlineContext } from '@src/course-outline/CourseOutlineContext';
1412
import { useOutlineSidebarContext } from '@src/course-outline/outline-sidebar/OutlineSidebarContext';
1513
import { getLibraryId } from '@src/generic/key-utils';
1614
import { SectionSettings } from '@src/course-outline/outline-sidebar/info-sidebar/SectionSettings';
15+
import { canMoveSection } from '@src/course-outline/drag-helper/utils';
16+
1717
import { InfoSection } from './InfoSection';
1818
import messages from '../messages';
1919
import { PublishButon } from './PublishButon';
20-
import { canMoveSection } from '@src/course-outline/drag-helper/utils';
2120

2221
export const SectionSidebar = () => {
2322
const intl = useIntl();
2423
const navigate = useNavigate();
25-
const [tab, setTab] = useState<'info' | 'settings'>('info');
26-
const { clearSelection, selectedContainerState, setSelectedContainerState } = useOutlineSidebarContext();
27-
const { sectionId = '', index } = selectedContainerState ?? {};
28-
const { data: sectionData, isLoading } = useCourseItemData(sectionId);
24+
2925
const { openUnlinkModal } = useCourseAuthoringContext();
3026
const {
3127
openPublishModal,
@@ -34,6 +30,26 @@ export const SectionSidebar = () => {
3430
updateSectionOrderByIndex,
3531
openDeleteModal,
3632
} = useCourseOutlineContext();
33+
const {
34+
clearSelection,
35+
currentTabKey,
36+
setCurrentTabKey,
37+
selectedContainerState,
38+
setSelectedContainerState,
39+
} = useOutlineSidebarContext();
40+
const availableTabs = {
41+
info: 'info',
42+
settings: 'settings',
43+
};
44+
45+
useEffect(() => {
46+
if (!currentTabKey || !Object.values(availableTabs).includes(currentTabKey)) {
47+
// Set default Tab key
48+
setCurrentTabKey('info');
49+
}
50+
}, [currentTabKey, setCurrentTabKey]);
51+
const { sectionId = '', index } = selectedContainerState ?? {};
52+
const { data: sectionData, isLoading } = useCourseItemData(sectionId);
3753

3854
const handlePublish = () => {
3955
if (sectionData?.hasChanges) {
@@ -87,15 +103,15 @@ export const SectionSidebar = () => {
87103
variant="tabs"
88104
className="my-2 mx-n3.5"
89105
id="add-content-tabs"
90-
activeKey={tab}
91-
onSelect={setTab}
106+
activeKey={currentTabKey}
107+
onSelect={setCurrentTabKey}
92108
mountOnEnter
93109
>
94-
<Tab eventKey="info" title={intl.formatMessage(messages.infoTabText)}>
110+
<Tab eventKey={availableTabs.info} title={intl.formatMessage(messages.infoTabText)}>
95111
<InfoSection itemId={sectionId} />
96112
</Tab>
97113
<Tab
98-
eventKey="settings"
114+
eventKey={availableTabs.settings}
99115
title={intl.formatMessage(messages.settingsTabText)}
100116
>
101117
<SectionSettings key={sectionId} sectionId={sectionId} />

src/course-outline/outline-sidebar/info-sidebar/SubsectionInfoSidebar.tsx

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react';
1+
import { useEffect } from 'react';
22
import { isEmpty } from 'lodash';
33

44
import { useIntl } from '@edx/frontend-platform/i18n';
@@ -24,10 +24,29 @@ import { SubsectionSettings } from './SubsectionSettings';
2424
export const SubsectionSidebar = () => {
2525
const intl = useIntl();
2626
const navigate = useNavigate();
27-
const [tab, setTab] = useState<'info' | 'settings'>('info');
28-
const { clearSelection, selectedContainerState, setSelectedContainerState } = useOutlineSidebarContext();
27+
28+
const {
29+
clearSelection,
30+
currentTabKey,
31+
setCurrentTabKey,
32+
selectedContainerState,
33+
setSelectedContainerState,
34+
} = useOutlineSidebarContext();
2935
const { subsectionId = '', index } = selectedContainerState ?? {};
36+
3037
const { data: subsectionData, isLoading } = useCourseItemData(subsectionId);
38+
39+
const availableTabs = {
40+
info: 'info',
41+
settings: 'settings',
42+
};
43+
44+
useEffect(() => {
45+
if (!currentTabKey || !Object.values(availableTabs).includes(currentTabKey)) {
46+
// Set default Tab key
47+
setCurrentTabKey('info');
48+
}
49+
}, [currentTabKey, setCurrentTabKey]);
3150
const { data: section } = useCourseItemData<XBlock>(selectedContainerState?.sectionId);
3251
const { openUnlinkModal } = useCourseAuthoringContext();
3352
const {
@@ -140,14 +159,14 @@ export const SubsectionSidebar = () => {
140159
variant="tabs"
141160
className="my-2 mx-n3.5"
142161
id="add-content-tabs"
143-
activeKey={tab}
144-
onSelect={setTab}
162+
activeKey={currentTabKey}
163+
onSelect={setCurrentTabKey}
145164
mountOnEnter
146165
>
147-
<Tab eventKey="info" title={intl.formatMessage(messages.infoTabText)}>
166+
<Tab eventKey={availableTabs.info} title={intl.formatMessage(messages.infoTabText)}>
148167
<InfoSection itemId={subsectionId} />
149168
</Tab>
150-
<Tab eventKey="settings" title={intl.formatMessage(messages.settingsTabText)}>
169+
<Tab eventKey={availableTabs.settings} title={intl.formatMessage(messages.settingsTabText)}>
151170
{/* key is required to reset local state of tab */}
152171
<SubsectionSettings key={subsectionId} subsectionId={subsectionId} />
153172
</Tab>

src/course-outline/outline-sidebar/info-sidebar/UnitInfoSidebar.test.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ describe('UnitSidebar', () => {
5151
selectedContainerState: { currentId: 'unit-1', sectionId: 's1', subsectionId: 'ss1' },
5252
clearSelection: jest.fn(),
5353
setSelectedContainerState: jest.fn(),
54+
currentTabKey: 'info',
55+
setCurrentTabKey: jest.fn(),
5456
});
5557
authoring.useCourseAuthoringContext.mockReturnValue({
5658
openPublishModal: jest.fn(),
@@ -97,6 +99,8 @@ describe('UnitSidebar', () => {
9799
selectedContainerState: { currentId: 'unit-2', sectionId: 's1', subsectionId: 'ss1' },
98100
clearSelection: jest.fn(),
99101
setSelectedContainerState: jest.fn(),
102+
currentTabKey: 'info',
103+
setCurrentTabKey: jest.fn(),
100104
});
101105
apiHooks.useCourseItemData.mockReturnValue({
102106
data: {
@@ -121,6 +125,8 @@ describe('UnitSidebar', () => {
121125
selectedContainerState: { currentId: 'unit-3', sectionId: 's1', subsectionId: 'ss1' },
122126
clearSelection: jest.fn(),
123127
setSelectedContainerState: jest.fn(),
128+
currentTabKey: 'preview',
129+
setCurrentTabKey: jest.fn(),
124130
});
125131
apiHooks.useCourseItemData.mockReturnValue({
126132
data: {
@@ -143,6 +149,8 @@ describe('UnitSidebar', () => {
143149
selectedContainerState: { currentId: 'unit-4', sectionId: 's1', subsectionId: 'ss1' },
144150
clearSelection: jest.fn(),
145151
setSelectedContainerState: jest.fn(),
152+
currentTabKey: 'settings',
153+
setCurrentTabKey: jest.fn(),
146154
});
147155
apiHooks.useCourseItemData.mockReturnValue({
148156
data: {

src/course-outline/outline-sidebar/info-sidebar/UnitInfoSidebar.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useContext, useState } from 'react';
1+
import { useEffect, useContext } from 'react';
22
import { isEmpty } from 'lodash';
33

44
import { useIntl } from '@edx/frontend-platform/i18n';
@@ -69,13 +69,31 @@ const UnitSettingsTab = ({ unitId }: Props) => {
6969
export const UnitSidebar = () => {
7070
const intl = useIntl();
7171
const navigate = useNavigate();
72-
const [tab, setTab] = useState<'preview' | 'info' | 'settings'>('info');
73-
const { selectedContainerState, clearSelection, setSelectedContainerState } = useOutlineSidebarContext();
72+
const {
73+
selectedContainerState,
74+
clearSelection,
75+
currentTabKey,
76+
setCurrentTabKey,
77+
setSelectedContainerState,
78+
} = useOutlineSidebarContext();
7479
const {
7580
currentId: unitId = /* istanbul ignore next */ '',
7681
index,
7782
} = selectedContainerState ?? {};
7883
const { data: unitData, isPending } = useCourseItemData(unitId);
84+
const availableTabs = {
85+
preview: 'preview',
86+
info: 'info',
87+
settings: 'settings',
88+
};
89+
90+
useEffect(() => {
91+
if (!currentTabKey || !Object.values(availableTabs).includes(currentTabKey)) {
92+
// Set default Tab key
93+
setCurrentTabKey('preview');
94+
}
95+
}, [currentTabKey, setCurrentTabKey]);
96+
7997
const { data: section } = useCourseItemData<XBlock>(selectedContainerState?.sectionId);
8098
const { data: subsection } = useCourseItemData<XBlock>(selectedContainerState?.subsectionId);
8199
const { getUnitUrl, courseId, openUnlinkModal } = useCourseAuthoringContext();
@@ -239,12 +257,12 @@ export const UnitSidebar = () => {
239257
variant="tabs"
240258
className="my-2 mx-n3.5"
241259
id="unit-content-tabs"
242-
activeKey={tab}
243-
onSelect={setTab}
260+
activeKey={currentTabKey}
261+
onSelect={setCurrentTabKey}
244262
mountOnEnter
245263
>
246264
<Tab
247-
eventKey="preview"
265+
eventKey={availableTabs.preview}
248266
title={intl.formatMessage(messages.previewTabText)}
249267
// To make sure that data is fresh
250268
unmountOnExit
@@ -260,10 +278,10 @@ export const UnitSidebar = () => {
260278
/>
261279
</IframeProvider>
262280
</Tab>
263-
<Tab eventKey="info" title={intl.formatMessage(messages.infoTabText)}>
281+
<Tab eventKey={availableTabs.info} title={intl.formatMessage(messages.infoTabText)}>
264282
<InfoSection itemId={unitId} />
265283
</Tab>
266-
<Tab eventKey="settings" title={intl.formatMessage(messages.settingsTabText)}>
284+
<Tab eventKey={availableTabs.settings} title={intl.formatMessage(messages.settingsTabText)}>
267285
<UnitSettingsTab unitId={unitId} />
268286
</Tab>
269287
</Tabs>

0 commit comments

Comments
 (0)