Skip to content
Merged
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
199 changes: 177 additions & 22 deletions frontend/src/components/pages/ViewModulePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ArrowCircleUp,
CheckCircleOutline,
DeleteOutline,
HourglassBottom,
Refresh,
VisibilityOutlined,
} from "@mui/icons-material";
Expand All @@ -16,14 +17,15 @@ import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
import BookmarkIcon from "@mui/icons-material/Bookmark";
import BookmarkBorderIcon from "@mui/icons-material/BookmarkBorder";
import FullscreenIcon from "@mui/icons-material/Fullscreen";
import PlayCircleOutlineIcon from "@mui/icons-material/PlayCircleOutline";
import {
Box,
Button,
Divider,
IconButton,
Menu,
MenuItem,
Snackbar,
SnackbarContent,
Stack,
Typography,
useTheme,
Expand Down Expand Up @@ -132,6 +134,11 @@ const ViewModulePage = () => {
useState<null | HTMLElement>(null);
const [selectedPageIndexForActivity, setSelectedPageIndexForActivity] =
useState<number | null>(null);
const [uploadSnackbarOpen, setUploadSnackbarOpen] = useState(false);
const [uploadSnackbarMessage, setUploadSnackbarMessage] = useState("");
const [isSnackbarSuccess, setIsSnackbarSuccess] = useState(true);
const [isUploadingPdf, setIsUploadingPdf] = useState(false);
const [isDeletingFromContext, setIsDeletingFromContext] = useState(false);

const [hasImage, setHasImage] = useState(
(currentPageObject &&
Expand Down Expand Up @@ -205,16 +212,29 @@ const ViewModulePage = () => {
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file && module) {
setIsUploadingPdf(true);
setIsSnackbarSuccess(false);
setUploadSnackbarMessage("Uploading PDF...");
setUploadSnackbarOpen(true);
try {
const previousPageCount = module.pages.length;
const updatedModule = await CourseAPIClient.lessonUpload(
file,
module.id,
contextMenu.pageIndex,
);
setModule(updatedModule);
const pagesAdded = updatedModule.pages.length - previousPageCount;
setIsSnackbarSuccess(true);
setUploadSnackbarMessage(
`${pagesAdded} page${pagesAdded !== 1 ? "s" : ""} uploaded`,
);
} catch (error) {
/* eslint-disable-next-line no-console */
console.error("Failed to upload PDF:", error);
setUploadSnackbarOpen(false);
} finally {
setIsUploadingPdf(false);
}
}
};
Expand All @@ -230,16 +250,29 @@ const ViewModulePage = () => {
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file && module) {
setIsUploadingPdf(true);
setIsSnackbarSuccess(false);
setUploadSnackbarMessage("Uploading PDF...");
setUploadSnackbarOpen(true);
try {
const previousPageCount = module.pages.length;
const updatedModule = await CourseAPIClient.lessonUpload(
file,
module.id,
contextMenu.pageIndex + 1,
);
setModule(updatedModule);
const pagesAdded = updatedModule.pages.length - previousPageCount;
setIsSnackbarSuccess(true);
setUploadSnackbarMessage(
`${pagesAdded} page${pagesAdded !== 1 ? "s" : ""} uploaded`,
);
} catch (error) {
/* eslint-disable-next-line no-console */
console.error("Failed to upload PDF:", error);
setUploadSnackbarOpen(false);
} finally {
setIsUploadingPdf(false);
}
}
};
Expand Down Expand Up @@ -278,6 +311,10 @@ const ViewModulePage = () => {
const pageToDelete = module.pages[contextMenu.pageIndex];
if (!pageToDelete) return;

setIsDeletingFromContext(true);
setIsSnackbarSuccess(false);
setUploadSnackbarMessage("Deleting page...");
setUploadSnackbarOpen(true);
try {
const deletedPageId = await CourseAPIClient.deletePage(
module.id,
Expand All @@ -293,10 +330,15 @@ const ViewModulePage = () => {
if (updatedPagesLength === 0) return 0;
return Math.min(prevPage, updatedPagesLength - 1);
});
setIsSnackbarSuccess(true);
setUploadSnackbarMessage("Page deleted");
}
} catch (error) {
/* eslint-disable-next-line no-console */
console.error("Failed to delete page:", error);
setUploadSnackbarOpen(false);
} finally {
setIsDeletingFromContext(false);
}
handleCloseContextMenu();
};
Expand Down Expand Up @@ -504,6 +546,10 @@ const ViewModulePage = () => {
return;
}

setIsSnackbarSuccess(false);
setUploadSnackbarMessage("Rearranging pages...");
setUploadSnackbarOpen(true);

const updatedModule = await CourseAPIClient.reorderPages(
module.id,
draggedIndex,
Expand All @@ -526,10 +572,14 @@ const ViewModulePage = () => {
) {
setCurrentPage(currentPage + 1);
}

setIsSnackbarSuccess(true);
setUploadSnackbarMessage("Pages rearranged");
}
} catch (error) {
/* eslint-disable-next-line no-console */
console.error("Failed to reorder pages:", error);
setUploadSnackbarOpen(false);
} finally {
setDraggedIndex(null);
setHoverIndex(null);
Expand Down Expand Up @@ -594,23 +644,43 @@ const ViewModulePage = () => {
</Document>
)}
{isActivityPage(page) && (
<>
<PlayCircleOutlineIcon
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
fontSize: "60px",
zIndex: "1",
}}
/>
<Box
height={215}
width={280}
sx={{ backgroundColor: "white" }}
/>
</>
<Box
height={168}
width={224}
flexShrink={0}
sx={{
display: "inline-flex",
// padding: "0 45.5px",
justifyContent: "center",
alignItems: "center",
backgroundColor: "white",
borderRadius: "4px",
border: `1px solid ${theme.palette.Learner.Dark.Default}`,
background: theme.palette.Learner.Light.Default,
color: theme.palette.Learner.Dark.Default,
}}
>
<Stack
direction="row"
justifyContent="center"
alignItems="center"
gap="8px"
>
{questionTypeIcons[page.questionType]}
<Stack
direction="column"
justifyContent="center"
alignItems="flex-start"
>
<Typography variant="bodyMedium">
Activity 4.5.1
</Typography>
<Typography variant="labelSmall" textAlign="center">
{questionTypeLabels[page.questionType]}
</Typography>
</Stack>
</Stack>
</Box>
)}
</ModuleSidebarThumbnail>
))
Expand Down Expand Up @@ -666,6 +736,7 @@ const ViewModulePage = () => {
[
theme.palette.Neutral,
theme.palette.Learner.Dark.Default,
theme.palette.Learner.Light.Default,
isFullScreen,
isEmptyModuleEditing,
module?.pages,
Expand Down Expand Up @@ -1186,22 +1257,34 @@ const ViewModulePage = () => {
: undefined
}
>
<MenuItem onClick={handleUploadPdfAbove}>
<MenuItem
onClick={handleUploadPdfAbove}
disabled={isUploadingPdf || isDeletingFromContext}
>
<Stack direction="row" alignItems="center" gap="12px" paddingY="8px">
<ArrowCircleUp /> Insert pages above
</Stack>
</MenuItem>
<MenuItem onClick={handleUploadPdfBelow}>
<MenuItem
onClick={handleUploadPdfBelow}
disabled={isUploadingPdf || isDeletingFromContext}
>
<Stack direction="row" alignItems="center" gap="12px" paddingY="8px">
<ArrowCircleDown /> Insert pages below
</Stack>
</MenuItem>
<MenuItem onClick={handleCreateActivity}>
<MenuItem
onClick={handleCreateActivity}
disabled={isUploadingPdf || isDeletingFromContext}
>
<Stack direction="row" alignItems="center" gap="12px" paddingY="8px">
<Add /> Create activity
</Stack>
</MenuItem>
<MenuItem onClick={handleDeletePageFromContext}>
<MenuItem
onClick={handleDeletePageFromContext}
disabled={isUploadingPdf || isDeletingFromContext}
>
<Stack
direction="row"
alignItems="center"
Expand Down Expand Up @@ -1233,6 +1316,78 @@ const ViewModulePage = () => {
</MenuItem>
))}
</Menu>
<Snackbar
anchorOrigin={{ horizontal: "center", vertical: "bottom" }}
open={uploadSnackbarOpen}
autoHideDuration={6000}
onClose={() => setUploadSnackbarOpen(false)}
sx={{
maxWidth: "482px",
maxHeight: "64px",
width: "100%",
height: "100%",
}}
>
<SnackbarContent
sx={{
backgroundColor: isSnackbarSuccess
? theme.palette.Success.Light.Hover
: theme.palette.Neutral[200],
color: theme.palette.Neutral[700],
paddingTop: "12px",
paddingLeft: "32px",
paddingRight: "12px",
paddingBottom: "12px",
gap: "16px",
"& .MuiSnackbarContent-action": {
padding: "0px",
margin: "0px",
},
}}
message={
<span
style={{ display: "flex", alignItems: "center", gap: "16px" }}
>
{isSnackbarSuccess ? (
<CheckCircleOutline
sx={{
color: theme.palette.Success.Dark.Default,
}}
/>
) : (
<HourglassBottom
sx={{
color: theme.palette.Neutral[700],
}}
/>
)}
<Typography
variant="bodyMedium"
sx={{
color: isSnackbarSuccess
? theme.palette.Success.Dark.Default
: theme.palette.Neutral[700],
}}
>
{uploadSnackbarMessage}
</Typography>
</span>
}
action={
<Button
size="small"
onClick={() => setUploadSnackbarOpen(false)}
sx={{
color: isSnackbarSuccess
? theme.palette.Success.Dark.Default
: theme.palette.Neutral[700],
}}
>
CLOSE
</Button>
}
/>
</Snackbar>
</>
);
};
Expand Down