diff --git a/src/background/handler/userScheduleHandler.ts b/src/background/handler/userScheduleHandler.ts index 456c496c..8a33fcb5 100644 --- a/src/background/handler/userScheduleHandler.ts +++ b/src/background/handler/userScheduleHandler.ts @@ -6,10 +6,10 @@ import removeCourse from '../lib/removeCourse'; const userScheduleHandler: MessageHandler = { addCourse({ data, sendResponse }) { - addCourse(data.scheduleId, new Course(data.course)).then(sendResponse); + addCourse(data.scheduleName, new Course(data.course)).then(sendResponse); }, removeCourse({ data, sendResponse }) { - removeCourse(data.scheduleId, new Course(data.course)).then(sendResponse); + removeCourse(data.scheduleName, new Course(data.course)).then(sendResponse); }, }; diff --git a/src/background/lib/addCourse.ts b/src/background/lib/addCourse.ts index eff142ae..4d0e6bd8 100644 --- a/src/background/lib/addCourse.ts +++ b/src/background/lib/addCourse.ts @@ -1,12 +1,12 @@ -import { userScheduleStore } from 'src/shared/storage/UserScheduleStore'; +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); +export default async function addCourse(scheduleName: string, course: Course): Promise { + const schedules = await UserScheduleStore.get('schedules'); + const activeSchedule = schedules.find(s => s.name === scheduleName); if (!activeSchedule) { throw new Error('Schedule not found'); } @@ -14,5 +14,5 @@ export default async function addCourse(scheduleId: string, course: Course): Pro activeSchedule.creditHours += course.creditHours; activeSchedule.courses.push(course); - await userScheduleStore.set('schedules', schedules); + await UserScheduleStore.set('schedules', schedules); } diff --git a/src/background/lib/removeCourse.ts b/src/background/lib/removeCourse.ts index 7c73f015..ca93684d 100644 --- a/src/background/lib/removeCourse.ts +++ b/src/background/lib/removeCourse.ts @@ -1,12 +1,12 @@ -import { userScheduleStore } from 'src/shared/storage/UserScheduleStore'; +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); +export default async function removeCourse(scheduleName: string, course: Course): Promise { + const schedules = await UserScheduleStore.get('schedules'); + const activeSchedule = schedules.find(s => s.name === scheduleName); if (!activeSchedule) { throw new Error('Schedule not found'); } @@ -14,5 +14,5 @@ export default async function removeCourse(scheduleId: string, course: Course): activeSchedule.creditHours -= course.creditHours; activeSchedule.courses = activeSchedule.courses.filter(c => c.uniqueId !== course.uniqueId); - await userScheduleStore.set('schedules', schedules); + await UserScheduleStore.set('schedules', schedules); } diff --git a/src/debug/index.tsx b/src/debug/index.tsx index 7865b29c..cb11077e 100644 --- a/src/debug/index.tsx +++ b/src/debug/index.tsx @@ -1,7 +1,7 @@ import './hotReload'; import React, { useEffect } from 'react'; -import { render } from 'react-dom'; import { DevStore } from 'src/shared/storage/DevStore'; +import render from 'src/views/lib/react'; const manifest = chrome.runtime.getManifest(); diff --git a/src/shared/messages/UserScheduleMessages.ts b/src/shared/messages/UserScheduleMessages.ts index 738e0569..a601a0aa 100644 --- a/src/shared/messages/UserScheduleMessages.ts +++ b/src/shared/messages/UserScheduleMessages.ts @@ -1,7 +1,7 @@ import { Course } from '../types/Course'; export interface UserScheduleMessages { - addCourse: (data: { scheduleId: string; course: Course }) => void; + addCourse: (data: { scheduleName: string; course: Course }) => void; - removeCourse: (data: { scheduleId: string; course: Course }) => void; + removeCourse: (data: { scheduleName: string; course: Course }) => void; } diff --git a/src/shared/storage/OptionsStore.ts b/src/shared/storage/OptionsStore.ts index a7b1e12f..bc5cccbf 100644 --- a/src/shared/storage/OptionsStore.ts +++ b/src/shared/storage/OptionsStore.ts @@ -10,9 +10,9 @@ interface IOptionsStore { shouldScrollToLoad: boolean; } -export const optionsStore = createSyncStore({ +export const OptionsStore = createSyncStore({ shouldHighlightConflicts: true, shouldScrollToLoad: true, }); -debugStore({ optionsStore }); +debugStore({ OptionsStore }); diff --git a/src/shared/storage/UserScheduleStore.ts b/src/shared/storage/UserScheduleStore.ts index cf8830bd..618e0f45 100644 --- a/src/shared/storage/UserScheduleStore.ts +++ b/src/shared/storage/UserScheduleStore.ts @@ -1,23 +1,23 @@ import { createLocalStore, debugStore } from 'chrome-extension-toolkit'; import { UserSchedule } from 'src/shared/types/UserSchedule'; -import { v4 as uuidv4 } from 'uuid'; interface IUserScheduleStore { schedules: UserSchedule[]; + activeIndex: number; } /** * A store that is used for storing user schedules (and the active schedule) */ -export const userScheduleStore = createLocalStore({ +export const UserScheduleStore = createLocalStore({ schedules: [ new UserSchedule({ courses: [], - id: uuidv4(), name: 'Schedule 1', creditHours: 0, }), ], + activeIndex: 0, }); -debugStore({ userScheduleStore }); +debugStore({ userScheduleStore: UserScheduleStore }); diff --git a/src/shared/types/UserSchedule.ts b/src/shared/types/UserSchedule.ts index fd74dc92..b35e9e26 100644 --- a/src/shared/types/UserSchedule.ts +++ b/src/shared/types/UserSchedule.ts @@ -6,14 +6,12 @@ import { Course } from './Course'; */ 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; } diff --git a/src/views/components/CourseCatalogMain.tsx b/src/views/components/CourseCatalogMain.tsx index 03aef3fa..6036388f 100644 --- a/src/views/components/CourseCatalogMain.tsx +++ b/src/views/components/CourseCatalogMain.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Course, ScrapedRow } from 'src/shared/types/Course'; import { useKeyPress } from '../hooks/useKeyPress'; -import useUserSchedules from '../hooks/useUserSchedules'; +import useSchedules from '../hooks/useSchedules'; import { CourseCatalogScraper } from '../lib/CourseCatalogScraper'; import getCourseTableRows from '../lib/getCourseTableRows'; import { SiteSupport } from '../lib/getSiteSupport'; @@ -44,9 +44,6 @@ export default function CourseCatalogMain({ support }: Props) { setRows([...rows, ...newRows]); }; - const schedules = useUserSchedules(); - const [activeSchedule] = schedules; - const handleRowButtonClick = (course: Course) => () => { setSelectedCourse(course); }; @@ -57,6 +54,12 @@ export default function CourseCatalogMain({ support }: Props) { useKeyPress('Escape', handleClearSelectedCourse); + const [activeSchedule] = useSchedules(); + + if (!activeSchedule) { + return null; + } + return ( diff --git a/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.module.scss b/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.module.scss index d1ff0378..701fe303 100644 --- a/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.module.scss +++ b/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.module.scss @@ -3,6 +3,7 @@ .container { margin: 12px 4px; display: flex; + flex-wrap: wrap; align-items: center; justify-content: center; box-shadow: none; diff --git a/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx b/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx index aa50163e..06279fdc 100644 --- a/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx +++ b/src/views/components/injected/CoursePopup/CourseHeader/CourseButtons/CourseButtons.tsx @@ -65,12 +65,12 @@ export default function CourseButtons({ course, activeSchedule }: Props) { const handleSaveCourse = async () => { if (!activeSchedule) return; - addCourse({ course, scheduleId: activeSchedule.id }); + addCourse({ course, scheduleName: activeSchedule.name }); }; const handleRemoveCourse = async () => { if (!activeSchedule) return; - removeCourse({ course, scheduleId: activeSchedule.id }); + removeCourse({ course, scheduleName: activeSchedule.name }); }; const isCourseSaved = (() => { diff --git a/src/views/components/injected/CoursePopup/CourseHeader/CourseHeader.tsx b/src/views/components/injected/CoursePopup/CourseHeader/CourseHeader.tsx index a6762741..4e9e0697 100644 --- a/src/views/components/injected/CoursePopup/CourseHeader/CourseHeader.tsx +++ b/src/views/components/injected/CoursePopup/CourseHeader/CourseHeader.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { Course } from 'src/shared/types/Course'; import { UserSchedule } from 'src/shared/types/UserSchedule'; import Card from 'src/views/components/common/Card/Card'; -import Divider from 'src/views/components/common/Divider/Divider'; import Icon from 'src/views/components/common/Icon/Icon'; import Link from 'src/views/components/common/Link/Link'; import Text from 'src/views/components/common/Text/Text'; diff --git a/src/views/components/injected/CoursePopup/CoursePopup.module.scss b/src/views/components/injected/CoursePopup/CoursePopup.module.scss index 8e263b4c..336b0057 100644 --- a/src/views/components/injected/CoursePopup/CoursePopup.module.scss +++ b/src/views/components/injected/CoursePopup/CoursePopup.module.scss @@ -1,7 +1,7 @@ .popup { border-radius: 12px; position: relative; - width: 55%; + max-width: 55%; overflow-y: auto; max-height: 90%; diff --git a/src/views/hooks/useSchedules.ts b/src/views/hooks/useSchedules.ts new file mode 100644 index 00000000..9f3e4839 --- /dev/null +++ b/src/views/hooks/useSchedules.ts @@ -0,0 +1,37 @@ +import { Serialized } from 'chrome-extension-toolkit'; +import { useEffect, useState } from 'react'; +import { UserScheduleStore } from 'src/shared/storage/UserScheduleStore'; +import { UserSchedule } from 'src/shared/types/UserSchedule'; + +export default function useSchedules(): [active: UserSchedule | null, schedules: UserSchedule[]] { + const [schedules, setSchedules] = useState([]); + const [activeIndex, setActiveIndex] = useState(0); + const [activeSchedule, setActiveSchedule] = useState(null); + + useEffect(() => { + Promise.all([UserScheduleStore.get('schedules'), UserScheduleStore.get('activeIndex')]).then( + ([schedules, activeIndex]) => { + setSchedules(schedules.map(s => new UserSchedule(s))); + setActiveIndex(activeIndex); + setActiveSchedule(new UserSchedule(schedules[activeIndex])); + } + ); + + const l1 = UserScheduleStore.listen('schedules', ({ newValue }) => { + setSchedules(newValue.map(s => new UserSchedule(s))); + setActiveSchedule(new UserSchedule(newValue[activeIndex])); + }); + + const l2 = UserScheduleStore.listen('activeIndex', ({ newValue }) => { + setActiveIndex(newValue); + setActiveSchedule(new UserSchedule(schedules[newValue])); + }); + + return () => { + UserScheduleStore.removeListener(l1); + UserScheduleStore.removeListener(l2); + }; + }, []); + + return [activeSchedule, schedules]; +} diff --git a/src/views/hooks/useUserSchedules.ts b/src/views/hooks/useUserSchedules.ts deleted file mode 100644 index 4f322a78..00000000 --- a/src/views/hooks/useUserSchedules.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Serialized } from 'chrome-extension-toolkit'; -import { useEffect, useState } from 'react'; -import { userScheduleStore } from 'src/shared/storage/UserScheduleStore'; -import { UserSchedule } from 'src/shared/types/UserSchedule'; - -export default function useUserSchedules(): UserSchedule[] { - const [schedules, setSchedules] = useState([]); - - useEffect(() => { - function updateSchedules(schedules: Serialized[]) { - setSchedules(schedules.map(s => new UserSchedule(s))); - } - - userScheduleStore.get('schedules').then(updateSchedules); - - const listener = userScheduleStore.listen('schedules', ({ newValue }) => { - updateSchedules(newValue); - }); - - return () => { - userScheduleStore.removeListener(listener); - }; - }, []); - - return schedules; -} diff --git a/src/views/index.tsx b/src/views/index.tsx index 55085876..bb2a7291 100644 --- a/src/views/index.tsx +++ b/src/views/index.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { render } from 'react-dom'; +import render from './lib/react'; + import { ContextInvalidated, createShadowDOM, isExtensionPopup, onContextInvalidated } from 'chrome-extension-toolkit'; import CourseCatalogMain from './components/CourseCatalogMain'; import colors from './styles/colors.module.scss'; @@ -14,7 +15,7 @@ if (!support) { } if (support === SiteSupport.EXTENSION_POPUP) { - render(, document.getElementById('root')); + render(, document.body); } if (support === SiteSupport.MY_CALENDAR) { @@ -39,6 +40,7 @@ onContextInvalidated(() => { const div = document.createElement('div'); div.id = 'context-invalidated-container'; document.body.appendChild(div); + render( , div diff --git a/src/views/lib/react/index.tsx b/src/views/lib/react/index.tsx new file mode 100644 index 00000000..e8d7f76e --- /dev/null +++ b/src/views/lib/react/index.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; + +export default function render(element: React.ReactElement, container: HTMLElement | ShadowRoot | null) { + if (!container) { + throw new Error('Container is null'); + } + const root = ReactDOM.createRoot(container); + root.render(element); +}