Skip to content

Commit fa9194c

Browse files
feat: enhance course optimizer page design in studio
1 parent 92c3a98 commit fa9194c

File tree

9 files changed

+504
-129
lines changed

9 files changed

+504
-129
lines changed

src/data/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export async function getCourseDetail(courseId: string, username: string) {
3232
*/
3333
export const waffleFlagDefaults = {
3434
enableCourseOptimizer: false,
35+
enableCourseOptimizerCheckPrevRunLinks: false,
3536
useNewHomePage: true,
3637
useNewCustomPages: true,
3738
useNewScheduleDetailsPage: true,

src/optimizer-page/CourseOptimizerPage.tsx

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import {
55
import { useDispatch, useSelector } from 'react-redux';
66
import { useIntl } from '@edx/frontend-platform/i18n';
77
import {
8-
Badge, Container, Layout, Button, Card,
8+
Badge, Container, Layout, Button, Card, Spinner,
99
} from '@openedx/paragon';
1010
import { Helmet } from 'react-helmet';
1111

1212
import CourseStepper from '../generic/course-stepper';
1313
import ConnectionErrorAlert from '../generic/ConnectionErrorAlert';
14-
import SubHeader from '../generic/sub-header/SubHeader';
1514
import { RequestFailureStatuses } from '../data/constants';
1615
import messages from './messages';
1716
import {
@@ -53,7 +52,6 @@ const CourseOptimizerPage: FC<{ courseId: string }> = ({ courseId }) => {
5352
const linkCheckResult = useSelector(getLinkCheckResult);
5453
const lastScannedAt = useSelector(getLastScannedAt);
5554
const { msg: errorMessage } = useSelector(getError);
56-
const isShowExportButton = !linkCheckInProgress || errorMessage;
5755
const isLoadingDenied = (RequestFailureStatuses as string[]).includes(loadingStatus);
5856
const isSavingDenied = (RequestFailureStatuses as string[]).includes(savingStatus);
5957
const interval = useRef<number | undefined>(undefined);
@@ -136,45 +134,50 @@ const CourseOptimizerPage: FC<{ courseId: string }> = ({ courseId }) => {
136134
<Container size="xl" className="mt-4 px-4 export">
137135
<section className="setting-items mb-4">
138136
<Layout
139-
lg={[{ span: 9 }, { span: 3 }]}
140-
md={[{ span: 9 }, { span: 3 }]}
141-
sm={[{ span: 9 }, { span: 3 }]}
142-
xs={[{ span: 9 }, { span: 3 }]}
143-
xl={[{ span: 9 }, { span: 3 }]}
137+
lg={[{ span: 12 }, { span: 0 }]}
144138
>
145139
<Layout.Element>
146140
<article>
147-
<SubHeader
148-
hideBorder
149-
title={
150-
(
151-
<span style={{ display: 'inline-flex', alignItems: 'center' }}>
152-
{intl.formatMessage(messages.headingTitle)}
153-
<Badge variant="primary" className="ml-2" style={{ fontSize: 'large' }}>{intl.formatMessage(messages.new)}</Badge>
154-
</span>
155-
)
156-
}
157-
subtitle={intl.formatMessage(messages.headingSubtitle)}
158-
/>
159-
<Card>
141+
<div className="d-flex flex-wrap justify-content-between align-items-center mb-3 px-3 py-3">
142+
<div>
143+
<p className="small text-muted mb-1">Tools</p>
144+
<div className="d-flex align-items-center">
145+
<h1 className="h2 mb-0 mr-3">{intl.formatMessage(messages.headingTitle)}</h1>
146+
<Badge variant="dark" className="ml-2">{intl.formatMessage(messages.new)}</Badge>
147+
</div>
148+
</div>
149+
<Button
150+
variant="primary"
151+
size="md"
152+
className="px-4 rounded-0 scan-course-btn"
153+
onClick={() => dispatch(startLinkCheck(courseId))}
154+
disabled={linkCheckInProgress && !errorMessage}
155+
>
156+
{linkCheckInProgress && !errorMessage ? (
157+
<>
158+
<Spinner
159+
animation="border"
160+
size="sm"
161+
className="mr-2"
162+
style={{ width: '1rem', height: '1rem' }}
163+
/>
164+
{intl.formatMessage(messages.buttonTitle)}
165+
</>
166+
) : (
167+
intl.formatMessage(messages.buttonTitle)
168+
)}
169+
</Button>
170+
</div>
171+
<Card style={{ boxShadow: 'none', backgroundColor: 'transparent' }}>
172+
<p className="px-3 py-1 small">{intl.formatMessage(messages.description)}</p>
173+
<hr style={{ margin: '0 20px' }} />
160174
<Card.Header
161175
className="scan-header h3 px-3 text-black mb-2"
162-
title={intl.formatMessage(messages.card1Title)}
176+
title={intl.formatMessage(messages.scanHeader)}
163177
/>
164-
<p className="px-3 py-1 small ">{intl.formatMessage(messages.description)}</p>
165-
{isShowExportButton && (
166178
<Card.Section className="px-3 py-1">
167-
<Button
168-
size="md"
169-
block
170-
className="mb-3"
171-
onClick={() => dispatch(startLinkCheck(courseId))}
172-
>
173-
{intl.formatMessage(messages.buttonTitle)}
174-
</Button>
175179
<p className="small"> {lastScannedAt && `${intl.formatMessage(messages.lastScannedOn)} ${intl.formatDate(lastScannedAt, { year: 'numeric', month: 'long', day: 'numeric' })}`}</p>
176180
</Card.Section>
177-
)}
178181
{showStepper && (
179182
<Card.Section className="px-3 py-1">
180183
<CourseStepper

src/optimizer-page/messages.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const messages = defineMessages({
2727
},
2828
buttonTitle: {
2929
id: 'course-authoring.course-optimizer.button.title',
30-
defaultMessage: 'Start scanning',
30+
defaultMessage: 'Scan course',
3131
},
3232
preparingStepTitle: {
3333
id: 'course-authoring.course-optimizer.peparing-step.title',
@@ -57,6 +57,10 @@ const messages = defineMessages({
5757
id: 'course-authoring.course-optimizer.last-scanned-on',
5858
defaultMessage: 'Last scanned on',
5959
},
60+
scanHeader: {
61+
id: 'course-authoring.course-optimizer.scanHeader',
62+
defaultMessage: 'Scan results',
63+
},
6064
});
6165

6266
export default messages;
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import {
2+
Card, Icon, DataTable,
3+
} from '@openedx/paragon';
4+
import {
5+
ArrowForwardIos,
6+
} from '@openedx/paragon/icons';
7+
import { FC } from 'react';
8+
import { Unit } from '../types';
9+
10+
const PreviousRunLinkHref: FC<{ href: string }> = ({ href }) => {
11+
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
12+
event.preventDefault();
13+
window.open(href, '_blank');
14+
};
15+
16+
return (
17+
<div className="broken-link-container">
18+
<a href={href} onClick={handleClick} className="broken-link" rel="noreferrer">
19+
{href}
20+
</a>
21+
</div>
22+
);
23+
};
24+
25+
const GoToBlock: FC<{ block: { url: string, displayName: string } }> = ({ block }) => {
26+
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
27+
event.preventDefault();
28+
window.open(block.url, '_blank');
29+
};
30+
31+
return (
32+
<div className="go-to-block-link-container">
33+
<a href={block.url} onClick={handleClick} className="broken-link" rel="noreferrer">
34+
{block.displayName}
35+
</a>
36+
</div>
37+
);
38+
};
39+
40+
const PreviousRunLinksCol: FC<{ block: { url: string, displayName: string }, href: string }> = ({ block, href }) => (
41+
<span className="links-container">
42+
<GoToBlock block={{ url: block.url, displayName: block.displayName || 'Go to block' }} />
43+
<Icon className="arrow-forward-ios" src={ArrowForwardIos} style={{ color: '#8F8F8F' }} />
44+
<PreviousRunLinkHref href={href} />
45+
</span>
46+
);
47+
48+
interface PreviousRunLinkTableProps {
49+
unit: Unit;
50+
}
51+
52+
type TableData = {
53+
Links: JSX.Element;
54+
}[];
55+
56+
const PreviousRunLinkTable: FC<PreviousRunLinkTableProps> = ({
57+
unit,
58+
}) => {
59+
const previousRunLinkList = unit.blocks.reduce(
60+
(
61+
acc: TableData,
62+
block,
63+
) => {
64+
if (block.previousRunLinks && block.previousRunLinks.length > 0) {
65+
const blockPreviousRunLinks = block.previousRunLinks.map((link) => ({
66+
Links: (
67+
<PreviousRunLinksCol
68+
block={{ url: block.url, displayName: block.displayName || 'Go to block' }}
69+
href={link}
70+
/>
71+
),
72+
}));
73+
acc.push(...blockPreviousRunLinks);
74+
}
75+
76+
return acc;
77+
},
78+
[],
79+
);
80+
81+
if (previousRunLinkList.length === 0) {
82+
return null;
83+
}
84+
85+
return (
86+
<Card className="unit-card rounded-sm pt-2 pb-3 pl-3 pr-4 mb-2.5">
87+
<p className="unit-header">{unit.displayName}</p>
88+
<DataTable
89+
data={previousRunLinkList}
90+
itemCount={previousRunLinkList.length}
91+
columns={[
92+
{
93+
accessor: 'Links',
94+
width: 'col-12',
95+
},
96+
]}
97+
/>
98+
</Card>
99+
);
100+
};
101+
102+
export default PreviousRunLinkTable;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { FC } from 'react';
2+
import {
3+
Collapsible,
4+
Icon,
5+
} from '@openedx/paragon';
6+
import {
7+
ArrowRight,
8+
ArrowDropDown,
9+
} from '@openedx/paragon/icons';
10+
11+
interface Props {
12+
index: number;
13+
handleToggle: Function;
14+
isOpen: boolean;
15+
hasPrevAndIsOpen: boolean;
16+
hasNextAndIsOpen: boolean;
17+
title: string;
18+
children: React.ReactNode;
19+
previousRunLinksCount: number;
20+
className?: string;
21+
}
22+
23+
const PreviousRunSectionCollapsible: FC<Props> = ({
24+
index,
25+
handleToggle,
26+
isOpen,
27+
hasPrevAndIsOpen,
28+
hasNextAndIsOpen,
29+
title,
30+
children,
31+
previousRunLinksCount,
32+
className,
33+
}) => {
34+
const styling = `card-lg open-section-rounded ${hasPrevAndIsOpen ? 'closed-section-rounded-top' : ''} ${hasNextAndIsOpen ? 'closed-section-rounded-bottom' : ''}`;
35+
const collapsibleTitle = (
36+
<div className={className}>
37+
<div className="section-collapsible-header-item">
38+
<Icon src={isOpen ? ArrowDropDown : ArrowRight} />
39+
<p className="section-title">{title}</p>
40+
</div>
41+
<div className="section-collapsible-header-actions">
42+
<div className="section-collapsible-header-action-item">
43+
<p>{previousRunLinksCount > 0 ? previousRunLinksCount : '-'}</p>
44+
</div>
45+
</div>
46+
</div>
47+
);
48+
49+
return (
50+
<div className={`section ${isOpen ? 'is-open' : ''}`}>
51+
<Collapsible
52+
className="section-collapsible-item-container"
53+
styling={styling}
54+
title={(
55+
<p className="flex-grow-1 section-collapsible-item">
56+
<strong>{collapsibleTitle}</strong>
57+
</p>
58+
)}
59+
iconWhenClosed=""
60+
iconWhenOpen=""
61+
open={isOpen}
62+
onToggle={() => handleToggle(index)}
63+
>
64+
<Collapsible.Body className="section-collapsible-item-body">{children}</Collapsible.Body>
65+
</Collapsible>
66+
</div>
67+
);
68+
};
69+
70+
export default PreviousRunSectionCollapsible;

src/optimizer-page/scan-results/ScanResults.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@
183183
display: flex;
184184
align-items: center;
185185
align-self: center;
186+
font-size: 18px;
186187
}
187188
}
188189

@@ -332,3 +333,7 @@
332333
line-height: 24px;
333334
margin: 0;
334335
}
336+
337+
.scan-course-btn:focus::before {
338+
border: none !important;
339+
}

0 commit comments

Comments
 (0)