diff --git a/flake.nix b/flake.nix index 4c458aee..7841752f 100644 --- a/flake.nix +++ b/flake.nix @@ -1,20 +1,25 @@ { inputs = { - flake-utils.url = "github:numtide/flake-utils"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; }; outputs = - inputs: - inputs.flake-utils.lib.eachDefaultSystem ( + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( system: let - pkgs = (import (inputs.nixpkgs) { inherit system; }); + pkgs = (import nixpkgs { inherit system; }); in { formatter = pkgs.nixfmt-rfc-style; - devShell = pkgs.mkShell { + devShells.default = pkgs.mkShell { + name = "utrp-dev"; buildInputs = with pkgs; [ nodejs_20 # v20.19.4 pnpm_10 # v10.14.0 diff --git a/public/database/grade_distributions.db b/public/database/grade_distributions.db index fdd3b058..556dc43e 100644 Binary files a/public/database/grade_distributions.db and b/public/database/grade_distributions.db differ diff --git a/src/views/components/CourseCatalogMain.tsx b/src/views/components/CourseCatalogMain.tsx index 073c117b..06e95973 100644 --- a/src/views/components/CourseCatalogMain.tsx +++ b/src/views/components/CourseCatalogMain.tsx @@ -15,6 +15,8 @@ import type { SiteSupportType } from '@views/lib/getSiteSupport'; import { populateSearchInputs } from '@views/lib/populateSearchInputs'; import React, { useEffect, useRef, useState } from 'react'; +import DialogProvider from './common/DialogProvider/DialogProvider'; + interface Props { support: Extract; } @@ -82,28 +84,30 @@ export default function CourseCatalogMain({ support }: Props): JSX.Element | nul return ( - - - Plus - {rows.map( - row => - row.course && ( - - ) - )} - setShowPopup(false)} - afterLeave={() => setSelectedCourse(null)} - /> - {enableScrollToLoad && } + + + + Plus + {rows.map( + row => + row.course && ( + + ) + )} + setShowPopup(false)} + afterLeave={() => setSelectedCourse(null)} + /> + {enableScrollToLoad && } + ); } diff --git a/src/views/components/common/ExtensionRoot/ExtensionRoot.module.scss b/src/views/components/common/ExtensionRoot/ExtensionRoot.module.scss index d9221ddb..992b63c3 100644 --- a/src/views/components/common/ExtensionRoot/ExtensionRoot.module.scss +++ b/src/views/components/common/ExtensionRoot/ExtensionRoot.module.scss @@ -15,6 +15,11 @@ @apply font-sans; color: #303030; + // fix font-family on injected pages + * { + @apply font-sans; + } + [data-rfd-drag-handle-context-id=':r1:'] { cursor: move; } diff --git a/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx b/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx index 9c49e9ff..38879a94 100644 --- a/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx +++ b/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx @@ -1,3 +1,5 @@ +import createSchedule from '@pages/background/lib/createSchedule'; +import switchSchedule from '@pages/background/lib/switchSchedule'; import { ArrowUpRight, CalendarDots, @@ -14,8 +16,10 @@ import { background } from '@shared/messages'; import type { Course } from '@shared/types/Course'; import type Instructor from '@shared/types/Instructor'; import type { UserSchedule } from '@shared/types/UserSchedule'; +import { englishStringifyList } from '@shared/util/string'; import { Button } from '@views/components/common/Button'; import { Chip, coreMap, flagMap } from '@views/components/common/Chip'; +import { usePrompt } from '@views/components/common/DialogProvider/DialogProvider'; import Divider from '@views/components/common/Divider'; import Link from '@views/components/common/Link'; import Text from '@views/components/common/Text/Text'; @@ -60,7 +64,7 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H const [isCopied, setIsCopied] = useState(false); const lastCopyTime = useRef(0); - + const showDialog = usePrompt(); const getInstructorFullName = (instructor: Instructor) => instructor.toString({ format: 'first_last' }); const handleCopy = async (e: React.MouseEvent) => { @@ -112,10 +116,78 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H } }; + const handleAddToNewSchedule = async (close: () => void) => { + const newScheduleId = await createSchedule(`${course.semester.season} ${course.semester.year}`); + switchSchedule(newScheduleId); + addCourse({ course, scheduleId: newScheduleId }); + close(); + }; + const handleAddOrRemoveCourse = async () => { + const uniqueSemesterCodes = [ + ...new Set( + activeSchedule.courses + .map(course => course.semester.code) + .filter((code): code is string => code !== undefined) + ), + ]; + uniqueSemesterCodes.sort(); + const codeToReadableMap: Record = {}; + activeSchedule.courses.forEach(course => { + const { code } = course.semester; + if (code) { + const readable = `${course.semester.season} ${course.semester.year}`; + codeToReadableMap[code] = readable; + } + }); + const sortedSemesters = uniqueSemesterCodes + .map(code => codeToReadableMap[code]) + .filter((value): value is string => value !== undefined); + const activeSemesters = englishStringifyList(sortedSemesters); + if (!activeSchedule) return; if (!courseAdded) { - addCourse({ course, scheduleId: activeSchedule.id }); + const currentSemesterCode = course.semester.code; + // Show warning if this course is for a different semester than the selected schedule + if ( + activeSchedule.courses.length > 0 && + activeSchedule.courses.every(otherCourse => otherCourse.semester.code !== currentSemesterCode) + ) { + const dialogButtons = (close: () => void) => ( + <> + + + + ); + + showDialog({ + title: 'This course section is from a different semester!', + description: ( + <> + The section you're adding is for{' '} + + {course.semester.season} {course.semester.year} + + , but your current schedule contains sections in{' '} + {activeSemesters}. Mixing semesters in one + schedule may cause confusion. + + ), + buttons: dialogButtons, + }); + } else { + addCourse({ course, scheduleId: activeSchedule.id }); + } } else { removeCourse({ course, scheduleId: activeSchedule.id }); }