Skip to content

Commit 1250f9a

Browse files
committed
Bunch of polish
- Learner MC view option color states - PDF type for uploads - Admin last page press next button opens up Add page menu
1 parent 4f3d7df commit 1250f9a

File tree

7 files changed

+135
-82
lines changed

7 files changed

+135
-82
lines changed

frontend/src/components/course_authoring/EmptyModuleEditing.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ export const AddYourFirstPageSlide = ({
172172
<Typography variant="labelLarge">Upload page(s)</Typography>
173173
<VisuallyHiddenInput
174174
type="file"
175+
accept="application/pdf"
175176
onChange={handleFileChange}
176177
multiple
177178
/>
@@ -205,18 +206,18 @@ export const AddYourFirstPageSlide = ({
205206
MenuListProps={{
206207
"aria-labelledby": "activity-type-button",
207208
}}
208-
PaperProps={{
209-
sx: {
210-
width: anchorEl?.offsetWidth,
211-
},
212-
}}
213209
>
214210
{Object.values(QuestionType).map((type) => (
215211
<MenuItem
216212
key={type}
217213
onClick={() => handleActivityTypeSelect(type)}
218214
>
219-
<Stack direction="row" alignItems="center" gap="8px">
215+
<Stack
216+
direction="row"
217+
alignItems="center"
218+
gap="8px"
219+
color={theme.palette.Administrator.Dark.Default}
220+
>
220221
{questionTypeIcons[type]} {questionTypeLabels[type]}
221222
</Stack>
222223
</MenuItem>

frontend/src/components/course_viewing/CourseModulesGrid.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1+
import { Add } from "@mui/icons-material";
12
import { Button, Grid, Typography, useTheme } from "@mui/material";
23
import { useCallback, useEffect, useRef, useState } from "react";
34
import { DndProvider } from "react-dnd";
45
import { HTML5Backend } from "react-dnd-html5-backend";
5-
import { Add } from "@mui/icons-material";
66
import CourseAPIClient from "../../APIClients/CourseAPIClient";
77
import useCourseModules from "../../hooks/useCourseModules";
88
import { useUser } from "../../hooks/useUser";
99
import { CourseModule, ModuleStatus } from "../../types/CourseTypes";
1010
import ModuleCardAdmin from "./library-viewing/ModuleCardAdmin";
11-
import ModuleCardLearner from "./library-viewing/ModuleCardLearner";
1211
import ModuleCardFacilitator from "./library-viewing/ModuleCardFacilitator";
12+
import ModuleCardLearner from "./library-viewing/ModuleCardLearner";
1313
import CreateModuleModal from "./modals/CreateModuleModal";
1414

1515
interface ModuleGridProps {
@@ -90,7 +90,10 @@ export default function CourseModulesGrid({
9090

9191
if (loading)
9292
return <Typography paddingLeft="10px">Loading modules...</Typography>;
93-
if (error) return <Typography color="error">{error}</Typography>;
93+
if (error)
94+
return (
95+
<Typography color="error">Failed to fetch modules: {error}</Typography>
96+
);
9497

9598
// Filter modules based on search query
9699
const filteredModules = courseModules.filter((module) =>

frontend/src/components/course_viewing/CourseViewingPage.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import MenuOpenIcon from "@mui/icons-material/MenuOpen";
21
import { Search } from "@mui/icons-material";
2+
import MenuOpenIcon from "@mui/icons-material/MenuOpen";
33
import { Box, Button, Stack, Typography, useTheme } from "@mui/material";
44
import { useEffect, useState } from "react";
55
import { useHistory, useLocation } from "react-router-dom";
6+
import { useUser } from "../../hooks/useUser";
67
import { CourseUnit } from "../../types/CourseTypes";
8+
import StartAdornedTextField from "../common/form/StartAdornedTextField";
79
import CourseModulesGrid from "./CourseModulesGrid";
810
import UnitSidebar from "./sidebar/UnitSidebar";
9-
import StartAdornedTextField from "../common/form/StartAdornedTextField";
10-
import { useUser } from "../../hooks/useUser";
1111

1212
export default function CourseUnitsPage() {
1313
const theme = useTheme();
@@ -135,7 +135,7 @@ export default function CourseUnitsPage() {
135135
/>
136136
</Stack>
137137
) : (
138-
<Typography>Loading units...</Typography>
138+
<></>
139139
)}
140140
</Box>
141141
</Box>

frontend/src/components/course_viewing/multiple-choice/MultipleChoiceViewOption.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ export default function MultipleChoiceViewOption({
3232
iconElement = <CheckBoxOutlineBlank />;
3333
}
3434
} else if (selected) {
35-
iconElement = <RadioButtonCheckedOutlined />;
35+
iconElement = (
36+
<RadioButtonCheckedOutlined
37+
sx={{ color: theme.palette.Learner.Dark.Default }}
38+
/>
39+
);
3640
} else {
3741
iconElement = <RadioButtonUncheckedOutlined />;
3842
}
@@ -42,7 +46,16 @@ export default function MultipleChoiceViewOption({
4246
backgroundColor = "#F5FFDF"; // light green
4347
} else if (selected) {
4448
// eslint-disable-next-line prefer-destructuring
45-
backgroundColor = theme.palette.Neutral[100];
49+
backgroundColor = theme.palette.Learner.Light.Default;
50+
}
51+
52+
let border: string | null = null;
53+
if (displayCorrect) {
54+
border = `1px solid ${theme.palette.Learner.Dark.Default}`; // darker green border
55+
} else if (selected) {
56+
border = `1px solid ${theme.palette.Learner.Dark.Default}`;
57+
} else {
58+
border = `1px solid ${theme.palette.Neutral[400]}`;
4659
}
4760

4861
return (
@@ -68,9 +81,20 @@ export default function MultipleChoiceViewOption({
6881
justifyContent: "flex-start",
6982
position: "relative",
7083

71-
border: `1px solid ${theme.palette.Neutral[400]}`,
84+
border,
7285
borderRadius: "4px",
7386
backgroundColor,
87+
88+
"&:hover": {
89+
backgroundColor: selected ? undefined : theme.palette.Neutral[200],
90+
cursor: "pointer",
91+
},
92+
93+
"&:active": {
94+
backgroundColor: selected
95+
? theme.palette.Learner.Light.Pressed
96+
: theme.palette.Neutral[300],
97+
},
7498
}}
7599
>
76100
<Box

frontend/src/components/pages/ViewModulePage.tsx

Lines changed: 67 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,26 @@ import {
4646
} from "../../constants/ActivityLabels";
4747
import * as Routes from "../../constants/Routes";
4848
import { COURSE_PAGE } from "../../constants/Routes";
49+
import { useCourseUnits } from "../../contexts/CourseUnitsContext";
50+
import { useSocket } from "../../contexts/SocketContext";
4951
import useActivity from "../../hooks/useActivity";
5052
import useQueryParams from "../../hooks/useQueryParams";
5153
import { useUser } from "../../hooks/useUser";
5254
import {
5355
Activity,
5456
CourseModule,
5557
CourseUnit,
58+
HeaderColumnIncludesTypes,
5659
isActivityPage,
5760
isLessonPage,
5861
isMatchingActivity,
5962
isMultipleChoiceActivity,
6063
isMultiSelectActivity,
6164
isTableActivity,
62-
HeaderColumnIncludesTypes,
65+
isTextInputActivity,
6366
Media,
64-
QuestionType,
6567
ModuleStatus,
66-
isTextInputActivity,
68+
QuestionType,
6769
} from "../../types/CourseTypes";
6870
import { Bookmark } from "../../types/UserTypes";
6971
import { padNumber } from "../../utils/StringUtils";
@@ -78,26 +80,24 @@ import MultipleChoiceMainEditor from "../course_authoring/multiple-choice/Multip
7880
import MultipleChoiceEditorSidebar from "../course_authoring/multiple-choice/MultipleChoiceSidebar";
7981
import TableMainEditor from "../course_authoring/table/TableEditor";
8082
import TableSidebar from "../course_authoring/table/TableSidebar";
83+
import TextInputEditor from "../course_authoring/text-input/TextInputEditor";
84+
import TextInputEditorSidebar from "../course_authoring/text-input/TextInputSidebar";
8185
import MatchingViewer from "../course_viewing/matching/MatchingViewer";
86+
import EditPublishedModuleModal from "../course_viewing/modals/EditPublishedModuleModal";
87+
import PublishModuleModal from "../course_viewing/modals/PublishModuleModal";
8288
import WrongAnswerModal from "../course_viewing/modals/WrongAnswerModal";
8389
import MultipleChoiceViewer, {
8490
ActivityViewerHandle,
8591
} from "../course_viewing/multiple-choice/MultipleChoiceViewer";
8692
import TableViewer from "../course_viewing/table/TableViewer";
93+
import TextInputViewer from "../course_viewing/text-input/TextInputViewer";
8794
import FeedbackThumbnail from "../courses/moduleViewing/learner-giving-feedback/FeedbackThumbnail";
8895
import SurveySlides from "../courses/moduleViewing/learner-giving-feedback/SurveySlides";
8996
import ModuleSidebarThumbnail from "../courses/moduleViewing/Thumbnail";
9097
import NeedHelpModal from "../help/NeedHelpModal";
9198
import DeletePageModal from "./DeletePageModal";
9299
import ModuleLockedModal from "./ModuleLockedModal";
93100
import "./ViewModulePage.css";
94-
import { useCourseUnits } from "../../contexts/CourseUnitsContext";
95-
import EditPublishedModuleModal from "../course_viewing/modals/EditPublishedModuleModal";
96-
import { useSocket } from "../../contexts/SocketContext";
97-
import PublishModuleModal from "../course_viewing/modals/PublishModuleModal";
98-
import TextInputEditorSidebar from "../course_authoring/text-input/TextInputSidebar";
99-
import TextInputEditor from "../course_authoring/text-input/TextInputEditor";
100-
import TextInputViewer from "../course_viewing/text-input/TextInputViewer";
101101

102102
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
103103
"pdfjs-dist/build/pdf.worker.min.mjs",
@@ -156,6 +156,8 @@ const ViewModulePage = () => {
156156
mouseX: number;
157157
mouseY: number;
158158
pageIndex: number;
159+
addToEnd: boolean;
160+
open: boolean;
159161
} | null>(null);
160162
const [activityMenuAnchor, setActivityMenuAnchor] =
161163
useState<null | HTMLElement>(null);
@@ -268,14 +270,16 @@ const ViewModulePage = () => {
268270
}, [activity]);
269271

270272
const handleContextMenu = useCallback(
271-
(event: React.MouseEvent<HTMLDivElement>, pageIndex: number) => {
273+
(event: React.MouseEvent, pageIndex: number, addToEnd = false) => {
272274
event.preventDefault();
273275
setContextMenu((prev) =>
274-
prev === null
276+
prev === null || !prev.open
275277
? {
276278
mouseX: event.clientX + 2,
277279
mouseY: event.clientY - 6,
278280
pageIndex,
281+
addToEnd,
282+
open: true,
279283
}
280284
: null,
281285
);
@@ -284,7 +288,7 @@ const ViewModulePage = () => {
284288
);
285289

286290
const handleCloseContextMenu = useCallback(() => {
287-
setContextMenu(null);
291+
setContextMenu((prev) => (prev ? { ...prev, open: false } : null));
288292
}, []);
289293

290294
const handleUploadPdfAbove = () => {
@@ -367,7 +371,6 @@ const ViewModulePage = () => {
367371
if (contextMenu === null) return;
368372
setSelectedPageIndexForActivity(contextMenu.pageIndex);
369373
setActivityMenuAnchor(event.currentTarget);
370-
handleCloseContextMenu();
371374
};
372375

373376
const handleActivityTypeSelect = async (questionType: QuestionType) => {
@@ -385,6 +388,7 @@ const ViewModulePage = () => {
385388
/* eslint-disable-next-line no-console */
386389
console.error("Failed to create activity:", error);
387390
}
391+
handleCloseContextMenu();
388392
setActivityMenuAnchor(null);
389393
setSelectedPageIndexForActivity(null);
390394
};
@@ -1437,11 +1441,18 @@ const ViewModulePage = () => {
14371441
</Typography>
14381442
<IconButton
14391443
disabled={
1440-
role === "Learner" && module && isModuleCompleted(module.id)
1444+
role !== "Administrator" &&
1445+
(role === "Learner" && module && isModuleCompleted(module.id)
14411446
? currentPage >= numPages
1442-
: currentPage >= numPages - 1
1447+
: currentPage >= numPages - 1)
14431448
}
1444-
onClick={() => setCurrentPage(currentPage + 1)}
1449+
onClick={(event) => {
1450+
if (role === "Administrator" && currentPage + 1 >= numPages) {
1451+
handleContextMenu(event, currentPage, true);
1452+
} else {
1453+
setCurrentPage(currentPage + 1);
1454+
}
1455+
}}
14451456
sx={{
14461457
border: "1px solid black",
14471458
height: "48px",
@@ -1679,23 +1690,30 @@ const ViewModulePage = () => {
16791690
/>
16801691
)}
16811692
<Menu
1682-
open={contextMenu !== null}
1693+
open={contextMenu !== null && contextMenu.open}
16831694
onClose={handleCloseContextMenu}
16841695
anchorReference="anchorPosition"
16851696
anchorPosition={
1686-
contextMenu !== null
1697+
contextMenu !== null && contextMenu.open
16871698
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
16881699
: undefined
16891700
}
16901701
>
1691-
<MenuItem
1692-
onClick={handleUploadPdfAbove}
1693-
disabled={isUploadingPdf || isDeletingFromContext}
1694-
>
1695-
<Stack direction="row" alignItems="center" gap="12px" paddingY="8px">
1696-
<ArrowCircleUp /> Insert pages above
1697-
</Stack>
1698-
</MenuItem>
1702+
{!contextMenu?.addToEnd && (
1703+
<MenuItem
1704+
onClick={handleUploadPdfAbove}
1705+
disabled={isUploadingPdf || isDeletingFromContext}
1706+
>
1707+
<Stack
1708+
direction="row"
1709+
alignItems="center"
1710+
gap="12px"
1711+
paddingY="8px"
1712+
>
1713+
<ArrowCircleUp /> Insert pages above
1714+
</Stack>
1715+
</MenuItem>
1716+
)}
16991717
<MenuItem
17001718
onClick={handleUploadPdfBelow}
17011719
disabled={isUploadingPdf || isDeletingFromContext}
@@ -1712,36 +1730,43 @@ const ViewModulePage = () => {
17121730
<Add /> Create activity
17131731
</Stack>
17141732
</MenuItem>
1715-
<MenuItem
1716-
onClick={handleDeletePageFromContext}
1717-
disabled={isUploadingPdf || isDeletingFromContext}
1718-
>
1719-
<Stack
1720-
direction="row"
1721-
alignItems="center"
1722-
gap="12px"
1723-
color={theme.palette.Error.Dark.Default}
1724-
paddingY="8px"
1733+
{!contextMenu?.addToEnd && (
1734+
<MenuItem
1735+
onClick={handleDeletePageFromContext}
1736+
disabled={isUploadingPdf || isDeletingFromContext}
17251737
>
1726-
<DeleteOutline /> Delete page
1727-
</Stack>
1728-
</MenuItem>
1738+
<Stack
1739+
direction="row"
1740+
alignItems="center"
1741+
gap="12px"
1742+
color={theme.palette.Error.Dark.Default}
1743+
paddingY="8px"
1744+
>
1745+
<DeleteOutline /> Delete page
1746+
</Stack>
1747+
</MenuItem>
1748+
)}
17291749
</Menu>
17301750
<Menu
17311751
id="activity-type-menu"
17321752
anchorEl={activityMenuAnchor}
17331753
open={Boolean(activityMenuAnchor)}
17341754
onClose={() => {
1735-
setActivityMenuAnchor(null);
17361755
setSelectedPageIndexForActivity(null);
1756+
setActivityMenuAnchor(null);
17371757
}}
17381758
MenuListProps={{
17391759
"aria-labelledby": "activity-type-button",
17401760
}}
17411761
>
17421762
{Object.values(QuestionType).map((type) => (
17431763
<MenuItem key={type} onClick={() => handleActivityTypeSelect(type)}>
1744-
<Stack direction="row" alignItems="center" gap="8px">
1764+
<Stack
1765+
direction="row"
1766+
alignItems="center"
1767+
gap="8px"
1768+
color={theme.palette.Administrator.Dark.Default}
1769+
>
17451770
{questionTypeIcons[type]} {questionTypeLabels[type]}
17461771
</Stack>
17471772
</MenuItem>

frontend/src/constants/ActivityLabels.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import {
22
AccountTreeOutlined,
3+
CheckBox,
4+
RadioButtonChecked,
35
Subject,
46
TableChartOutlined,
57
} from "@mui/icons-material";
68
import React from "react";
79
import { QuestionType } from "../types/CourseTypes";
810

911
export const questionTypeIcons: Record<QuestionType, React.ReactNode> = {
10-
[QuestionType.MultipleChoice]: <AccountTreeOutlined />,
11-
[QuestionType.MultiSelect]: <AccountTreeOutlined />,
12+
[QuestionType.MultipleChoice]: <RadioButtonChecked />,
13+
[QuestionType.MultiSelect]: <CheckBox />,
1214
[QuestionType.Table]: <TableChartOutlined />,
1315
[QuestionType.Matching]: <AccountTreeOutlined />,
1416
[QuestionType.TextInput]: <Subject />,

0 commit comments

Comments
 (0)