refactoring courseschedule storage

This commit is contained in:
Sriram Hariharan
2023-09-17 19:29:00 -05:00
parent 9658697d96
commit aea9b16f98
17 changed files with 83 additions and 59 deletions

View File

@@ -6,10 +6,10 @@ import removeCourse from '../lib/removeCourse';
const userScheduleHandler: MessageHandler<UserScheduleMessages> = { const userScheduleHandler: MessageHandler<UserScheduleMessages> = {
addCourse({ data, sendResponse }) { 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, sendResponse }) {
removeCourse(data.scheduleId, new Course(data.course)).then(sendResponse); removeCourse(data.scheduleName, new Course(data.course)).then(sendResponse);
}, },
}; };

View File

@@ -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'; import { Course } from 'src/shared/types/Course';
/** /**
* *
*/ */
export default async function addCourse(scheduleId: string, course: Course): Promise<void> { export default async function addCourse(scheduleName: string, course: Course): Promise<void> {
const schedules = await userScheduleStore.get('schedules'); const schedules = await UserScheduleStore.get('schedules');
const activeSchedule = schedules.find(s => s.id === scheduleId); const activeSchedule = schedules.find(s => s.name === scheduleName);
if (!activeSchedule) { if (!activeSchedule) {
throw new Error('Schedule not found'); 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.creditHours += course.creditHours;
activeSchedule.courses.push(course); activeSchedule.courses.push(course);
await userScheduleStore.set('schedules', schedules); await UserScheduleStore.set('schedules', schedules);
} }

View File

@@ -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'; import { Course } from 'src/shared/types/Course';
/** /**
* *
*/ */
export default async function removeCourse(scheduleId: string, course: Course): Promise<void> { export default async function removeCourse(scheduleName: string, course: Course): Promise<void> {
const schedules = await userScheduleStore.get('schedules'); const schedules = await UserScheduleStore.get('schedules');
const activeSchedule = schedules.find(s => s.id === scheduleId); const activeSchedule = schedules.find(s => s.name === scheduleName);
if (!activeSchedule) { if (!activeSchedule) {
throw new Error('Schedule not found'); throw new Error('Schedule not found');
} }
@@ -14,5 +14,5 @@ export default async function removeCourse(scheduleId: string, course: Course):
activeSchedule.creditHours -= course.creditHours; activeSchedule.creditHours -= course.creditHours;
activeSchedule.courses = activeSchedule.courses.filter(c => c.uniqueId !== course.uniqueId); activeSchedule.courses = activeSchedule.courses.filter(c => c.uniqueId !== course.uniqueId);
await userScheduleStore.set('schedules', schedules); await UserScheduleStore.set('schedules', schedules);
} }

View File

@@ -1,7 +1,7 @@
import './hotReload'; import './hotReload';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { render } from 'react-dom';
import { DevStore } from 'src/shared/storage/DevStore'; import { DevStore } from 'src/shared/storage/DevStore';
import render from 'src/views/lib/react';
const manifest = chrome.runtime.getManifest(); const manifest = chrome.runtime.getManifest();

View File

@@ -1,7 +1,7 @@
import { Course } from '../types/Course'; import { Course } from '../types/Course';
export interface UserScheduleMessages { 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;
} }

View File

@@ -10,9 +10,9 @@ interface IOptionsStore {
shouldScrollToLoad: boolean; shouldScrollToLoad: boolean;
} }
export const optionsStore = createSyncStore<IOptionsStore>({ export const OptionsStore = createSyncStore<IOptionsStore>({
shouldHighlightConflicts: true, shouldHighlightConflicts: true,
shouldScrollToLoad: true, shouldScrollToLoad: true,
}); });
debugStore({ optionsStore }); debugStore({ OptionsStore });

View File

@@ -1,23 +1,23 @@
import { createLocalStore, debugStore } from 'chrome-extension-toolkit'; import { createLocalStore, debugStore } from 'chrome-extension-toolkit';
import { UserSchedule } from 'src/shared/types/UserSchedule'; import { UserSchedule } from 'src/shared/types/UserSchedule';
import { v4 as uuidv4 } from 'uuid';
interface IUserScheduleStore { interface IUserScheduleStore {
schedules: UserSchedule[]; schedules: UserSchedule[];
activeIndex: number;
} }
/** /**
* A store that is used for storing user schedules (and the active schedule) * A store that is used for storing user schedules (and the active schedule)
*/ */
export const userScheduleStore = createLocalStore<IUserScheduleStore>({ export const UserScheduleStore = createLocalStore<IUserScheduleStore>({
schedules: [ schedules: [
new UserSchedule({ new UserSchedule({
courses: [], courses: [],
id: uuidv4(),
name: 'Schedule 1', name: 'Schedule 1',
creditHours: 0, creditHours: 0,
}), }),
], ],
activeIndex: 0,
}); });
debugStore({ userScheduleStore }); debugStore({ userScheduleStore: UserScheduleStore });

View File

@@ -6,14 +6,12 @@ import { Course } from './Course';
*/ */
export class UserSchedule { export class UserSchedule {
courses: Course[]; courses: Course[];
id: string;
name: string; name: string;
creditHours: number; creditHours: number;
constructor(schedule: Serialized<UserSchedule>) { constructor(schedule: Serialized<UserSchedule>) {
this.courses = schedule.courses.map(c => new Course(c)); this.courses = schedule.courses.map(c => new Course(c));
this.creditHours = this.courses.reduce((acc, course) => acc + course.creditHours, 0); this.creditHours = this.courses.reduce((acc, course) => acc + course.creditHours, 0);
this.id = schedule.id;
this.name = schedule.name; this.name = schedule.name;
} }

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Course, ScrapedRow } from 'src/shared/types/Course'; import { Course, ScrapedRow } from 'src/shared/types/Course';
import { useKeyPress } from '../hooks/useKeyPress'; import { useKeyPress } from '../hooks/useKeyPress';
import useUserSchedules from '../hooks/useUserSchedules'; import useSchedules from '../hooks/useSchedules';
import { CourseCatalogScraper } from '../lib/CourseCatalogScraper'; import { CourseCatalogScraper } from '../lib/CourseCatalogScraper';
import getCourseTableRows from '../lib/getCourseTableRows'; import getCourseTableRows from '../lib/getCourseTableRows';
import { SiteSupport } from '../lib/getSiteSupport'; import { SiteSupport } from '../lib/getSiteSupport';
@@ -44,9 +44,6 @@ export default function CourseCatalogMain({ support }: Props) {
setRows([...rows, ...newRows]); setRows([...rows, ...newRows]);
}; };
const schedules = useUserSchedules();
const [activeSchedule] = schedules;
const handleRowButtonClick = (course: Course) => () => { const handleRowButtonClick = (course: Course) => () => {
setSelectedCourse(course); setSelectedCourse(course);
}; };
@@ -57,6 +54,12 @@ export default function CourseCatalogMain({ support }: Props) {
useKeyPress('Escape', handleClearSelectedCourse); useKeyPress('Escape', handleClearSelectedCourse);
const [activeSchedule] = useSchedules();
if (!activeSchedule) {
return null;
}
return ( return (
<ExtensionRoot> <ExtensionRoot>
<RecruitmentBanner /> <RecruitmentBanner />

View File

@@ -3,6 +3,7 @@
.container { .container {
margin: 12px 4px; margin: 12px 4px;
display: flex; display: flex;
flex-wrap: wrap;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-shadow: none; box-shadow: none;

View File

@@ -65,12 +65,12 @@ export default function CourseButtons({ course, activeSchedule }: Props) {
const handleSaveCourse = async () => { const handleSaveCourse = async () => {
if (!activeSchedule) return; if (!activeSchedule) return;
addCourse({ course, scheduleId: activeSchedule.id }); addCourse({ course, scheduleName: activeSchedule.name });
}; };
const handleRemoveCourse = async () => { const handleRemoveCourse = async () => {
if (!activeSchedule) return; if (!activeSchedule) return;
removeCourse({ course, scheduleId: activeSchedule.id }); removeCourse({ course, scheduleName: activeSchedule.name });
}; };
const isCourseSaved = (() => { const isCourseSaved = (() => {

View File

@@ -2,7 +2,6 @@ import React from 'react';
import { Course } from 'src/shared/types/Course'; import { Course } from 'src/shared/types/Course';
import { UserSchedule } from 'src/shared/types/UserSchedule'; import { UserSchedule } from 'src/shared/types/UserSchedule';
import Card from 'src/views/components/common/Card/Card'; 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 Icon from 'src/views/components/common/Icon/Icon';
import Link from 'src/views/components/common/Link/Link'; import Link from 'src/views/components/common/Link/Link';
import Text from 'src/views/components/common/Text/Text'; import Text from 'src/views/components/common/Text/Text';

View File

@@ -1,7 +1,7 @@
.popup { .popup {
border-radius: 12px; border-radius: 12px;
position: relative; position: relative;
width: 55%; max-width: 55%;
overflow-y: auto; overflow-y: auto;
max-height: 90%; max-height: 90%;

View File

@@ -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<UserSchedule[]>([]);
const [activeIndex, setActiveIndex] = useState<number>(0);
const [activeSchedule, setActiveSchedule] = useState<UserSchedule | null>(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];
}

View File

@@ -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<UserSchedule[]>([]);
useEffect(() => {
function updateSchedules(schedules: Serialized<UserSchedule>[]) {
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;
}

View File

@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import render from './lib/react';
import { ContextInvalidated, createShadowDOM, isExtensionPopup, onContextInvalidated } from 'chrome-extension-toolkit'; import { ContextInvalidated, createShadowDOM, isExtensionPopup, onContextInvalidated } from 'chrome-extension-toolkit';
import CourseCatalogMain from './components/CourseCatalogMain'; import CourseCatalogMain from './components/CourseCatalogMain';
import colors from './styles/colors.module.scss'; import colors from './styles/colors.module.scss';
@@ -14,7 +15,7 @@ if (!support) {
} }
if (support === SiteSupport.EXTENSION_POPUP) { if (support === SiteSupport.EXTENSION_POPUP) {
render(<PopupMain />, document.getElementById('root')); render(<PopupMain />, document.body);
} }
if (support === SiteSupport.MY_CALENDAR) { if (support === SiteSupport.MY_CALENDAR) {
@@ -39,6 +40,7 @@ onContextInvalidated(() => {
const div = document.createElement('div'); const div = document.createElement('div');
div.id = 'context-invalidated-container'; div.id = 'context-invalidated-container';
document.body.appendChild(div); document.body.appendChild(div);
render( render(
<ContextInvalidated fontFamily='monospace' color={colors.white} backgroundColor={colors.burnt_orange} />, <ContextInvalidated fontFamily='monospace' color={colors.white} backgroundColor={colors.burnt_orange} />,
div div

View File

@@ -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);
}