Skip to content

Commit 1c3610e

Browse files
feat: [FC-0056] create course outline sidebar (openedx#1375)
1 parent 796bbef commit 1c3610e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1891
-367
lines changed

package-lock.json

Lines changed: 4 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"@fortawesome/free-solid-svg-icons": "5.15.4",
4545
"@fortawesome/react-fontawesome": "^0.1.4",
4646
"@openedx/frontend-plugin-framework": "^1.0.2",
47-
"@openedx/paragon": "^22.1.1",
47+
"@openedx/paragon": "^22.3.0",
4848
"@popperjs/core": "2.11.8",
4949
"@reduxjs/toolkit": "1.8.1",
5050
"classnames": "2.3.2",

src/course-home/data/__snapshots__/redux.test.js.snap

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ Object {
1414
},
1515
"courseware": Object {
1616
"courseId": null,
17+
"courseOutline": Object {},
18+
"courseOutlineShouldUpdate": false,
19+
"courseOutlineStatus": "loading",
1720
"courseStatus": "loading",
21+
"coursewareOutlineSidebarSettings": Object {},
1822
"sequenceId": null,
1923
"sequenceMightBeUnit": false,
2024
"sequenceStatus": "loading",
@@ -402,7 +406,11 @@ Object {
402406
},
403407
"courseware": Object {
404408
"courseId": null,
409+
"courseOutline": Object {},
410+
"courseOutlineShouldUpdate": false,
411+
"courseOutlineStatus": "loading",
405412
"courseStatus": "loading",
413+
"coursewareOutlineSidebarSettings": Object {},
406414
"sequenceId": null,
407415
"sequenceMightBeUnit": false,
408416
"sequenceStatus": "loading",
@@ -671,7 +679,11 @@ Object {
671679
},
672680
"courseware": Object {
673681
"courseId": null,
682+
"courseOutline": Object {},
683+
"courseOutlineShouldUpdate": false,
684+
"courseOutlineStatus": "loading",
674685
"courseStatus": "loading",
686+
"coursewareOutlineSidebarSettings": Object {},
675687
"sequenceId": null,
676688
"sequenceMightBeUnit": false,
677689
"sequenceStatus": "loading",

src/course-home/outline-tab/SequenceLink.jsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import React from 'react';
21
import PropTypes from 'prop-types';
32
import classNames from 'classnames';
43
import { Link } from 'react-router-dom';
@@ -96,15 +95,15 @@ const SequenceLink = ({
9695
icon={fasCheckCircle}
9796
fixedWidth
9897
className="float-left text-success mt-1"
99-
aria-hidden="true"
98+
aria-hidden={complete}
10099
title={intl.formatMessage(messages.completedAssignment)}
101100
/>
102101
) : (
103102
<FontAwesomeIcon
104103
icon={farCheckCircle}
105104
fixedWidth
106105
className="float-left text-gray-400 mt-1"
107-
aria-hidden="true"
106+
aria-hidden={complete}
108107
title={intl.formatMessage(messages.incompleteAssignment)}
109108
/>
110109
)}
@@ -118,14 +117,14 @@ const SequenceLink = ({
118117
</div>
119118
</div>
120119
{hideFromTOC && (
121-
<div className="row w-100 my-2 mx-4 pl-3">
122-
<span className="small d-flex">
123-
<Icon className="mr-2" src={Block} data-testid="hide-from-toc-sequence-link-icon" />
124-
<span data-testid="hide-from-toc-sequence-link-text">
125-
{intl.formatMessage(messages.hiddenSequenceLink)}
120+
<div className="row w-100 my-2 mx-4 pl-3">
121+
<span className="small d-flex">
122+
<Icon className="mr-2" src={Block} data-testid="hide-from-toc-sequence-link-icon" />
123+
<span data-testid="hide-from-toc-sequence-link-text">
124+
{intl.formatMessage(messages.hiddenSequenceLink)}
125+
</span>
126126
</span>
127-
</span>
128-
</div>
127+
</div>
129128
)}
130129
<div className="row w-100 m-0 ml-3 pl-3">
131130
<small className="text-body pl-2">

src/courseware/course/Course.jsx

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
1-
import React, { useEffect, useState } from 'react';
1+
import { useEffect, useState } from 'react';
22
import PropTypes from 'prop-types';
33
import { Helmet } from 'react-helmet';
4-
import { useDispatch } from 'react-redux';
4+
import { useDispatch, useSelector } from 'react-redux';
55
import { getConfig } from '@edx/frontend-platform';
66
import { breakpoints, useWindowSize } from '@openedx/paragon';
77

8-
import { AlertList } from '../../generic/user-messages';
9-
10-
import Sequence from './sequence';
11-
12-
import { CelebrationModal, shouldCelebrateOnSectionLoad, WeeklyGoalCelebrationModal } from './celebration';
8+
import { AlertList } from '@src/generic/user-messages';
9+
import { useModel } from '@src/generic/model-store';
10+
import { getCoursewareOutlineSidebarSettings } from '../data/selectors';
11+
import { Trigger as CourseOutlineTrigger } from './sidebar/sidebars/course-outline';
1312
import Chat from './chat/Chat';
14-
import ContentTools from './content-tools';
15-
import CourseBreadcrumbs from './CourseBreadcrumbs';
1613
import SidebarProvider from './sidebar/SidebarContextProvider';
1714
import SidebarTriggers from './sidebar/SidebarTriggers';
1815
import NewSidebarProvider from './new-sidebar/SidebarContextProvider';
1916
import NewSidebarTriggers from './new-sidebar/SidebarTriggers';
20-
21-
import { useModel } from '../../generic/model-store';
17+
import { CelebrationModal, shouldCelebrateOnSectionLoad, WeeklyGoalCelebrationModal } from './celebration';
18+
import CourseBreadcrumbs from './CourseBreadcrumbs';
19+
import ContentTools from './content-tools';
20+
import Sequence from './sequence';
2221

2322
const Course = ({
2423
courseId,
@@ -37,7 +36,8 @@ const Course = ({
3736
} = useModel('courseHomeMeta', courseId);
3837
const sequence = useModel('sequences', sequenceId);
3938
const section = useModel('sections', sequence ? sequence.sectionId : null);
40-
const navigationDisabled = sequence?.navigationDisabled ?? false;
39+
const { enableNavigationSidebar } = useSelector(getCoursewareOutlineSidebarSettings);
40+
const navigationDisabled = enableNavigationSidebar || (sequence?.navigationDisabled ?? false);
4141

4242
const pageTitleBreadCrumbs = [
4343
sequence,
@@ -54,7 +54,6 @@ const Course = ({
5454
const [weeklyGoalCelebrationOpen, setWeeklyGoalCelebrationOpen] = useState(
5555
celebrations && !celebrations.streakLengthToCelebrate && celebrations.weeklyGoal,
5656
);
57-
const shouldDisplayTriggers = windowWidth >= breakpoints.small.minWidth;
5857
const shouldDisplayChat = windowWidth >= breakpoints.medium.minWidth;
5958
const daysPerWeek = course?.courseGoals?.selectedGoal?.daysPerWeek;
6059

@@ -76,7 +75,7 @@ const Course = ({
7675
<Helmet>
7776
<title>{`${pageTitleBreadCrumbs.join(' | ')} | ${getConfig().SITE_NAME}`}</title>
7877
</Helmet>
79-
<div className="position-relative d-flex align-items-center mb-4 mt-1">
78+
<div className="position-relative d-flex align-items-xl-center mb-4 mt-1 flex-column flex-xl-row">
8079
{navigationDisabled || (
8180
<>
8281
<CourseBreadcrumbs
@@ -100,11 +99,10 @@ const Course = ({
10099
/>
101100
</>
102101
)}
103-
{shouldDisplayTriggers && (
104-
<>
105-
{isNewDiscussionSidebarViewEnabled ? <NewSidebarTriggers /> : <SidebarTriggers /> }
106-
</>
107-
)}
102+
<div className="w-100 d-flex align-items-center">
103+
<CourseOutlineTrigger isMobileView />
104+
{isNewDiscussionSidebarViewEnabled ? <NewSidebarTriggers /> : <SidebarTriggers /> }
105+
</div>
108106
</div>
109107

110108
<AlertList topic="sequence" />

src/courseware/course/Course.test.jsx

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Factory } from 'rosie';
55
import { breakpoints } from '@openedx/paragon';
66

77
import {
8-
act, fireEvent, getByRole, initializeTestStore, loadUnit, render, screen, waitFor,
8+
fireEvent, getByRole, initializeTestStore, loadUnit, render, screen, waitFor,
99
} from '../../setupTest';
1010
import * as celebrationUtils from './celebration/utils';
1111
import { handleNextSectionCelebration } from './celebration';
@@ -59,7 +59,7 @@ describe('Course', () => {
5959

6060
it('loads learning sequence', async () => {
6161
render(<Course {...mockData} />, { wrapWithRouter: true });
62-
expect(screen.getByRole('navigation', { name: 'breadcrumb' })).toBeInTheDocument();
62+
expect(screen.queryByRole('navigation', { name: 'breadcrumb' })).not.toBeInTheDocument();
6363
expect(await screen.findByText('Loading learning sequence...')).toBeInTheDocument();
6464

6565
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
@@ -142,27 +142,32 @@ describe('Course', () => {
142142

143143
const notificationTrigger = screen.getByRole('button', { name: /Show notification tray/i });
144144
expect(notificationTrigger).toBeInTheDocument();
145-
expect(notificationTrigger.parentNode).not.toHaveClass('mt-3', { exact: true });
145+
expect(notificationTrigger.parentNode).not.toHaveClass('sidebar-active', { exact: true });
146146
fireEvent.click(notificationTrigger);
147-
expect(notificationTrigger.parentNode).toHaveClass('mt-3');
147+
expect(notificationTrigger.parentNode).toHaveClass('sidebar-active');
148148
});
149149

150150
it('handles click to open/close discussions sidebar', async () => {
151151
await setupDiscussionSidebar();
152-
const discussionsTrigger = await screen.getByRole('button', { name: /Show discussions tray/i });
153-
const discussionsSideBar = await waitFor(() => screen.findByTestId('sidebar-DISCUSSIONS'));
154152

155-
expect(discussionsSideBar).not.toHaveClass('d-none');
153+
await waitFor(() => {
154+
expect(screen.getByTestId('sidebar-DISCUSSIONS')).toBeInTheDocument();
155+
expect(screen.getByTestId('sidebar-DISCUSSIONS')).not.toHaveClass('d-none');
156+
});
157+
158+
const discussionsTrigger = await screen.getByRole('button', { name: /Show discussions tray/i });
159+
expect(discussionsTrigger).toBeInTheDocument();
160+
fireEvent.click(discussionsTrigger);
156161

157-
await act(async () => {
158-
fireEvent.click(discussionsTrigger);
162+
await waitFor(() => {
163+
expect(screen.queryByTestId('sidebar-DISCUSSIONS')).not.toBeInTheDocument();
159164
});
160-
await expect(discussionsSideBar).toHaveClass('d-none');
161165

162-
await act(async () => {
163-
fireEvent.click(discussionsTrigger);
166+
fireEvent.click(discussionsTrigger);
167+
168+
await waitFor(() => {
169+
expect(screen.queryByTestId('sidebar-DISCUSSIONS')).toBeInTheDocument();
164170
});
165-
await expect(discussionsSideBar).not.toHaveClass('d-none');
166171
});
167172

168173
it('displays discussions sidebar when unit changes', async () => {
@@ -192,8 +197,9 @@ describe('Course', () => {
192197
it('handles click to open/close notification tray', async () => {
193198
await setupDiscussionSidebar();
194199
const notificationShowButton = await screen.findByRole('button', { name: /Show notification tray/i });
195-
expect(screen.queryByRole('region', { name: /notification tray/i })).toHaveClass('d-none');
200+
expect(screen.queryByRole('region', { name: /notification tray/i })).not.toBeInTheDocument();
196201
fireEvent.click(notificationShowButton);
202+
expect(screen.queryByRole('region', { name: /notification tray/i })).toBeInTheDocument();
197203
expect(screen.queryByRole('region', { name: /notification tray/i })).not.toHaveClass('d-none');
198204
});
199205

@@ -204,7 +210,9 @@ describe('Course', () => {
204210
{ type: 'vertical' },
205211
{ courseId: courseMetadata.id },
206212
));
207-
const testStore = await initializeTestStore({ courseMetadata, unitBlocks }, false);
213+
const testStore = await initializeTestStore({
214+
courseMetadata, unitBlocks, enableNavigationSidebar: { enable_navigation_sidebar: false },
215+
}, false);
208216
const { courseware, models } = testStore.getState();
209217
const { courseId, sequenceId } = courseware;
210218
const testData = {

src/courseware/course/CourseBreadcrumbs.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ const CourseBreadcrumbs = ({
154154
}, [courseStatus, sequenceStatus, allSequencesInSections]);
155155

156156
return (
157-
<nav aria-label="breadcrumb" className="d-inline-block col-sm-10">
157+
<nav aria-label="breadcrumb" className="d-inline-block col-sm-10 mb-3">
158158
<ol className="list-unstyled d-flex flex-nowrap align-items-center m-0">
159159
<li className="list-unstyled col-auto m-0 p-0">
160160
<Link

0 commit comments

Comments
 (0)