diff --git a/src/shared/types/Course.ts b/src/shared/types/Course.ts index 83bb3c02b..a8935b461 100644 --- a/src/shared/types/Course.ts +++ b/src/shared/types/Course.ts @@ -79,6 +79,8 @@ export class Course { scrapedAt!: number; /** The colors of the course when displayed */ colors: CourseColors; + /** The core curriculum requirements the course satisfies */ + core: string[]; constructor(course: Serialized) { Object.assign(this, course); @@ -88,6 +90,7 @@ export class Course { this.scrapedAt = Date.now(); } this.colors = course.colors ? structuredClone(course.colors) : getCourseColors('emerald', 500); + this.core = course.core ?? []; } /** diff --git a/src/stories/components/Chip.stories.tsx b/src/stories/components/Chip.stories.tsx index 54ce1e2f6..7fc9ea083 100644 --- a/src/stories/components/Chip.stories.tsx +++ b/src/stories/components/Chip.stories.tsx @@ -16,8 +16,16 @@ export default meta; type Story = StoryObj; -export const Default: Story = { +export const FlagChip: Story = { args: { label: 'QR', + variant: 'flag', + }, +}; + +export const CoreChip: Story = { + args: { + label: 'SB', + variant: 'core', }, }; diff --git a/src/stories/components/ConflictsWithWarning.stories.tsx b/src/stories/components/ConflictsWithWarning.stories.tsx index 7e4acd069..40c5ccec4 100644 --- a/src/stories/components/ConflictsWithWarning.stories.tsx +++ b/src/stories/components/ConflictsWithWarning.stories.tsx @@ -17,6 +17,7 @@ export const ExampleCourse: Course = new Course({ 'Taught as a Web-based course.', ], flags: ['Quantitative Reasoning'], + core: ['Natural Science and Technology, Part I'], fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB', instructionMode: 'Online', instructors: [ @@ -60,6 +61,7 @@ export const ExampleCourse2: Course = new Course({ 'May be counted toward the Independent Inquiry flag requirement.', ], flags: ['Independent Inquiry'], + core: ['Natural Science and Technology, Part II'], fullName: 'C S 439 PRINCIPLES OF COMPUTER SYSTEMS', instructionMode: 'In Person', instructors: [ diff --git a/src/stories/components/List.stories.tsx b/src/stories/components/List.stories.tsx index 6beb0706f..471258d42 100644 --- a/src/stories/components/List.stories.tsx +++ b/src/stories/components/List.stories.tsx @@ -34,6 +34,7 @@ const generateCourses = (count: number): Course[] => { 'Taught as a Web-based course.', ], flags: ['Quantitative Reasoning'], + core: ['Natural Science and Technology, Part I'], fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB', instructionMode: 'Online', instructors: [ diff --git a/src/stories/components/PopupCourseBlock.stories.tsx b/src/stories/components/PopupCourseBlock.stories.tsx index 3c6438e6a..f7753584c 100644 --- a/src/stories/components/PopupCourseBlock.stories.tsx +++ b/src/stories/components/PopupCourseBlock.stories.tsx @@ -26,6 +26,7 @@ export const ExampleCourse: Course = new Course({ 'Taught as a Web-based course.', ], flags: ['Quantitative Reasoning'], + core: ['Natural Science and Technology, Part I'], fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB', instructionMode: 'Online', instructors: [ diff --git a/src/stories/components/calendar/CalendarBottomBar.stories.tsx b/src/stories/components/calendar/CalendarBottomBar.stories.tsx index b19f9d2e4..da0f8de25 100644 --- a/src/stories/components/calendar/CalendarBottomBar.stories.tsx +++ b/src/stories/components/calendar/CalendarBottomBar.stories.tsx @@ -11,6 +11,7 @@ const exampleGovCourse: Course = new Course({ department: 'GOV', description: ['nah', 'aint typing this', 'corndog'], flags: ['no flag for you >:)'], + core: ['American and Texas Government'], fullName: 'GOV 312L Something something', instructionMode: 'Online', instructors: [ @@ -43,6 +44,7 @@ const examplePsyCourse: Course = new Course({ department: 'PSY', description: ['nah', 'aint typing this', 'corndog'], flags: ['no flag for you >:)'], + core: ['Social and Behavioral Sciences'], fullName: 'PSY 317L Yada yada', instructionMode: 'Online', scrapedAt: Date.now(), diff --git a/src/stories/injected/mocked.ts b/src/stories/injected/mocked.ts index 8fd952637..2b9f200aa 100644 --- a/src/stories/injected/mocked.ts +++ b/src/stories/injected/mocked.ts @@ -16,6 +16,7 @@ export const exampleCourse: Course = new Course({ 'Taught as a Web-based course.', ], flags: ['Quantitative Reasoning'], + core: ['Natural Science and Technology, Part I'], fullName: 'C S 303E ELEMS OF COMPTRS/PROGRAMMNG-WB', instructionMode: 'Online', scrapedAt: Date.now(), @@ -99,6 +100,7 @@ export const bevoCourse: Course = new Course({ }, url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/12345/', flags: ['Independent Inquiry', 'Writing'], + core: ['Humanities'], instructionMode: 'In Person', semester: { code: '12345', @@ -154,6 +156,7 @@ export const mikeScottCS314Course: Course = new Course({ }, url: 'https://utdirect.utexas.edu/apps/registrar/course_schedule/20242/50825/', flags: ['Writing', 'Independent Inquiry'], + core: ['Natural Science and Technology, Part II'], instructionMode: 'In Person', semester: { code: '12345', diff --git a/src/views/components/common/Chip.tsx b/src/views/components/common/Chip.tsx index 0c4e345f7..691736af5 100644 --- a/src/views/components/common/Chip.tsx +++ b/src/views/components/common/Chip.tsx @@ -1,4 +1,5 @@ import Text from '@views/components/common/Text/Text'; +import clsx from 'clsx'; import React from 'react'; /** @@ -14,26 +15,60 @@ export const flagMap = { 'Independent Inquiry': 'II', } as const satisfies Record; -interface Props { - label: Flag; -} +/** + * A type that represents the core curriculum aspects that a course can satisfy. + */ +export type Core = 'ID' | 'C1' | 'HU' | 'GO' | 'HI' | 'SB' | 'MA' | 'N1' | 'N2' | 'VP'; +export const coreMap = { + 'First-Year Signature Course': 'ID', + 'English Composition': 'C1', + Humanities: 'HU', + 'American and Texas Government': 'GO', + 'U.S. History': 'HI', + 'Social and Behavioral Sciences': 'SB', + 'Natural Science and Technology, Part I': 'N1', + 'Natural Science and Technology, Part II': 'N2', + Mathematics: 'MA', + 'Visual and Performing Arts': 'VP', +} as const satisfies Record; + +type Props = + | { + variant: 'core'; + label: Core; + } + | { + variant: 'flag'; + label: Flag; + }; /** * A reusable chip component that follows the design system of the extension. * @returns */ -export function Chip({ label }: React.PropsWithChildren): JSX.Element { - const longFlagName = Object.entries(flagMap).find(([full, short]) => short === label)?.[0] ?? label; +export function Chip({ variant, label }: React.PropsWithChildren): JSX.Element { + let labelMap; + switch (variant) { + case 'core': + labelMap = coreMap; + break; + case 'flag': + labelMap = flagMap; + break; + default: + labelMap = {}; + } + const longName = Object.entries(labelMap).find(([full, short]) => short === label)?.[0] ?? label; return ( {label} diff --git a/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx b/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx index b6853add0..6e12cc46a 100644 --- a/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx +++ b/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx @@ -3,7 +3,7 @@ import type { Course } from '@shared/types/Course'; import type Instructor from '@shared/types/Instructor'; import type { UserSchedule } from '@shared/types/UserSchedule'; import { Button } from '@views/components/common/Button'; -import { Chip, flagMap } from '@views/components/common/Chip'; +import { Chip, coreMap, flagMap } from '@views/components/common/Chip'; import Divider from '@views/components/common/Divider'; import Link from '@views/components/common/Link'; import Text from '@views/components/common/Text/Text'; @@ -49,7 +49,7 @@ const capitalizeString = (str: string) => str.charAt(0).toUpperCase() + str.slic * @returns {JSX.Element} The rendered component. */ export default function HeadingAndActions({ course, activeSchedule, onClose }: HeadingAndActionProps): JSX.Element { - const { courseName, department, number: courseNumber, uniqueId, instructors, flags, schedule } = course; + const { courseName, department, number: courseNumber, uniqueId, instructors, flags, schedule, core } = course; const courseAdded = activeSchedule.courses.some(ourCourse => ourCourse.uniqueId === uniqueId); const formattedUniqueId = uniqueId.toString().padStart(5, '0'); const isInCalendar = useCalendar(); @@ -152,6 +152,14 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H + ))} + {core.map((coreVal: string) => ( + ))} diff --git a/src/views/lib/CourseCatalogScraper.ts b/src/views/lib/CourseCatalogScraper.ts index 838ff2e10..96474531f 100644 --- a/src/views/lib/CourseCatalogScraper.ts +++ b/src/views/lib/CourseCatalogScraper.ts @@ -19,6 +19,7 @@ const TableDataSelector = { SCHEDULE_HOURS: 'td[data-th="Hour"]>span', SCHEDULE_LOCATION: 'td[data-th="Room"]>span', FLAGS: 'td[data-th="Flags"] ul li', + CORE_CURRICULUM: 'td[data-th="Core"] ul li', } as const satisfies Record; type TableDataSelectorType = (typeof TableDataSelector)[keyof typeof TableDataSelector]; @@ -99,6 +100,7 @@ export class CourseCatalogScraper { semester: this.getSemester(), scrapedAt: Date.now(), colors: getCourseColors('emerald', 500), + core: this.getCore(row), }); courses.push({ element: row, @@ -337,6 +339,21 @@ export class CourseCatalogScraper { return Array.from(lis).map(li => li.textContent || ''); } + /** + * Get the list of core curriculum requirements the course satisfies + * @param row + * @returns an array of core curriculum codes + */ + getCore(row: HTMLTableRowElement): string[] { + const lis = row.querySelectorAll(TableDataSelector.CORE_CURRICULUM); + return ( + Array.from(lis) + // ut schedule is weird and puts a blank core curriculum element even if there aren't any core requirements so filter those out + .filter(li => li.getAttribute('title') !== ' core curriculum requirement') + .map(li => li.textContent || '') + ); + } + /** * This will scrape all the time information from the course catalog table row and return it as a CourseSchedule object, which represents all of the meeting timiestimes/places of the course. * @param row the row of the course catalog table