diff --git a/src/shared/types/Course.ts b/src/shared/types/Course.ts index ce930deb..dc716222 100644 --- a/src/shared/types/Course.ts +++ b/src/shared/types/Course.ts @@ -72,7 +72,7 @@ export class Course { /** Which semester is the course from */ semester: Semester; - constructor(course: Course | Serialized) { + constructor(course: Serialized) { Object.assign(this, course); this.schedule = new CourseSchedule(course.schedule); } diff --git a/src/shared/types/CourseMeeting.ts b/src/shared/types/CourseMeeting.ts new file mode 100644 index 00000000..dbeac976 --- /dev/null +++ b/src/shared/types/CourseMeeting.ts @@ -0,0 +1,128 @@ +import { Serialized } from 'chrome-extension-toolkit'; + +/** + * a map of the days of the week that a class is taught, and the corresponding abbreviation + */ +export const DAY_MAP = { + M: 'Monday', + T: 'Tuesday', + W: 'Wednesday', + TH: 'Thursday', + F: 'Friday', + S: 'Saturday', + SU: 'Sunday', +} as const; + +/** A day of the week that a class is taught */ +export type Day = typeof DAY_MAP[keyof typeof DAY_MAP]; + +/** A physical room that a class is taught in */ +export type Room = { + /** The UT building code for where the class is taught */ + building: string; + /** The room number for where the class is taught */ + number: string; +}; + +/** + * This represents one "Meeting Time" for a course, which includes the day of the week that the course is taught, the time that the course is taught, and the location that the course is taught + */ +export class CourseMeeting { + /** The day of the week that the course is taught */ + days: Day[]; + /** The start time of the course, in minutes since midnight */ + startTime: number; + /** The end time of the course, in minutes since midnight */ + endTime: number; + /** The location that the course is taught */ + room?: Room; + + constructor(meeting: Serialized) { + Object.assign(this, meeting); + } + + /** + * Return the string representation of the days of the week that this meeting is taught + * @param options options for the string representation + * @returns string representation of the days of the week that this meeting is taught + */ + getDaysString(options: DaysStringOptions): string { + let { format, separator } = options; + let { days } = this; + + if (format === 'short') { + days = Object.keys(DAY_MAP).filter(day => days.includes(DAY_MAP[day as keyof typeof DAY_MAP])) as Day[]; + } + if (separator === 'none') { + return days.join(''); + } + const listFormat = new Intl.ListFormat('en-US', { + style: separator, + type: 'conjunction', + }); + return listFormat.format(days); + } + + /** + * Return the string representation of the time range for the course + * @param options options for the string representation + * @returns string representation of the time range for the course + */ + getTimeString(options: TimeStringOptions): string { + const { startTime, endTime } = this; + const startHour = Math.floor(startTime / 60); + const startMinute = startTime % 60; + const endHour = Math.floor(endTime / 60); + const endMinute = endTime % 60; + + let startTimeString = ''; + let endTimeString = ''; + + if (startHour === 0) { + startTimeString = '12'; + } else if (startHour > 12) { + startTimeString = `${startHour - 12}`; + } else { + startTimeString = `${startHour}`; + } + + startTimeString += startMinute === 0 ? ':00' : `:${startMinute}`; + startTimeString += startHour >= 12 ? 'pm' : 'am'; + + if (endHour === 0) { + endTimeString = '12'; + } else if (endHour > 12) { + endTimeString = `${endHour - 12}`; + } else { + endTimeString = `${endHour}`; + } + endTimeString += endMinute === 0 ? ':00' : `:${endMinute}`; + endTimeString += endHour >= 12 ? 'pm' : 'am'; + + if (options.capitalize) { + startTimeString = startTimeString.toUpperCase(); + endTimeString = endTimeString.toUpperCase(); + } + + return `${startTimeString} ${options.separator} ${endTimeString}`; + } +} + +/** + * Options to control the format of the time string + */ +type TimeStringOptions = { + /** the separator between the start and end times */ + separator: string; + /** capitalizes the AM/PM */ + capitalize?: boolean; +}; + +/** + * Options to control the format of the days string + */ +type DaysStringOptions = { + /** The format of the days string, short = MWF, long = Monday, Wednesday, Friday */ + format: 'short' | 'long'; + separator: Intl.ListFormatStyle | 'none'; +}; diff --git a/src/shared/types/CourseSchedule.ts b/src/shared/types/CourseSchedule.ts index 18cf3b0d..5d706307 100644 --- a/src/shared/types/CourseSchedule.ts +++ b/src/shared/types/CourseSchedule.ts @@ -1,61 +1,27 @@ import { Serialized } from 'chrome-extension-toolkit'; - -/** - * a map of the days of the week that a class is taught, and the corresponding abbreviation - */ -const DAY_MAP = { - M: 'Monday', - T: 'Tuesday', - W: 'Wednesday', - TH: 'Thursday', - F: 'Friday', - S: 'Saturday', - SU: 'Sunday', -} as const; - -/** A day of the week that a class is taught */ -export type Day = typeof DAY_MAP[keyof typeof DAY_MAP]; - -/** A physical room that a class is taught in */ -export type Room = { - /** The UT building code for where the class is taught */ - building: string; - /** The room number for where the class is taught */ - number: string; -}; - -/** - * This represents one "Meeting Time" for a course, which includes the day of the week that the course is taught, the time that the course is taught, and the location that the course is taught - */ -export type CourseMeeting = { - /** The day of the week that the course is taught */ - day: Day; - /** The start time of the course, in minutes since midnight */ - startTime: number; - /** The end time of the course, in minutes since midnight */ - endTime: number; - /** The location that the course is taught */ - room?: Room; -}; +import { CourseMeeting, Day, DAY_MAP } from './CourseMeeting'; /** * This represents the schedule for a course, which includes all the meeting times for the course, as well as helper functions for parsing, serializing, and deserializing the schedule */ export class CourseSchedule { - meetings: CourseMeeting[]; + meetings: CourseMeeting[] = []; - constructor(courseSchedule: CourseSchedule | Serialized) { + constructor(courseSchedule?: Serialized) { + if (!courseSchedule) { + return; + } Object.assign(this, courseSchedule); } /** - * Given a string representation of a schedule, parse it into a CourseSchedule object + * Given a string representation of the meeting information for a class, parse it into a CourseMeeting object * @param dayLine a string representation of the days of the week that the course is taught: MWF, TR, etc. * @param timeLine a string representation of a time-range that the course is taught: 10:00 am - 11:00 am, 1:00 pm - 2:00 pm, etc. * @param roomLine a string representation of the room that the course is taught in: JGB 2.302, etc. - * @returns an array of CourseMeeting objects, which represent the schedule for the course + * @returns CourseMeeting object representing the meeting information */ - static parse(dayLine: string, timeLine: string, roomLine: string): CourseMeeting[] { + static parse(dayLine: string, timeLine: string, roomLine: string): CourseMeeting { try { let days: Day[] = dayLine .split('') @@ -87,15 +53,15 @@ export class CourseSchedule { const [building, number] = roomLine.split(' '); - return days.map(day => ({ - day, + return new CourseMeeting({ + days, startTime, endTime, room: { building, number, }, - })); + }); } catch (e) { throw new Error(`Failed to parse schedule: ${dayLine} ${timeLine} ${roomLine}`); } diff --git a/src/views/components/common/Text/Text.tsx b/src/views/components/common/Text/Text.tsx index b726e88a..b4e2880d 100644 --- a/src/views/components/common/Text/Text.tsx +++ b/src/views/components/common/Text/Text.tsx @@ -25,6 +25,7 @@ export default function Text(props: PropsWithChildren) { style.color ??= colors?.[props.color ?? 'charcoal']; style.fontSize ??= fonts?.[`${props.size ?? 'medium'}_size`]; style.fontWeight ??= fonts?.[`${props.weight ?? 'regular'}_weight`]; + style.lineHeight ??= fonts?.[`${props.size ?? 'medium'}_line_height`]; if (props.span) { return ( diff --git a/src/views/components/injected/CoursePopup/CoursePopup.tsx b/src/views/components/injected/CoursePopup/CoursePopup.tsx index 5f2822eb..0a1c6e8e 100644 --- a/src/views/components/injected/CoursePopup/CoursePopup.tsx +++ b/src/views/components/injected/CoursePopup/CoursePopup.tsx @@ -16,6 +16,7 @@ interface Props { * The popup that appears when the user clicks on a course for more details. */ export default function CoursePopup({ course, onClose }: Props) { + console.log(course); return ( diff --git a/src/views/lib/CourseCatalogScraper.ts b/src/views/lib/CourseCatalogScraper.ts index 6cbc85ec..89f45b40 100644 --- a/src/views/lib/CourseCatalogScraper.ts +++ b/src/views/lib/CourseCatalogScraper.ts @@ -1,5 +1,5 @@ import { Course, Instructor, Status, InstructionMode, ScrapedRow } from 'src/shared/types/Course'; -import { CourseSchedule, CourseMeeting } from 'src/shared/types/CourseSchedule'; +import { CourseSchedule } from 'src/shared/types/CourseSchedule'; import { SiteSupport } from 'src/views/lib/getSiteSupport'; /** @@ -289,17 +289,18 @@ export class CourseCatalogScraper { throw new Error('Schedule data is malformed'); } - const meetings: CourseMeeting[] = []; + const schedule = new CourseSchedule(); for (let i = 0; i < dayLines.length; i += 1) { - const lineMeetings = CourseSchedule.parse( - dayLines[i].textContent || '', - hourLines[i].textContent || '', - roomLines[i].textContent || '' + schedule.meetings.push( + CourseSchedule.parse( + dayLines[i].textContent || '', + hourLines[i].textContent || '', + roomLines[i].textContent || '' + ) ); - meetings.push(...lineMeetings); } - return new CourseSchedule({ meetings }); + return schedule; } } diff --git a/src/views/styles/fonts.module.scss b/src/views/styles/fonts.module.scss index 022fcb09..d92cb806 100644 --- a/src/views/styles/fonts.module.scss +++ b/src/views/styles/fonts.module.scss @@ -30,6 +30,13 @@ $large_size: 24px; $x_large_size: 32px; $xx_large_size: 48px; +$x_small_line_height: 12px; +$small_line_height: 16px; +$medium_line_height: 20px; +$large_line_height: 28px; +$x_large_line_height: 36px; +$xx_large_line_height: 52px; + :export { light_weight: $light_weight; regular_weight: $regular_weight; @@ -44,4 +51,12 @@ $xx_large_size: 48px; large_size: $large_size; x_large_size: $x_large_size; xx_large_size: $xx_large_size; + + x_small_line_height: $x_small_line_height; + small_line_height: $small_line_height; + medium_line_height: $medium_line_height; + large_line_height: $large_line_height; + x_large_line_height: $x_large_line_height; + xx_large_line_height: $xx_large_line_height; + } diff --git a/src/views/styles/fonts.module.scss.d.ts b/src/views/styles/fonts.module.scss.d.ts index 9f6af4c8..a55ca581 100644 --- a/src/views/styles/fonts.module.scss.d.ts +++ b/src/views/styles/fonts.module.scss.d.ts @@ -22,6 +22,18 @@ export interface ISizes { xx_large_size: number; } +/** + * the type for all the line height scss variables exported from fonts.module.scss + */ +export interface LineHeight { + x_small_line_height: number; + small_line_height: number; + medium_line_height: number; + large_line_height: number; + x_large_line_height: number; + xx_large_line_height: number; +} + /** A utility type that removes the _weight postfix from the variable names for weights */ export type Weight = keyof IWeights extends `${infer U}_weight` ? U : never; @@ -32,7 +44,7 @@ export type Size = keyof ISizes extends `${infer U}_size` ? U : never; * This is a file that we need to create to tell typescript what the shape of the css modules is * when we import them into ts/tsx files */ -export type IFonts = IWeights & ISizes; +export type IFonts = IWeights & ISizes & LineHeight; declare const fonts: IFonts; export default fonts; diff --git a/todo.md b/todo.md index 78187da9..d7479f61 100644 --- a/todo.md +++ b/todo.md @@ -42,7 +42,9 @@ Last Updated: 03/4/2023 - [ ] see who else is looking at certain classes (waitlist, or has it in their schedule) - [ ] github contributors displayed somewhere - [ ] Links to discord/github +- [ ] my twitter handle for support - [ ] on CS/ECE/MIS pages, show some banner somewhere for students to join the dev team! +- [ ] suggest fun classes to take? or classes that are easy A's? or classes that have not a lot of people / lot of people in them? - [ ] CHECK ALL THE TODOs in CODE BEFORE LAUNCHING ## LEGACY FROM UTRP-V1