Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add core curriculum chips to injected popup #372

Merged
3 changes: 3 additions & 0 deletions src/shared/types/Course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Course>) {
Object.assign(this, course);
Expand All @@ -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 ?? [];
}

/**
Expand Down
10 changes: 9 additions & 1 deletion src/stories/components/Chip.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,16 @@ export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
export const FlagChip: Story = {
args: {
label: 'QR',
variant: 'flag',
},
};

export const CoreChip: Story = {
args: {
label: 'SB',
variant: 'core',
},
};
2 changes: 2 additions & 0 deletions src/stories/components/ConflictsWithWarning.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -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: [
Expand Down
1 change: 1 addition & 0 deletions src/stories/components/List.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
1 change: 1 addition & 0 deletions src/stories/components/PopupCourseBlock.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down
2 changes: 2 additions & 0 deletions src/stories/components/calendar/CalendarBottomBar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand Down Expand Up @@ -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(),
Expand Down
3 changes: 3 additions & 0 deletions src/stories/injected/mocked.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
55 changes: 45 additions & 10 deletions src/views/components/common/Chip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Text from '@views/components/common/Text/Text';
import clsx from 'clsx';
import React from 'react';

/**
Expand All @@ -14,26 +15,60 @@ export const flagMap = {
'Independent Inquiry': 'II',
} as const satisfies Record<string, Flag>;

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<string, Core>;

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<Props>): JSX.Element {
const longFlagName = Object.entries(flagMap).find(([full, short]) => short === label)?.[0] ?? label;
export function Chip({ variant, label }: React.PropsWithChildren<Props>): 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 (
<Text
as='div'
variant='h4'
className='min-w-5 inline-flex items-center justify-center gap-2.5 rounded-lg px-1.5 py-0.5'
style={{
backgroundColor: '#FFD600',
}}
title={`${longFlagName} flag`}
className={clsx('min-w-5 inline-flex items-center justify-center gap-2.5 rounded-lg px-1.5 py-0.5', {
'bg-ut-yellow text-black': variant === 'flag',
'bg-ut-blue text-white': variant === 'core',
})}
title={variant === 'flag' ? `${longName} flag` : `${longName} core curriculum requirement`}
>
{label}
</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -152,6 +152,14 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
<Chip
key={flagMap[flag as keyof typeof flagMap]}
label={flagMap[flag as keyof typeof flagMap]}
variant='flag'
/>
))}
{core.map((coreVal: string) => (
<Chip
key={coreMap[coreVal as keyof typeof coreMap]}
label={coreMap[coreVal as keyof typeof coreMap]}
variant='core'
/>
))}
</div>
Expand Down
17 changes: 17 additions & 0 deletions src/views/lib/CourseCatalogScraper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>;

type TableDataSelectorType = (typeof TableDataSelector)[keyof typeof TableDataSelector];
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Loading