From 882b5b4e00632e631531998bd1cdf9da005884d8 Mon Sep 17 00:00:00 2001 From: Sriram Hariharan Date: Thu, 16 Mar 2023 00:32:10 -0500 Subject: [PATCH] schedules working --- src/background/background.ts | 2 ++ src/background/handler/userScheduleHandler.ts | 16 +++++++++ src/background/lib/addCourse.ts | 18 ++++++++++ src/background/lib/createSchedule.ts | 0 src/background/lib/removeCourse.ts | 18 ++++++++++ src/shared/messages/UserScheduleMessages.ts | 7 ++++ src/shared/messages/index.ts | 6 +++- src/shared/storage/UserScheduleStore.ts | 10 +++++- src/shared/types/UserSchedule.ts | 16 ++------- .../CourseButtons/CourseButtons.tsx | 36 +++++++++++-------- .../CoursePopup/CourseHeader/CourseHeader.tsx | 2 +- .../injected/TableRow/TableRow.module.scss | 7 ++++ .../components/injected/TableRow/TableRow.tsx | 25 +++++++++---- 13 files changed, 125 insertions(+), 38 deletions(-) create mode 100644 src/background/handler/userScheduleHandler.ts create mode 100644 src/background/lib/addCourse.ts create mode 100644 src/background/lib/createSchedule.ts create mode 100644 src/background/lib/removeCourse.ts create mode 100644 src/shared/messages/UserScheduleMessages.ts diff --git a/src/background/background.ts b/src/background/background.ts index e60d8078..d5c51749 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -9,6 +9,7 @@ import { sessionStore } from '../shared/storage/sessionStore'; import browserActionHandler from './handler/browserActionHandler'; import hotReloadingHandler from './handler/hotReloadingHandler'; import tabManagementHandler from './handler/tabManagementHandler'; +import userScheduleHandler from './handler/userScheduleHandler'; onServiceWorkerAlive(); @@ -34,6 +35,7 @@ const messageListener = new MessageListener({ ...browserActionHandler, ...hotReloadingHandler, ...tabManagementHandler, + ...userScheduleHandler, }); messageListener.listen(); diff --git a/src/background/handler/userScheduleHandler.ts b/src/background/handler/userScheduleHandler.ts new file mode 100644 index 00000000..456c496c --- /dev/null +++ b/src/background/handler/userScheduleHandler.ts @@ -0,0 +1,16 @@ +import { MessageHandler } from 'chrome-extension-toolkit'; +import { UserScheduleMessages } from 'src/shared/messages/UserScheduleMessages'; +import { Course } from 'src/shared/types/Course'; +import addCourse from '../lib/addCourse'; +import removeCourse from '../lib/removeCourse'; + +const userScheduleHandler: MessageHandler = { + addCourse({ data, sendResponse }) { + addCourse(data.scheduleId, new Course(data.course)).then(sendResponse); + }, + removeCourse({ data, sendResponse }) { + removeCourse(data.scheduleId, new Course(data.course)).then(sendResponse); + }, +}; + +export default userScheduleHandler; diff --git a/src/background/lib/addCourse.ts b/src/background/lib/addCourse.ts new file mode 100644 index 00000000..c90ae0b1 --- /dev/null +++ b/src/background/lib/addCourse.ts @@ -0,0 +1,18 @@ +import { userScheduleStore } from 'src/shared/storage/userScheduleStore'; +import { Course } from 'src/shared/types/Course'; + +/** + * + */ +export default async function addCourse(scheduleId: string, course: Course): Promise { + const schedules = await userScheduleStore.get('schedules'); + const activeSchedule = schedules.find(s => s.id === scheduleId); + if (!activeSchedule) { + throw new Error('Schedule not found'); + } + + activeSchedule.creditHours += course.creditHours; + activeSchedule.courses.push(course); + + await userScheduleStore.set('schedules', schedules); +} diff --git a/src/background/lib/createSchedule.ts b/src/background/lib/createSchedule.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/background/lib/removeCourse.ts b/src/background/lib/removeCourse.ts new file mode 100644 index 00000000..aa8b05d3 --- /dev/null +++ b/src/background/lib/removeCourse.ts @@ -0,0 +1,18 @@ +import { userScheduleStore } from 'src/shared/storage/userScheduleStore'; +import { Course } from 'src/shared/types/Course'; + +/** + * + */ +export default async function removeCourse(scheduleId: string, course: Course): Promise { + const schedules = await userScheduleStore.get('schedules'); + const activeSchedule = schedules.find(s => s.id === scheduleId); + if (!activeSchedule) { + throw new Error('Schedule not found'); + } + + activeSchedule.creditHours -= course.creditHours; + activeSchedule.courses = activeSchedule.courses.filter(c => c.uniqueId !== course.uniqueId); + + await userScheduleStore.set('schedules', schedules); +} diff --git a/src/shared/messages/UserScheduleMessages.ts b/src/shared/messages/UserScheduleMessages.ts new file mode 100644 index 00000000..738e0569 --- /dev/null +++ b/src/shared/messages/UserScheduleMessages.ts @@ -0,0 +1,7 @@ +import { Course } from '../types/Course'; + +export interface UserScheduleMessages { + addCourse: (data: { scheduleId: string; course: Course }) => void; + + removeCourse: (data: { scheduleId: string; course: Course }) => void; +} diff --git a/src/shared/messages/index.ts b/src/shared/messages/index.ts index 7b5645e2..f5b4ba10 100644 --- a/src/shared/messages/index.ts +++ b/src/shared/messages/index.ts @@ -3,11 +3,15 @@ import TAB_MESSAGES from './TabMessages'; import BrowserActionMessages from './BrowserActionMessages'; import HotReloadingMessages from './HotReloadingMessages'; import TabManagementMessages from './TabManagementMessages'; +import { UserScheduleMessages } from './UserScheduleMessages'; /** * This is a type with all the message definitions that can be sent TO the background script */ -export type BACKGROUND_MESSAGES = BrowserActionMessages & TabManagementMessages & HotReloadingMessages; +export type BACKGROUND_MESSAGES = BrowserActionMessages & + TabManagementMessages & + HotReloadingMessages & + UserScheduleMessages; /** * A utility object that can be used to send type-safe messages to the background script diff --git a/src/shared/storage/UserScheduleStore.ts b/src/shared/storage/UserScheduleStore.ts index 98100ddb..cf8830bd 100644 --- a/src/shared/storage/UserScheduleStore.ts +++ b/src/shared/storage/UserScheduleStore.ts @@ -1,5 +1,6 @@ import { createLocalStore, debugStore } from 'chrome-extension-toolkit'; import { UserSchedule } from 'src/shared/types/UserSchedule'; +import { v4 as uuidv4 } from 'uuid'; interface IUserScheduleStore { schedules: UserSchedule[]; @@ -9,7 +10,14 @@ interface IUserScheduleStore { * A store that is used for storing user schedules (and the active schedule) */ export const userScheduleStore = createLocalStore({ - schedules: [], + schedules: [ + new UserSchedule({ + courses: [], + id: uuidv4(), + name: 'Schedule 1', + creditHours: 0, + }), + ], }); debugStore({ userScheduleStore }); diff --git a/src/shared/types/UserSchedule.ts b/src/shared/types/UserSchedule.ts index c3f9db0a..fd74dc92 100644 --- a/src/shared/types/UserSchedule.ts +++ b/src/shared/types/UserSchedule.ts @@ -8,9 +8,11 @@ export class UserSchedule { courses: Course[]; id: string; name: string; + creditHours: number; constructor(schedule: Serialized) { this.courses = schedule.courses.map(c => new Course(c)); + this.creditHours = this.courses.reduce((acc, course) => acc + course.creditHours, 0); this.id = schedule.id; this.name = schedule.name; } @@ -18,18 +20,4 @@ export class UserSchedule { containsCourse(course: Course): boolean { return this.courses.some(c => c.uniqueId === course.uniqueId); } - - getCreditHours(): number { - return this.courses.reduce((acc, course) => acc + course.creditHours, 0); - } - - addCourse(course: Course): void { - if (!this.containsCourse(course)) { - this.courses.push(course); - } - } - - removeCourse(course: Course): void { - this.courses = this.courses.filter(c => c.uniqueId !== course.uniqueId); - } } diff --git a/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx b/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx index c40575a1..aa50163e 100644 --- a/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx +++ b/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { bMessenger } from 'src/shared/messages'; -import { userScheduleStore } from 'src/shared/storage/userScheduleStore'; import { Course } from 'src/shared/types/Course'; import { UserSchedule } from 'src/shared/types/UserSchedule'; import { Button } from 'src/views/components/common/Button/Button'; @@ -14,7 +13,7 @@ type Props = { course: Course; }; -const { openNewTab } = bMessenger; +const { openNewTab, addCourse, removeCourse } = bMessenger; /** * This component displays the buttons for the course info popup, that allow the user to either @@ -64,17 +63,21 @@ export default function CourseButtons({ course, activeSchedule }: Props) { openNewTab({ url: url.toString() }); }; - const saveCourse = async () => { - const schedules = await userScheduleStore.get('schedules'); - const active = schedules.find(schedule => schedule.id === activeSchedule?.id); - - if (!active) return; - - active.addCourse(course); - - await userScheduleStore.set('schedules', schedules); + const handleSaveCourse = async () => { + if (!activeSchedule) return; + addCourse({ course, scheduleId: activeSchedule.id }); }; + const handleRemoveCourse = async () => { + if (!activeSchedule) return; + removeCourse({ course, scheduleId: activeSchedule.id }); + }; + + const isCourseSaved = (() => { + if (!activeSchedule) return false; + return Boolean(activeSchedule.containsCourse(course)); + })(); + return ( - ); diff --git a/src/views/components/injected/CoursePopup/CourseHeader/CourseHeader.tsx b/src/views/components/injected/CoursePopup/CourseHeader/CourseHeader.tsx index fe5daf9d..a6762741 100644 --- a/src/views/components/injected/CoursePopup/CourseHeader/CourseHeader.tsx +++ b/src/views/components/injected/CoursePopup/CourseHeader/CourseHeader.tsx @@ -91,7 +91,7 @@ export default function CourseHeader({ course, activeSchedule, onClose }: Props) ))} - + ); } diff --git a/src/views/components/injected/TableRow/TableRow.module.scss b/src/views/components/injected/TableRow/TableRow.module.scss index 6ab95bd0..61038f71 100644 --- a/src/views/components/injected/TableRow/TableRow.module.scss +++ b/src/views/components/injected/TableRow/TableRow.module.scss @@ -18,3 +18,10 @@ font-weight: bold !important; } } + +.isConflict { + * { + color: $speedway_brick !important; + text-decoration: line-through !important; + } +} diff --git a/src/views/components/injected/TableRow/TableRow.tsx b/src/views/components/injected/TableRow/TableRow.tsx index d037df0f..5b5c2e3e 100644 --- a/src/views/components/injected/TableRow/TableRow.tsx +++ b/src/views/components/injected/TableRow/TableRow.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; import { Course, ScrapedRow } from 'src/shared/types/Course'; +import { UserSchedule } from 'src/shared/types/UserSchedule'; import { Button } from '../../common/Button/Button'; import Icon from '../../common/Icon/Icon'; import styles from './TableRow.module.scss'; @@ -9,17 +10,14 @@ interface Props { isSelected: boolean; row: ScrapedRow; onClick: (...args: any[]) => any; - /** - * Whether the course is in the user' active schedule. - */ - isInActiveSchedule: boolean; + activeSchedule?: UserSchedule; } /** * This component is injected into each row of the course catalog table. * @returns a react portal to the new td in the column or null if the column has not been created yet. */ -export default function TableRow({ row, isSelected, isInActiveSchedule, onClick }: Props): JSX.Element | null { +export default function TableRow({ row, isSelected, activeSchedule, onClick }: Props): JSX.Element | null { const [container, setContainer] = useState(null); const { element, course } = row; @@ -40,8 +38,21 @@ export default function TableRow({ row, isSelected, isInActiveSchedule, onClick }, [isSelected, element.classList]); useEffect(() => { - element.classList[isInActiveSchedule ? 'add' : 'remove'](styles.inActiveSchedule); - }, [isInActiveSchedule, element.classList]); + if (!activeSchedule || !course) return; + + const isInSchedule = activeSchedule.containsCourse(course); + + element.classList[isInSchedule ? 'add' : 'remove'](styles.inActiveSchedule); + + return () => { + element.classList.remove(styles.inActiveSchedule); + }; + }, [activeSchedule, element.classList]); + + useEffect(() => { + // if (!activeSchedule || !course) return; + // TODO: handle conflicts here + }, []); if (!container) { return null;