refactoring courseschedule storage
This commit is contained in:
@@ -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);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 = (() => {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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%;
|
||||||
|
|
||||||
|
|||||||
37
src/views/hooks/useSchedules.ts
Normal file
37
src/views/hooks/useSchedules.ts
Normal 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];
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
10
src/views/lib/react/index.tsx
Normal file
10
src/views/lib/react/index.tsx
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user