From 8a6e9070e077b96e61e13c8496ec6e269151b5f3 Mon Sep 17 00:00:00 2001 From: doprz <52579214+doprz@users.noreply.github.com> Date: Thu, 22 Feb 2024 22:42:58 -0600 Subject: [PATCH] chore: lint-format-docs-tests-bugfixes (#105) * docs: add jsdoc * feat: change enums to as const objects * chore(test): add themeColors.test.ts * fix: fix tests and bugs with strings.ts util * fix: path alias imports and tsconfig file bug * fix: remove --max-warnings 0 --- .releaserc.json | 2 +- package.json | 4 +- src/manifest.ts | 1 + src/pages/background/background.ts | 3 +- .../handler/browserActionHandler.ts | 4 +- .../background/handler/hotReloadingHandler.ts | 4 +- .../handler/tabManagementHandler.ts | 5 +- .../background/handler/userScheduleHandler.ts | 5 +- src/pages/background/lib/addCourse.ts | 2 +- src/pages/background/lib/clearCourses.ts | 5 + src/pages/background/lib/createSchedule.ts | 2 +- src/pages/background/lib/deleteSchedule.ts | 6 + src/pages/background/lib/removeCourse.ts | 2 +- src/pages/background/lib/renameSchedule.ts | 6 + src/pages/background/lib/switchSchedule.ts | 6 + src/pages/calendar/CalendarMain.tsx | 2 +- src/pages/calendar/index.html | 26 ++- src/pages/calendar/index.tsx | 1 + src/pages/content/index.tsx | 4 +- src/pages/debug/App.tsx | 2 +- src/pages/debug/index.html | 26 ++- src/pages/options/App.tsx | 2 +- src/pages/options/index.html | 26 ++- src/pages/options/index.tsx | 1 + src/pages/popup/index.html | 26 ++- src/pages/popup/index.tsx | 1 + src/shared/messages/BrowserActionMessages.ts | 1 + src/shared/messages/HotReloadingMessages.ts | 1 + src/shared/messages/UserScheduleMessages.ts | 5 +- src/shared/messages/index.ts | 9 +- src/shared/storage/ExtensionStore.ts | 2 - src/shared/types/Course.ts | 25 ++- src/shared/types/CourseMeeting.ts | 2 +- src/shared/types/CourseSchedule.ts | 6 +- src/shared/types/Distribution.ts | 2 +- src/shared/types/Instructor.ts | 3 +- src/shared/types/UserSchedule.ts | 3 +- src/shared/util/colors.ts | 10 +- src/shared/util/icons.tsx | 9 +- src/shared/util/string.ts | 4 +- src/shared/util/tests/string.test.ts | 51 +++-- src/shared/util/tests/themeColors.test.ts | 51 +++++ src/shared/util/themeColors.ts | 45 +++-- src/shared/util/time.ts | 4 +- src/stories/components/Button.stories.tsx | 5 +- src/stories/components/Card.stories.tsx | 24 +-- src/stories/components/Chip.stories.tsx | 6 +- .../ConflictsWithWarning.stories.tsx | 8 +- .../components/CourseStatus.stories.tsx | 7 +- src/stories/components/Divider.stories.tsx | 7 +- src/stories/components/Dropdown.stories.tsx | 12 +- .../components/ImportantLinks.stories.tsx | 4 +- src/stories/components/InfoCard.stories.tsx | 6 +- src/stories/components/Link.stories.ts | 28 +-- src/stories/components/List.stories.tsx | 19 +- .../components/PopupCourseBlock.stories.tsx | 32 +-- src/stories/components/PopupMain.stories.tsx | 12 +- .../SchedulTotalHoursAndCourses.stories.tsx | 8 +- .../components/ScheduleListItem.stories.tsx | 9 +- src/stories/components/Settings.stories.tsx | 4 +- src/stories/components/Spinner.stories.ts | 10 +- src/stories/components/Text.stories.tsx | 3 +- .../components/calendar/Calendar.stories.tsx | 14 +- .../calendar/CalendarBottomBar.stories.tsx | 8 +- .../calendar/CalendarCourse.stories.tsx | 4 +- .../calendar/CalendarCourseCell.stories.tsx | 15 +- .../calendar/CalendarGrid.stories.tsx | 8 +- .../calendar/CalendarGridCell.stories.tsx | 3 +- .../calendar/CalendarHeader.stories.tsx | 5 +- .../calendar/CalendarSchedules.stories.tsx | 8 +- .../CourseCatalogInjectedPopup.stories.ts | 11 +- src/stories/injected/CoursePopup.stories.ts | 10 +- src/tsconfig.json | 12 +- src/views/components/CourseCatalogMain.tsx | 7 +- src/views/components/PopupMain.tsx | 187 ++++++++++++------ .../components/calendar/Calendar/Calendar.tsx | 3 +- .../CalendarBottomBar/CalendarBottomBar.tsx | 13 +- .../CalendarCourseMeeting.tsx | 7 +- .../CalendarCourseCell/CalendarCourseCell.tsx | 21 +- .../calendar/CalendarGrid/CalendarGrid.tsx | 11 +- .../CalendarGridCell/CalendarGridCell.tsx | 1 + .../CalendarHeader/CalenderHeader.tsx | 29 +-- .../CalendarSchedules/CalendarSchedules.tsx | 20 +- .../components/calendar/ImportantLinks.tsx | 6 +- src/views/components/common/Button/Button.tsx | 5 +- src/views/components/common/Card/Card.tsx | 4 + src/views/components/common/Chip/Chip.tsx | 1 + .../ConflictsWithWarning.tsx | 9 +- .../common/CourseStatus/CourseStatus.tsx | 3 +- .../components/common/Divider/Divider.tsx | 1 + .../components/common/Dropdown/Dropdown.tsx | 14 +- .../common/ExtensionRoot/ExtensionRoot.tsx | 7 +- src/views/components/common/Icon/Icon.tsx | 9 +- .../components/common/Icon/MaterialIcons.d.ts | 5 +- .../components/common/InfoCard/InfoCard.tsx | 51 ++--- src/views/components/common/Link/Link.tsx | 7 +- src/views/components/common/List/List.tsx | 3 +- src/views/components/common/Popup/Popup.tsx | 6 +- .../PopupCourseBlock/PopupCourseBlock.tsx | 8 +- .../ScheduleListItem/ScheduleListItem.tsx | 14 +- .../ScheduleTotalHoursAndCourses.tsx | 3 +- .../components/common/Spinner/Spinner.tsx | 1 + src/views/components/common/Text/Text.tsx | 4 +- .../components/injected/AutoLoad/AutoLoad.tsx | 7 +- .../CourseCatalogInjectedPopup.tsx | 15 +- .../Description.tsx | 10 +- .../GradeDistribution.tsx | 42 ++-- .../HeadingAndActions.tsx | 27 ++- .../CourseDescription/CourseDescription.tsx | 33 ++-- .../CourseButtons/CourseButtons.tsx | 5 +- .../CourseHeader/CourseHeader.tsx | 16 +- .../injected/CoursePopupOld/CoursePopup.tsx | 5 +- .../GradeDistribution/GradeDistribution.tsx | 34 ++-- .../RecruitmentBanner/RecruitmentBanner.tsx | 5 + src/views/components/injected/TableHead.tsx | 3 +- .../components/injected/TableRow/TableRow.tsx | 12 +- .../TableSubheading/TableSubheading.tsx | 3 +- src/views/hooks/useFlattenedCourseSchedule.ts | 9 +- src/views/hooks/useInfiniteScroll.ts | 20 +- src/views/hooks/useSchedules.ts | 6 +- src/views/hooks/useTabMessage.ts | 2 +- src/views/hooks/useVersion.ts | 4 + src/views/index.tsx | 2 +- src/views/lib/CourseCatalogScraper.ts | 45 +++-- src/views/lib/database/initializeDB.ts | 3 +- src/views/lib/database/queryDistribution.ts | 5 +- src/views/lib/getSiteSupport.ts | 24 ++- src/views/lib/loadNextCourseCatalogPage.ts | 21 +- src/views/lib/openNewTabFromContentScript.ts | 27 +-- src/views/lib/populateSearchInputs.ts | 1 - src/views/lib/react/index.tsx | 8 +- tsconfig.json | 41 +--- unocss.config.ts | 1 + vite.config.ts | 4 +- 134 files changed, 986 insertions(+), 623 deletions(-) create mode 100644 src/shared/util/tests/themeColors.test.ts diff --git a/.releaserc.json b/.releaserc.json index f73823d8..1b0fc571 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -34,4 +34,4 @@ } ] ] -} \ No newline at end of file +} diff --git a/package.json b/package.json index a8cb649f..2af6ccff 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "build": "tsc && vite build", "prettier": "prettier src --check", "prettier:fix": "prettier src --write", - "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "lint:fix": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0 --fix", + "lint": "eslint src --ext ts,tsx --report-unused-disable-directives", + "lint:fix": "eslint src --ext ts,tsx --report-unused-disable-directives --fix", "test": "vitest", "test:ui": "vitest --ui", "coverage": "vitest run --coverage", diff --git a/src/manifest.ts b/src/manifest.ts index 937d25f5..40a50f7f 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -1,4 +1,5 @@ import { defineManifest } from '@crxjs/vite-plugin'; + import packageJson from '../package.json'; // Convert from Semver (example: 0.1.0-beta6) diff --git a/src/pages/background/background.ts b/src/pages/background/background.ts index b4259998..de601f71 100644 --- a/src/pages/background/background.ts +++ b/src/pages/background/background.ts @@ -1,5 +1,6 @@ -import { BACKGROUND_MESSAGES } from '@shared/messages'; +import type { BACKGROUND_MESSAGES } from '@shared/messages'; import { MessageListener } from 'chrome-extension-toolkit'; + import onInstall from './events/onInstall'; import onServiceWorkerAlive from './events/onServiceWorkerAlive'; import onUpdate from './events/onUpdate'; diff --git a/src/pages/background/handler/browserActionHandler.ts b/src/pages/background/handler/browserActionHandler.ts index 41ea9a1a..9572cf1c 100644 --- a/src/pages/background/handler/browserActionHandler.ts +++ b/src/pages/background/handler/browserActionHandler.ts @@ -1,5 +1,5 @@ -import BrowserActionMessages from '@shared/messages/BrowserActionMessages'; -import { MessageHandler } from 'chrome-extension-toolkit'; +import type BrowserActionMessages from '@shared/messages/BrowserActionMessages'; +import type { MessageHandler } from 'chrome-extension-toolkit'; const browserActionHandler: MessageHandler = { disableBrowserAction({ sender, sendResponse }) { diff --git a/src/pages/background/handler/hotReloadingHandler.ts b/src/pages/background/handler/hotReloadingHandler.ts index 157ebd18..26b0b06b 100644 --- a/src/pages/background/handler/hotReloadingHandler.ts +++ b/src/pages/background/handler/hotReloadingHandler.ts @@ -1,6 +1,6 @@ -import HotReloadingMessages from '@shared/messages/HotReloadingMessages'; +import type HotReloadingMessages from '@shared/messages/HotReloadingMessages'; import { DevStore } from '@shared/storage/DevStore'; -import { MessageHandler } from 'chrome-extension-toolkit'; +import type { MessageHandler } from 'chrome-extension-toolkit'; const hotReloadingHandler: MessageHandler = { async reloadExtension({ sendResponse }) { diff --git a/src/pages/background/handler/tabManagementHandler.ts b/src/pages/background/handler/tabManagementHandler.ts index 29b250ca..078b555c 100644 --- a/src/pages/background/handler/tabManagementHandler.ts +++ b/src/pages/background/handler/tabManagementHandler.ts @@ -1,5 +1,6 @@ -import TabManagementMessages from '@shared/messages/TabManagementMessages'; -import { MessageHandler } from 'chrome-extension-toolkit'; +import type TabManagementMessages from '@shared/messages/TabManagementMessages'; +import type { MessageHandler } from 'chrome-extension-toolkit'; + import openNewTab from '../util/openNewTab'; const tabManagementHandler: MessageHandler = { diff --git a/src/pages/background/handler/userScheduleHandler.ts b/src/pages/background/handler/userScheduleHandler.ts index 3d6f3277..c7004594 100644 --- a/src/pages/background/handler/userScheduleHandler.ts +++ b/src/pages/background/handler/userScheduleHandler.ts @@ -1,6 +1,7 @@ -import { UserScheduleMessages } from '@shared/messages/UserScheduleMessages'; +import type { UserScheduleMessages } from '@shared/messages/UserScheduleMessages'; import { Course } from '@shared/types/Course'; -import { MessageHandler } from 'chrome-extension-toolkit'; +import type { MessageHandler } from 'chrome-extension-toolkit'; + import addCourse from '../lib/addCourse'; import clearCourses from '../lib/clearCourses'; import createSchedule from '../lib/createSchedule'; diff --git a/src/pages/background/lib/addCourse.ts b/src/pages/background/lib/addCourse.ts index 6c420348..a987828e 100644 --- a/src/pages/background/lib/addCourse.ts +++ b/src/pages/background/lib/addCourse.ts @@ -1,5 +1,5 @@ import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; -import { Course } from '@shared/types/Course'; +import type { Course } from '@shared/types/Course'; /** * diff --git a/src/pages/background/lib/clearCourses.ts b/src/pages/background/lib/clearCourses.ts index a013f544..296c4d80 100644 --- a/src/pages/background/lib/clearCourses.ts +++ b/src/pages/background/lib/clearCourses.ts @@ -1,5 +1,10 @@ import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; +/** + * Clears the courses for a given schedule. + * @param scheduleName - The name of the schedule. + * @throws Error if the schedule does not exist. + */ export default async function clearCourses(scheduleName: string): Promise { const schedules = await UserScheduleStore.get('schedules'); const schedule = schedules.find(schedule => schedule.name === scheduleName); diff --git a/src/pages/background/lib/createSchedule.ts b/src/pages/background/lib/createSchedule.ts index 97faf8b6..3e0a9486 100644 --- a/src/pages/background/lib/createSchedule.ts +++ b/src/pages/background/lib/createSchedule.ts @@ -14,7 +14,7 @@ export default async function createSchedule(scheduleName: string): Promise { const [schedules, activeIndex] = await Promise.all([ UserScheduleStore.get('schedules'), diff --git a/src/pages/background/lib/removeCourse.ts b/src/pages/background/lib/removeCourse.ts index 35a5576e..23670c8a 100644 --- a/src/pages/background/lib/removeCourse.ts +++ b/src/pages/background/lib/removeCourse.ts @@ -1,5 +1,5 @@ import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; -import { Course } from '@shared/types/Course'; +import type { Course } from '@shared/types/Course'; /** * diff --git a/src/pages/background/lib/renameSchedule.ts b/src/pages/background/lib/renameSchedule.ts index 57fb9fa6..4cf769c4 100644 --- a/src/pages/background/lib/renameSchedule.ts +++ b/src/pages/background/lib/renameSchedule.ts @@ -1,5 +1,11 @@ import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; +/** + * Renames a schedule with the specified name to a new name. + * @param scheduleName - The name of the schedule to be renamed. + * @param newName - The new name for the schedule. + * @returns A promise that resolves to a string if there is an error, or undefined if the schedule is renamed successfully. + */ export default async function renameSchedule(scheduleName: string, newName: string): Promise { const schedules = await UserScheduleStore.get('schedules'); const scheduleIndex = schedules.findIndex(schedule => schedule.name === scheduleName); diff --git a/src/pages/background/lib/switchSchedule.ts b/src/pages/background/lib/switchSchedule.ts index 193a3b3d..ddad8962 100644 --- a/src/pages/background/lib/switchSchedule.ts +++ b/src/pages/background/lib/switchSchedule.ts @@ -1,5 +1,11 @@ import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; +/** + * Switches the active schedule to the specified schedule name. + * Throws an error if the schedule does not exist. + * @param scheduleName - The name of the schedule to switch to. + * @returns A Promise that resolves when the active schedule is successfully switched. + */ export default async function switchSchedule(scheduleName: string): Promise { const schedules = await UserScheduleStore.get('schedules'); diff --git a/src/pages/calendar/CalendarMain.tsx b/src/pages/calendar/CalendarMain.tsx index a9abf555..225e120d 100644 --- a/src/pages/calendar/CalendarMain.tsx +++ b/src/pages/calendar/CalendarMain.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'; +import React from 'react'; import { Calendar } from 'src/views/components/calendar/Calendar/Calendar'; /** diff --git a/src/pages/calendar/index.html b/src/pages/calendar/index.html index 653fda5f..c2a46388 100644 --- a/src/pages/calendar/index.html +++ b/src/pages/calendar/index.html @@ -1,18 +1,16 @@ - + + + + + + Calendar + - - - - - Calendar - - - - -
- - - + + +
+ + diff --git a/src/pages/calendar/index.tsx b/src/pages/calendar/index.tsx index 53c29aed..d9c2f3b1 100644 --- a/src/pages/calendar/index.tsx +++ b/src/pages/calendar/index.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; + import CalendarMain from './CalendarMain'; createRoot(document.getElementById('root')).render(); diff --git a/src/pages/content/index.tsx b/src/pages/content/index.tsx index 4f5c3ea9..0e431c82 100644 --- a/src/pages/content/index.tsx +++ b/src/pages/content/index.tsx @@ -1,7 +1,7 @@ -import React from 'react'; -import { createRoot } from 'react-dom/client'; import CourseCatalogMain from '@views/components/CourseCatalogMain'; import getSiteSupport, { SiteSupport } from '@views/lib/getSiteSupport'; +import React from 'react'; +import { createRoot } from 'react-dom/client'; const support = getSiteSupport(window.location.href); diff --git a/src/pages/debug/App.tsx b/src/pages/debug/App.tsx index 5e7d58b7..6de7b184 100644 --- a/src/pages/debug/App.tsx +++ b/src/pages/debug/App.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'; +import React from 'react'; /** * diff --git a/src/pages/debug/index.html b/src/pages/debug/index.html index 1cd60648..ee831441 100644 --- a/src/pages/debug/index.html +++ b/src/pages/debug/index.html @@ -1,18 +1,16 @@ - + + + + + + Debug + - - - - - Debug - - - - -
- - - + + +
+ + diff --git a/src/pages/options/App.tsx b/src/pages/options/App.tsx index 5e7d58b7..6de7b184 100644 --- a/src/pages/options/App.tsx +++ b/src/pages/options/App.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'; +import React from 'react'; /** * diff --git a/src/pages/options/index.html b/src/pages/options/index.html index ff1b85be..b4b032c8 100644 --- a/src/pages/options/index.html +++ b/src/pages/options/index.html @@ -1,18 +1,16 @@ - + + + + + + Popup + - - - - - Popup - - - - -
- - - + + +
+ + diff --git a/src/pages/options/index.tsx b/src/pages/options/index.tsx index cc50fd07..7b6e0272 100644 --- a/src/pages/options/index.tsx +++ b/src/pages/options/index.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; + import App from './App'; createRoot(document.getElementById('root')).render(); diff --git a/src/pages/popup/index.html b/src/pages/popup/index.html index ff1b85be..b4b032c8 100644 --- a/src/pages/popup/index.html +++ b/src/pages/popup/index.html @@ -1,18 +1,16 @@ - + + + + + + Popup + - - - - - Popup - - - - -
- - - + + +
+ + diff --git a/src/pages/popup/index.tsx b/src/pages/popup/index.tsx index 96e53ce8..77f53cd7 100644 --- a/src/pages/popup/index.tsx +++ b/src/pages/popup/index.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; + import PopupMain from '../../views/components/PopupMain'; createRoot(document.getElementById('root')).render(); diff --git a/src/shared/messages/BrowserActionMessages.ts b/src/shared/messages/BrowserActionMessages.ts index faf1d475..c24c00e4 100644 --- a/src/shared/messages/BrowserActionMessages.ts +++ b/src/shared/messages/BrowserActionMessages.ts @@ -1,3 +1,4 @@ +/* eslint-disable jsdoc/require-jsdoc */ export default interface BrowserActionMessages { /** make it so that clicking the browser action will open the popup.html */ enableBrowserAction: () => void; diff --git a/src/shared/messages/HotReloadingMessages.ts b/src/shared/messages/HotReloadingMessages.ts index f52fbacc..346e5f1f 100644 --- a/src/shared/messages/HotReloadingMessages.ts +++ b/src/shared/messages/HotReloadingMessages.ts @@ -1,3 +1,4 @@ +/* eslint-disable jsdoc/require-jsdoc */ export default interface HotReloadingMessages { reloadExtension: () => void; } diff --git a/src/shared/messages/UserScheduleMessages.ts b/src/shared/messages/UserScheduleMessages.ts index 1332b369..1cec4782 100644 --- a/src/shared/messages/UserScheduleMessages.ts +++ b/src/shared/messages/UserScheduleMessages.ts @@ -1,5 +1,8 @@ -import { Course } from '../types/Course'; +import type { Course } from '@shared/types/Course'; +/** + * Represents a collection of user schedule messages. + */ export interface UserScheduleMessages { /** * Add a course to a schedule diff --git a/src/shared/messages/index.ts b/src/shared/messages/index.ts index a7f0227c..806d4a5e 100644 --- a/src/shared/messages/index.ts +++ b/src/shared/messages/index.ts @@ -1,8 +1,9 @@ import { createMessenger } from 'chrome-extension-toolkit'; -import BrowserActionMessages from './BrowserActionMessages'; -import TabManagementMessages from './TabManagementMessages'; -import TAB_MESSAGES from './TabMessages'; -import { UserScheduleMessages } from './UserScheduleMessages'; + +import type BrowserActionMessages from './BrowserActionMessages'; +import type TabManagementMessages from './TabManagementMessages'; +import type TAB_MESSAGES from './TabMessages'; +import type { UserScheduleMessages } from './UserScheduleMessages'; /** * This is a type with all the message definitions that can be sent TO the background script diff --git a/src/shared/storage/ExtensionStore.ts b/src/shared/storage/ExtensionStore.ts index 09e71b11..cd12660c 100644 --- a/src/shared/storage/ExtensionStore.ts +++ b/src/shared/storage/ExtensionStore.ts @@ -15,6 +15,4 @@ export const ExtensionStore = createLocalStore({ lastUpdate: Date.now(), }); - - debugStore({ ExtensionStore }); diff --git a/src/shared/types/Course.ts b/src/shared/types/Course.ts index 70a16ccb..c13b2733 100644 --- a/src/shared/types/Course.ts +++ b/src/shared/types/Course.ts @@ -1,6 +1,6 @@ -/* eslint-disable max-classes-per-file */ -import { Serialized } from 'chrome-extension-toolkit'; -import { CourseMeeting } from './CourseMeeting'; +import type { Serialized } from 'chrome-extension-toolkit'; + +import type { CourseMeeting } from './CourseMeeting'; import { CourseSchedule } from './CourseSchedule'; import Instructor from './Instructor'; @@ -12,12 +12,17 @@ export type InstructionMode = 'Online' | 'In Person' | 'Hybrid'; /** * The status of a course (e.g. open, closed, waitlisted, cancelled) */ -export enum Status { - OPEN = 'OPEN', - CLOSED = 'CLOSED', - WAITLISTED = 'WAITLISTED', - CANCELLED = 'CANCELLED', -} +export const Status = { + OPEN: 'OPEN', + CLOSED: 'CLOSED', + WAITLISTED: 'WAITLISTED', + CANCELLED: 'CANCELLED', +} as const; + +/** + * Represents the type of status for a course. + */ +export type StatusType = (typeof Status)[keyof typeof Status]; /** * Represents a semester, with the year and the season for when a course is offered @@ -49,7 +54,7 @@ export class Course { /** The number of credits that a course is worth */ creditHours: number; /** Is the course open, closed, waitlisted, or cancelled? */ - status: Status; + status: StatusType; /** all the people that are teaching this course, and some metadata about their names */ instructors: Instructor[]; /** Some courses at UT are reserved for certain groups of people or people within a certain major, which makes it difficult for people outside of that group to register for the course. */ diff --git a/src/shared/types/CourseMeeting.ts b/src/shared/types/CourseMeeting.ts index 5333a227..d915f3df 100644 --- a/src/shared/types/CourseMeeting.ts +++ b/src/shared/types/CourseMeeting.ts @@ -1,4 +1,4 @@ -import { Serialized } from 'chrome-extension-toolkit'; +import type { Serialized } from 'chrome-extension-toolkit'; /** * a map of the days of the week that a class is taught, and the corresponding abbreviation diff --git a/src/shared/types/CourseSchedule.ts b/src/shared/types/CourseSchedule.ts index 670df761..7898a780 100644 --- a/src/shared/types/CourseSchedule.ts +++ b/src/shared/types/CourseSchedule.ts @@ -1,5 +1,7 @@ -import { Serialized } from 'chrome-extension-toolkit'; -import { CourseMeeting, Day, DAY_MAP } from './CourseMeeting'; +import type { Serialized } from 'chrome-extension-toolkit'; + +import type { Day } from './CourseMeeting'; +import { CourseMeeting, 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 diff --git a/src/shared/types/Distribution.ts b/src/shared/types/Distribution.ts index 0325b121..9468e8ec 100644 --- a/src/shared/types/Distribution.ts +++ b/src/shared/types/Distribution.ts @@ -1,5 +1,5 @@ -/* eslint-disable max-classes-per-file */ import { PointOptionsObject } from 'highcharts'; + import { Semester } from './Course'; /** * Each of the possible letter grades that can be given in a course diff --git a/src/shared/types/Instructor.ts b/src/shared/types/Instructor.ts index 3c42a81a..1b2a1e53 100644 --- a/src/shared/types/Instructor.ts +++ b/src/shared/types/Instructor.ts @@ -1,4 +1,5 @@ -import { Serialized } from 'chrome-extension-toolkit'; +import type { Serialized } from 'chrome-extension-toolkit'; + import { capitalize } from '../util/string'; /** diff --git a/src/shared/types/UserSchedule.ts b/src/shared/types/UserSchedule.ts index 2806ebe2..f6bd1e85 100644 --- a/src/shared/types/UserSchedule.ts +++ b/src/shared/types/UserSchedule.ts @@ -1,4 +1,5 @@ -import { Serialized } from 'chrome-extension-toolkit'; +import type { Serialized } from 'chrome-extension-toolkit'; + import { Course } from './Course'; /** diff --git a/src/shared/util/colors.ts b/src/shared/util/colors.ts index 6a2af5d1..a18a9d7b 100644 --- a/src/shared/util/colors.ts +++ b/src/shared/util/colors.ts @@ -1,11 +1,19 @@ import { theme } from 'unocss/preset-mini'; +/** + * Represents the colors for a course. + */ export interface CourseColors { primaryColor: string; secondaryColor: string; } -// calculates luminance of a hex string +/** + * Calculates the luminance of a given hexadecimal color. + * + * @param hex - The hexadecimal color value. + * @returns The luminance value between 0 and 1. + */ export function getLuminance(hex: string): number { let r = parseInt(hex.substring(1, 3), 16); let g = parseInt(hex.substring(3, 5), 16); diff --git a/src/shared/util/icons.tsx b/src/shared/util/icons.tsx index 88f4ebfd..1a817f49 100644 --- a/src/shared/util/icons.tsx +++ b/src/shared/util/icons.tsx @@ -1,15 +1,18 @@ -import React, { SVGProps } from 'react'; +import type { StatusType } from '@shared/types/Course'; +import { Status } from '@shared/types/Course'; +import type { SVGProps } from 'react'; +import React from 'react'; + import ClosedIcon from '~icons/material-symbols/lock'; import WaitlistIcon from '~icons/material-symbols/timelapse'; import CancelledIcon from '~icons/material-symbols/warning'; -import { Status } from '../types/Course'; /** * Get Icon component based on status * @param props.status status * @returns React.ReactElement - the icon component */ -export function StatusIcon(props: SVGProps & { status: Status }): React.ReactElement { +export function StatusIcon(props: SVGProps & { status: StatusType }): React.ReactElement { const { status, ...rest } = props; switch (props.status) { diff --git a/src/shared/util/string.ts b/src/shared/util/string.ts index 043c0903..8b96986f 100644 --- a/src/shared/util/string.ts +++ b/src/shared/util/string.ts @@ -18,6 +18,8 @@ export function capitalize(input: string): string { } capitalized += ' '; } + capitalized = capitalized.trim(); // Remove extra space + return capitalized; } @@ -31,7 +33,7 @@ export function capitalizeFirstLetter(input: string): string { } /** - * Cuts the + * Cuts the input string to the specified length and adds an ellipsis if the string is longer than the specified length. * @param input The string to ellipsify. * @param length The length of the string to return. * @returns The ellipsified string. diff --git a/src/shared/util/tests/string.test.ts b/src/shared/util/tests/string.test.ts index 54cd5857..8a45da25 100644 --- a/src/shared/util/tests/string.test.ts +++ b/src/shared/util/tests/string.test.ts @@ -1,17 +1,17 @@ import { describe, expect, it } from 'vitest'; -import { capitalize } from '../string'; +import { capitalize, capitalizeFirstLetter, ellipsify } from '../string'; // TODO: Fix `string.ts` and `string.test.ts` to make the tests pass // `capitalize` is adding an extra space at the end of the word. describe('capitalize', () => { it('should capitalize the first letter of each word', () => { // Debug - const word = 'hello world'; - const capitalized = capitalize(word); - console.log(capitalize(word)); - console.log(capitalized.length); - console.log(capitalized.split('')); + // const word = 'hello world'; + // const capitalized = capitalize(word); + // console.log(capitalize(word)); + // console.log(capitalized.length); + // console.log(capitalized.split('')); // Test case 1: Single word expect(capitalize('hello')).toBe('Hello'); @@ -25,15 +25,40 @@ describe('capitalize', () => { // Test case 4: Words with hyphens and spaces expect(capitalize('hello-world test')).toBe('Hello-World Test'); }); +}); - it('should not change the capitalization of the remaining letters', () => { - // Test case 1: All lowercase - expect(capitalize('hello')).toBe('Hello'); +describe('capitalizeFirstLetter', () => { + it('should return a string with the first letter capitalized', () => { + // Test case 1: Single word + expect(capitalizeFirstLetter('hello')).toBe('Hello'); - // Test case 2: All uppercase - expect(capitalize('WORLD')).toBe('WORLD'); + // Test case 2: Word with all lowercase letters + expect(capitalizeFirstLetter('world')).toBe('World'); - // Test case 3: Mixed case - expect(capitalize('HeLLo WoRLd')).toBe('Hello World'); + // Test case 3: Word with all uppercase letters + expect(capitalizeFirstLetter('EXAMPLE')).toBe('Example'); + + // Test case 4: Word with mixed case letters + expect(capitalizeFirstLetter('tEsT')).toBe('Test'); + }); + + it('should handle empty string input', () => { + expect(capitalizeFirstLetter('')).toBe(''); + }); +}); + +describe('ellipsify', () => { + it('should add ellipsis if the input string exceeds the specified character limit', () => { + // Test case 1: Input string is shorter than the character limit + expect(ellipsify('Hello', 10)).toBe('Hello'); + + // Test case 2: Input string is equal to the character limit + expect(ellipsify('Hello World', 11)).toBe('Hello World'); + + // Test case 3: Input string is longer than the character limit + expect(ellipsify('Hello World', 5)).toBe('Hello...'); + + // Test case 4: Input string is empty + expect(ellipsify('', 5)).toBe(''); }); }); diff --git a/src/shared/util/tests/themeColors.test.ts b/src/shared/util/tests/themeColors.test.ts new file mode 100644 index 00000000..a5921dd9 --- /dev/null +++ b/src/shared/util/tests/themeColors.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest'; + +import { getThemeColorHexByName, getThemeColorRgbByName, hexToRgb } from '../themeColors'; + +describe('hexToRgb', () => { + it('should convert hex color to RGB', () => { + expect(hexToRgb('#BF5700')).toEqual([191, 87, 0]); + expect(hexToRgb('#333F48')).toEqual([51, 63, 72]); + expect(hexToRgb('#f8971f')).toEqual([248, 151, 31]); + expect(hexToRgb('#ffd600')).toEqual([255, 214, 0]); + expect(hexToRgb('#a6cd57')).toEqual([166, 205, 87]); + expect(hexToRgb('#579d42')).toEqual([87, 157, 66]); + expect(hexToRgb('#00a9b7')).toEqual([0, 169, 183]); + expect(hexToRgb('#005f86')).toEqual([0, 95, 134]); + expect(hexToRgb('#9cadb7')).toEqual([156, 173, 183]); + expect(hexToRgb('#d6d2c4')).toEqual([214, 210, 196]); + expect(hexToRgb('#95a5a6')).toEqual([149, 165, 166]); + expect(hexToRgb('#B91C1C')).toEqual([185, 28, 28]); + expect(hexToRgb('#af2e2d')).toEqual([175, 46, 45]); + expect(hexToRgb('#1a2024')).toEqual([26, 32, 36]); + expect(hexToRgb('#22c55e')).toEqual([34, 197, 94]); + expect(hexToRgb('#a3e635')).toEqual([163, 230, 53]); + expect(hexToRgb('#84CC16')).toEqual([132, 204, 22]); + expect(hexToRgb('#FDE047')).toEqual([253, 224, 71]); + expect(hexToRgb('#FACC15')).toEqual([250, 204, 21]); + expect(hexToRgb('#F59E0B')).toEqual([245, 158, 11]); + expect(hexToRgb('#FB923C')).toEqual([251, 146, 60]); + expect(hexToRgb('#F97316')).toEqual([249, 115, 22]); + expect(hexToRgb('#EA580C')).toEqual([234, 88, 12]); + expect(hexToRgb('#DC2626')).toEqual([220, 38, 38]); + expect(hexToRgb('#B91C1C')).toEqual([185, 28, 28]); + }); +}); + +describe('getThemeColorHexByName', () => { + it('should return the hex color value by name', () => { + expect(getThemeColorHexByName('ut-burntorange')).toEqual('#BF5700'); + expect(getThemeColorHexByName('ut-offwhite')).toEqual('#D6D2C4'); + expect(getThemeColorHexByName('ut-black')).toEqual('#333F48'); + // Add more test cases for other theme color names + }); +}); + +describe('getThemeColorRgbByName', () => { + it('should return the RGB color value by name', () => { + expect(getThemeColorRgbByName('ut-burntorange')).toEqual([191, 87, 0]); + expect(getThemeColorRgbByName('ut-offwhite')).toEqual([214, 210, 196]); + expect(getThemeColorRgbByName('ut-black')).toEqual([51, 63, 72]); + // Add more test cases for other theme color names + }); +}); diff --git a/src/shared/util/themeColors.ts b/src/shared/util/themeColors.ts index 6a9b6da5..c20775e5 100644 --- a/src/shared/util/themeColors.ts +++ b/src/shared/util/themeColors.ts @@ -2,24 +2,24 @@ export const colors = { ut: { burntorange: '#BF5700', black: '#333F48', - orange: '#f8971f', - yellow: '#ffd600', - lightgreen: '#a6cd57', - green: '#579d42', - teal: '#00a9b7', - blue: '#005f86', - gray: '#9cadb7', - offwhite: '#d6d2c4', - concrete: '#95a5a6', - red: '#B91C1C' // Not sure if this should be here, but it's used for remove course, and add course is ut-green + orange: '#F8971F', + yellow: '#FFD600', + lightgreen: '#A6CD57', + green: '#579D42', + teal: '#00A9B7', + blue: '#005F86', + gray: '#9CADB7', + offwhite: '#D6D2C4', + concrete: '#95A5A6', + red: '#B91C1C', // Not sure if this should be here, but it's used for remove course, and add course is ut-green }, theme: { - red: '#af2e2d', - black: '#1a2024', + red: '#AF2E2D', + black: '#1A2024', }, gradeDistribution: { - a: '#22c55e', - aminus: '#a3e635', + a: '#22C55E', + aminus: '#A3E635', bplus: '#84CC16', b: '#FDE047', bminus: '#FACC15', @@ -31,7 +31,7 @@ export const colors = { dminus: '#B91C1C', f: '#B91C1C', }, -} as const; +} as const satisfies Record>; type NestedKeys = { [K in keyof T]: T[K] extends Record ? `${string & K}-${string & keyof T[K]}` : never; @@ -42,6 +42,10 @@ type NestedKeys = { */ export type ThemeColor = NestedKeys; +/** + * Flattened colors object. + * @type {Record} + */ export const colorsFlattened = Object.entries(colors).reduce( (acc, [prefix, group]) => { for (const [name, hex] of Object.entries(group)) { @@ -52,9 +56,18 @@ export const colorsFlattened = Object.entries(colors).reduce( {} as Record ); -const hexToRgb = (hex: string) => +/** + * Converts a hexadecimal color code to an RGB color array. + * @param hex The hexadecimal color code to convert. + * @returns An array representing the RGB color values. + */ +export const hexToRgb = (hex: string) => hex.match(/[0-9a-f]{2}/gi).map(partialHex => parseInt(partialHex, 16)) as [number, number, number]; +/** + * Represents the flattened RGB values of the colors. + * @type {Record>} + */ const colorsFlattenedRgb = Object.fromEntries( Object.entries(colorsFlattened).map(([name, hex]) => [name, hexToRgb(hex)]) ) as Record>; diff --git a/src/shared/util/time.ts b/src/shared/util/time.ts index eb11cb0a..c461cd99 100644 --- a/src/shared/util/time.ts +++ b/src/shared/util/time.ts @@ -5,7 +5,9 @@ export const HOUR = 60 * MINUTE; export const DAY = 24 * HOUR; /** - * + * Pauses the execution for the specified number of milliseconds. + * @param milliseconds - The number of milliseconds to sleep. + * @returns A promise that resolves after the specified number of milliseconds. */ export const sleep = (milliseconds: number): Promise => new Promise(resolve => setTimeout(resolve, milliseconds)); diff --git a/src/stories/components/Button.stories.tsx b/src/stories/components/Button.stories.tsx index 1d497d8c..7326c734 100644 --- a/src/stories/components/Button.stories.tsx +++ b/src/stories/components/Button.stories.tsx @@ -1,7 +1,8 @@ +import { colorsFlattened } from '@shared/util/themeColors'; import type { Meta, StoryObj } from '@storybook/react'; +import { Button } from '@views/components/common/Button/Button'; import React from 'react'; -import { colorsFlattened } from 'src/shared/util/themeColors'; -import { Button } from 'src/views/components/common/Button/Button'; + import AddIcon from '~icons/material-symbols/add'; import CalendarMonthIcon from '~icons/material-symbols/calendar-month'; import DescriptionIcon from '~icons/material-symbols/description'; diff --git a/src/stories/components/Card.stories.tsx b/src/stories/components/Card.stories.tsx index 95c9876d..7fa1c9b9 100644 --- a/src/stories/components/Card.stories.tsx +++ b/src/stories/components/Card.stories.tsx @@ -1,20 +1,20 @@ -import Card from 'src/views/components/common/Card/Card'; import type { Meta, StoryObj } from '@storybook/react'; +import Card from '@views/components/common/Card/Card'; import React from 'react'; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export const meta = { - title: 'Components/Common/Card', - component: Card, - parameters: { - // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout - layout: 'centered', - }, - args: { - children:
Hello
, - }, - // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs - tags: ['autodocs'], + title: 'Components/Common/Card', + component: Card, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + layout: 'centered', + }, + args: { + children:
Hello
, + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + tags: ['autodocs'], } satisfies Meta; export default meta; diff --git a/src/stories/components/Chip.stories.tsx b/src/stories/components/Chip.stories.tsx index ed0f0b3f..830ff315 100644 --- a/src/stories/components/Chip.stories.tsx +++ b/src/stories/components/Chip.stories.tsx @@ -1,5 +1,5 @@ -import { Meta, StoryObj } from '@storybook/react'; -import { Chip } from 'src/views/components/common/Chip/Chip'; +import type { Meta, StoryObj } from '@storybook/react'; +import { Chip } from '@views/components/common/Chip/Chip'; const meta = { title: 'Components/Common/Chip', @@ -20,4 +20,4 @@ export const Default: Story = { args: { label: 'QR', }, -}; \ No newline at end of file +}; diff --git a/src/stories/components/ConflictsWithWarning.stories.tsx b/src/stories/components/ConflictsWithWarning.stories.tsx index d81706a7..b62c347c 100644 --- a/src/stories/components/ConflictsWithWarning.stories.tsx +++ b/src/stories/components/ConflictsWithWarning.stories.tsx @@ -1,8 +1,8 @@ -import { Meta, StoryObj } from '@storybook/react'; +import { Course, Status } from '@shared/types/Course'; +import { CourseMeeting } from '@shared/types/CourseMeeting'; +import Instructor from '@shared/types/Instructor'; +import type { Meta, StoryObj } from '@storybook/react'; import ConflictsWithWarning from '@views/components/common/ConflictsWithWarning/ConflictsWithWarning'; -import { Course, Status } from 'src/shared/types/Course'; -import { CourseMeeting } from 'src/shared/types/CourseMeeting'; -import Instructor from 'src/shared/types/Instructor'; export const ExampleCourse: Course = new Course({ courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB', diff --git a/src/stories/components/CourseStatus.stories.tsx b/src/stories/components/CourseStatus.stories.tsx index 0e35fd26..1646eb0b 100644 --- a/src/stories/components/CourseStatus.stories.tsx +++ b/src/stories/components/CourseStatus.stories.tsx @@ -1,8 +1,7 @@ -import React from 'react'; - import { Status } from '@shared/types/Course'; -import { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import CourseStatus from '@views/components/common/CourseStatus/CourseStatus'; +import React from 'react'; const meta = { title: 'Components/Common/CourseStatus', @@ -40,7 +39,7 @@ export const Default: Story = {}; export const Variants: Story = { render: args => ( -
+
diff --git a/src/stories/components/Divider.stories.tsx b/src/stories/components/Divider.stories.tsx index a2cf0caf..f24c16ef 100644 --- a/src/stories/components/Divider.stories.tsx +++ b/src/stories/components/Divider.stories.tsx @@ -1,7 +1,8 @@ -import React from 'react'; -import { Meta, StoryObj } from '@storybook/react'; -import Divider from '@views/components/common/Divider/Divider'; +import type { Meta, StoryObj } from '@storybook/react'; import { Button } from '@views/components/common/Button/Button'; +import Divider from '@views/components/common/Divider/Divider'; +import React from 'react'; + import AddIcon from '~icons/material-symbols/add'; import CalendarMonthIcon from '~icons/material-symbols/calendar-month'; import DescriptionIcon from '~icons/material-symbols/description'; diff --git a/src/stories/components/Dropdown.stories.tsx b/src/stories/components/Dropdown.stories.tsx index ba9704a4..5ed23eda 100644 --- a/src/stories/components/Dropdown.stories.tsx +++ b/src/stories/components/Dropdown.stories.tsx @@ -1,13 +1,13 @@ import { Course, Status } from '@shared/types/Course'; +import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting'; +import { CourseSchedule } from '@shared/types/CourseSchedule'; +import Instructor from '@shared/types/Instructor'; import { UserSchedule } from '@shared/types/UserSchedule'; import type { Meta, StoryObj } from '@storybook/react'; -import { Serialized } from 'chrome-extension-toolkit'; +import Dropdown from '@views/components/common/Dropdown/Dropdown'; +import ScheduleListItem from '@views/components/common/ScheduleListItem/ScheduleListItem'; +import type { Serialized } from 'chrome-extension-toolkit'; import React from 'react'; -import { CourseMeeting, DAY_MAP } from 'src/shared/types/CourseMeeting'; -import { CourseSchedule } from 'src/shared/types/CourseSchedule'; -import Instructor from 'src/shared/types/Instructor'; -import Dropdown from 'src/views/components/common/Dropdown/Dropdown'; -import ScheduleListItem from 'src/views/components/common/ScheduleListItem/ScheduleListItem'; const meta: Meta = { title: 'Components/Common/Dropdown', diff --git a/src/stories/components/ImportantLinks.stories.tsx b/src/stories/components/ImportantLinks.stories.tsx index 95619fa9..8ad7ea21 100644 --- a/src/stories/components/ImportantLinks.stories.tsx +++ b/src/stories/components/ImportantLinks.stories.tsx @@ -1,5 +1,5 @@ -import { Meta, StoryObj } from '@storybook/react'; -import ImportantLinks from 'src/views/components/calendar/ImportantLinks'; +import type { Meta, StoryObj } from '@storybook/react'; +import ImportantLinks from '@views/components/calendar/ImportantLinks'; const meta = { title: 'Components/Common/ImportantLinks', diff --git a/src/stories/components/InfoCard.stories.tsx b/src/stories/components/InfoCard.stories.tsx index 1b80b560..0fa9f8e4 100644 --- a/src/stories/components/InfoCard.stories.tsx +++ b/src/stories/components/InfoCard.stories.tsx @@ -1,5 +1,5 @@ -import { Meta, StoryObj } from '@storybook/react'; -import { InfoCard } from 'src/views/components/common/InfoCard/InfoCard'; +import type { Meta, StoryObj } from '@storybook/react'; +import { InfoCard } from '@views/components/common/InfoCard/InfoCard'; const meta = { title: 'Components/Common/InfoCard', @@ -22,4 +22,4 @@ export const Default: Story = { titleText: 'WAITLIST SIZE', bodyText: '14 Students', }, -}; \ No newline at end of file +}; diff --git a/src/stories/components/Link.stories.ts b/src/stories/components/Link.stories.ts index dedd38ff..8043f58c 100644 --- a/src/stories/components/Link.stories.ts +++ b/src/stories/components/Link.stories.ts @@ -1,21 +1,21 @@ -import Link from 'src/views/components/common/Link/Link'; import type { Meta, StoryObj } from '@storybook/react'; +import Link from '@views/components/common/Link/Link'; const meta = { - title: 'Components/Common/Link', - component: Link, - parameters: { - layout: 'centered', - }, - tags: ['autodocs'], - argTypes: { - color: { - control: 'color', + title: 'Components/Common/Link', + component: Link, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + color: { + control: 'color', + }, + }, + args: { + children: 'Link', }, - }, - args: { - children: 'Link', - }, } satisfies Meta; export default meta; diff --git a/src/stories/components/List.stories.tsx b/src/stories/components/List.stories.tsx index 99c1e9b3..acccb87d 100644 --- a/src/stories/components/List.stories.tsx +++ b/src/stories/components/List.stories.tsx @@ -1,15 +1,22 @@ import { Course, Status } from '@shared/types/Course'; import { CourseMeeting } from '@shared/types/CourseMeeting'; import Instructor from '@shared/types/Instructor'; -import { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import List from '@views/components/common/List/List'; import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock'; import React from 'react'; -import { test_colors } from './PopupCourseBlock.stories'; + +import { TestColors } from './PopupCourseBlock.stories'; const numberOfCourses = 5; -export const generateCourses = count => { +/** + * Generates an array of courses. + * + * @param count - The number of courses to generate. + * @returns An array of generated courses. + */ +export const GenerateCourses = count => { const courses = []; for (let i = 0; i < count; i++) { @@ -61,10 +68,10 @@ export const generateCourses = count => { return courses; }; -const exampleCourses = generateCourses(numberOfCourses); +const exampleCourses = GenerateCourses(numberOfCourses); const generateCourseBlocks = (exampleCourses, colors) => exampleCourses.map((course, i) => ); -export const exampleCourseBlocks = generateCourseBlocks(exampleCourses, test_colors); +export const ExampleCourseBlocks = generateCourseBlocks(exampleCourses, TestColors); const meta = { title: 'Components/Common/List', @@ -87,7 +94,7 @@ type Story = StoryObj; export const Default: Story = { args: { - draggableElements: exampleCourseBlocks, + draggableElements: ExampleCourseBlocks, itemHeight: 55, listHeight: 300, listWidth: 300, diff --git a/src/stories/components/PopupCourseBlock.stories.tsx b/src/stories/components/PopupCourseBlock.stories.tsx index 0e60f9fe..fa43f65d 100644 --- a/src/stories/components/PopupCourseBlock.stories.tsx +++ b/src/stories/components/PopupCourseBlock.stories.tsx @@ -1,13 +1,19 @@ +import { Course, Status } from '@shared/types/Course'; +import { CourseMeeting } from '@shared/types/CourseMeeting'; +import Instructor from '@shared/types/Instructor'; +import { getCourseColors } from '@shared/util/colors'; import type { Meta, StoryObj } from '@storybook/react'; import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock'; import React from 'react'; -import { Course, Status } from 'src/shared/types/Course'; -import { CourseMeeting } from 'src/shared/types/CourseMeeting'; -import Instructor from 'src/shared/types/Instructor'; -import { getCourseColors } from 'src/shared/util/colors'; import { theme } from 'unocss/preset-mini'; -export const exampleCourse: Course = new Course({ +/** + * Represents an example course. + * + * @remarks + * This is a sample course object that provides information about a specific course. + */ +export const ExampleCourse: Course = new Course({ courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB', creditHours: 3, department: 'C S', @@ -62,7 +68,7 @@ const meta = { // More on argTypes: https://storybook.js.org/docs/api/argtypes args: { colors: getCourseColors('emerald'), - course: exampleCourse, + course: ExampleCourse, }, argTypes: { colors: { @@ -87,15 +93,15 @@ export const Default: Story = { export const Variants: Story = { render: props => (
- - - - + + + +
), }; -export const test_colors = Object.keys(theme.colors) +export const TestColors = Object.keys(theme.colors) // check that the color is a colorway (is an object) .filter(color => typeof theme.colors[color] === 'object') .slice(0, 17) @@ -104,8 +110,8 @@ export const test_colors = Object.keys(theme.colors) export const AllColors: Story = { render: props => (
- {test_colors.map((color, i) => ( - + {TestColors.map((color, i) => ( + ))}
), diff --git a/src/stories/components/PopupMain.stories.tsx b/src/stories/components/PopupMain.stories.tsx index eddad476..f2e13471 100644 --- a/src/stories/components/PopupMain.stories.tsx +++ b/src/stories/components/PopupMain.stories.tsx @@ -1,4 +1,4 @@ -import { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import PopupMain from '@views/components/PopupMain'; const meta = { @@ -8,16 +8,12 @@ const meta = { layout: 'centered', }, tags: ['autodocs'], - argTypes: { - - }, + argTypes: {}, } satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = { - args: { - - }, -}; \ No newline at end of file + args: {}, +}; diff --git a/src/stories/components/SchedulTotalHoursAndCourses.stories.tsx b/src/stories/components/SchedulTotalHoursAndCourses.stories.tsx index 8164c8c6..9f6949e4 100644 --- a/src/stories/components/SchedulTotalHoursAndCourses.stories.tsx +++ b/src/stories/components/SchedulTotalHoursAndCourses.stories.tsx @@ -1,4 +1,4 @@ -import { Meta, StoryObj } from '@storybook/react'; +import type { Meta, StoryObj } from '@storybook/react'; import ScheduleTotalHoursAndCourses from '@views/components/common/ScheduleTotalHoursAndCourses/ScheduleTotalHoursAndCourses'; const meta = { @@ -11,7 +11,7 @@ const meta = { argTypes: { scheduleName: { control: 'text' }, totalHours: { control: 'number' }, - totalCourses: { control: 'number' } + totalCourses: { control: 'number' }, }, } satisfies Meta; export default meta; @@ -22,6 +22,6 @@ export const Default: Story = { args: { scheduleName: 'SCHEDULE', totalHours: 22, - totalCourses: 8 + totalCourses: 8, }, -}; \ No newline at end of file +}; diff --git a/src/stories/components/ScheduleListItem.stories.tsx b/src/stories/components/ScheduleListItem.stories.tsx index 80eb5871..d5926edc 100644 --- a/src/stories/components/ScheduleListItem.stories.tsx +++ b/src/stories/components/ScheduleListItem.stories.tsx @@ -1,5 +1,6 @@ +/* eslint-disable jsdoc/require-jsdoc */ +import ScheduleListItem from '@views/components/common/ScheduleListItem/ScheduleListItem'; import React from 'react'; -import ScheduleListItem from 'src/views/components/common/ScheduleListItem/ScheduleListItem'; export default { title: 'Components/Common/ScheduleListItem', @@ -14,21 +15,21 @@ export default { }, }; -export const Default = (args) => ; +export const Default = args => ; Default.args = { name: 'My Schedule', active: true, }; -export const Active = (args) => ; +export const Active = args => ; Active.args = { name: 'My Schedule', active: true, }; -export const Inactive = (args) => ; +export const Inactive = args => ; Inactive.args = { name: 'My Schedule', diff --git a/src/stories/components/Settings.stories.tsx b/src/stories/components/Settings.stories.tsx index 68ecc9c0..64ae5f82 100644 --- a/src/stories/components/Settings.stories.tsx +++ b/src/stories/components/Settings.stories.tsx @@ -1,5 +1,5 @@ -import { Meta, StoryObj } from '@storybook/react'; -import Settings from 'src/views/components/Settings'; +import type { Meta, StoryObj } from '@storybook/react'; +import Settings from '@views/components/Settings'; const meta = { title: 'Components/Common/Settings', diff --git a/src/stories/components/Spinner.stories.ts b/src/stories/components/Spinner.stories.ts index 8910b6b2..2ce320ec 100644 --- a/src/stories/components/Spinner.stories.ts +++ b/src/stories/components/Spinner.stories.ts @@ -1,11 +1,11 @@ -import Spinner from 'src/views/components/common/Spinner/Spinner'; import type { Meta, StoryObj } from '@storybook/react'; +import Spinner from '@views/components/common/Spinner/Spinner'; const meta = { - title: 'Components/Common/Spinner', - component: Spinner, - tags: ['autodocs'], - argTypes: {}, + title: 'Components/Common/Spinner', + component: Spinner, + tags: ['autodocs'], + argTypes: {}, } satisfies Meta; export default meta; diff --git a/src/stories/components/Text.stories.tsx b/src/stories/components/Text.stories.tsx index ecedaf92..1dc0de1d 100644 --- a/src/stories/components/Text.stories.tsx +++ b/src/stories/components/Text.stories.tsx @@ -1,7 +1,6 @@ -import { Button } from 'src/views/components/common/Button/Button'; import type { Meta, StoryObj } from '@storybook/react'; +import Text from '@views/components/common/Text/Text'; import React from 'react'; -import Text from '../../views/components/common/Text/Text'; // More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export const meta = { diff --git a/src/stories/components/calendar/Calendar.stories.tsx b/src/stories/components/calendar/Calendar.stories.tsx index beda65e2..6945e8be 100644 --- a/src/stories/components/calendar/Calendar.stories.tsx +++ b/src/stories/components/calendar/Calendar.stories.tsx @@ -1,5 +1,5 @@ -import { Meta, StoryObj } from '@storybook/react'; -import { Calendar } from 'src/views/components/calendar/Calendar/Calendar'; +import type { Meta, StoryObj } from '@storybook/react'; +import { Calendar } from '@views/components/calendar/Calendar/Calendar'; const meta = { title: 'Components/Calendar/Calendar', @@ -8,16 +8,12 @@ const meta = { layout: 'centered', }, tags: ['autodocs'], - argTypes: { - - }, + argTypes: {}, } satisfies Meta; export default meta; type Story = StoryObj; export const Default: Story = { - args: { - - }, -}; \ No newline at end of file + args: {}, +}; diff --git a/src/stories/components/calendar/CalendarBottomBar.stories.tsx b/src/stories/components/calendar/CalendarBottomBar.stories.tsx index e75f941d..10b90746 100644 --- a/src/stories/components/calendar/CalendarBottomBar.stories.tsx +++ b/src/stories/components/calendar/CalendarBottomBar.stories.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import { Meta, StoryObj } from '@storybook/react'; import { Course, Status } from '@shared/types/Course'; import Instructor from '@shared/types/Instructor'; -import { CalendarBottomBar } from 'src/views/components/calendar/CalendarBottomBar/CalendarBottomBar'; -import { getCourseColors } from '../../../shared/util/colors'; +import { getCourseColors } from '@shared/util/colors'; +import type { Meta, StoryObj } from '@storybook/react'; +import { CalendarBottomBar } from '@views/components/calendar/CalendarBottomBar/CalendarBottomBar'; +import React from 'react'; const exampleGovCourse: Course = new Course({ courseName: 'Nope', diff --git a/src/stories/components/calendar/CalendarCourse.stories.tsx b/src/stories/components/calendar/CalendarCourse.stories.tsx index 43066303..b5688ef9 100644 --- a/src/stories/components/calendar/CalendarCourse.stories.tsx +++ b/src/stories/components/calendar/CalendarCourse.stories.tsx @@ -1,9 +1,9 @@ -import { Meta, StoryObj } from '@storybook/react'; import { Course, Status } from '@shared/types/Course'; import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting'; import { CourseSchedule } from '@shared/types/CourseSchedule'; import Instructor from '@shared/types/Instructor'; -import CalendarCourse from 'src/views/components/calendar/CalendarCourseBlock/CalendarCourseMeeting'; +import type { Meta, StoryObj } from '@storybook/react'; +import CalendarCourse from '@views/components/calendar/CalendarCourseBlock/CalendarCourseMeeting'; const meta = { title: 'Components/Calendar/CalendarCourseMeeting', diff --git a/src/stories/components/calendar/CalendarCourseCell.stories.tsx b/src/stories/components/calendar/CalendarCourseCell.stories.tsx index 8df4d9fd..d32d1bf1 100644 --- a/src/stories/components/calendar/CalendarCourseCell.stories.tsx +++ b/src/stories/components/calendar/CalendarCourseCell.stories.tsx @@ -1,9 +1,10 @@ import { Course, Status } from '@shared/types/Course'; import { getCourseColors } from '@shared/util/colors'; -import { Meta, StoryObj } from '@storybook/react'; -import CalendarCourseCell from 'src/views/components/calendar/CalendarCourseCell/CalendarCourseCell'; +import type { Meta, StoryObj } from '@storybook/react'; +import CalendarCourseCell from '@views/components/calendar/CalendarCourseCell/CalendarCourseCell'; import React from 'react'; -import { exampleCourse } from '../PopupCourseBlock.stories'; + +import { ExampleCourse } from '../PopupCourseBlock.stories'; const meta = { title: 'Components/Calendar/CalendarCourseCell', @@ -25,10 +26,10 @@ const meta = {
), args: { - courseDeptAndInstr: exampleCourse.department, - className: exampleCourse.number, - status: exampleCourse.status, - timeAndLocation: exampleCourse.schedule.meetings[0].getTimeString({ separator: '-' }), + courseDeptAndInstr: ExampleCourse.department, + className: ExampleCourse.number, + status: ExampleCourse.status, + timeAndLocation: ExampleCourse.schedule.meetings[0].getTimeString({ separator: '-' }), colors: getCourseColors('emerald', 500), }, diff --git a/src/stories/components/calendar/CalendarGrid.stories.tsx b/src/stories/components/calendar/CalendarGrid.stories.tsx index 638d816b..4c3d0d81 100644 --- a/src/stories/components/calendar/CalendarGrid.stories.tsx +++ b/src/stories/components/calendar/CalendarGrid.stories.tsx @@ -1,8 +1,8 @@ -import { Meta, StoryObj } from '@storybook/react'; -import CalendarGrid from 'src/views/components/calendar/CalendarGrid/CalendarGrid'; -import { getCourseColors } from '@shared/util/colors'; -import { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule'; import { Status } from '@shared/types/Course'; +import { getCourseColors } from '@shared/util/colors'; +import type { Meta, StoryObj } from '@storybook/react'; +import CalendarGrid from '@views/components/calendar/CalendarGrid/CalendarGrid'; +import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule'; const meta = { title: 'Components/Calendar/CalendarGrid', diff --git a/src/stories/components/calendar/CalendarGridCell.stories.tsx b/src/stories/components/calendar/CalendarGridCell.stories.tsx index 067e134e..66ae4824 100644 --- a/src/stories/components/calendar/CalendarGridCell.stories.tsx +++ b/src/stories/components/calendar/CalendarGridCell.stories.tsx @@ -1,7 +1,6 @@ // Calendar.stories.tsx -import React from 'react'; -import CalendarCell from 'src/views/components/calendar/CalendarGridCell/CalendarGridCell'; import type { Meta, StoryObj } from '@storybook/react'; +import CalendarCell from '@views/components/calendar/CalendarGridCell/CalendarGridCell'; const meta = { title: 'Components/Calendar/CalendarGridCell', diff --git a/src/stories/components/calendar/CalendarHeader.stories.tsx b/src/stories/components/calendar/CalendarHeader.stories.tsx index 1a2a7131..197c3a02 100644 --- a/src/stories/components/calendar/CalendarHeader.stories.tsx +++ b/src/stories/components/calendar/CalendarHeader.stories.tsx @@ -1,6 +1,5 @@ -import React from 'react'; -import { Meta, StoryObj } from '@storybook/react'; -import CalendarHeader from 'src/views/components/calendar/CalendarHeader/CalenderHeader'; +import type { Meta, StoryObj } from '@storybook/react'; +import CalendarHeader from '@views/components/calendar/CalendarHeader/CalenderHeader'; const meta = { title: 'Components/Calendar/CalendarHeader', diff --git a/src/stories/components/calendar/CalendarSchedules.stories.tsx b/src/stories/components/calendar/CalendarSchedules.stories.tsx index 86b01826..576e7c9b 100644 --- a/src/stories/components/calendar/CalendarSchedules.stories.tsx +++ b/src/stories/components/calendar/CalendarSchedules.stories.tsx @@ -1,11 +1,11 @@ import { Course, Status } from '@shared/types/Course'; +import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting'; +import { CourseSchedule } from '@shared/types/CourseSchedule'; +import Instructor from '@shared/types/Instructor'; import { UserSchedule } from '@shared/types/UserSchedule'; import type { Meta, StoryObj } from '@storybook/react'; +import { CalendarSchedules } from '@views/components/calendar/CalendarSchedules/CalendarSchedules'; import React from 'react'; -import { CourseMeeting, DAY_MAP } from 'src/shared/types/CourseMeeting'; -import { CourseSchedule } from 'src/shared/types/CourseSchedule'; -import Instructor from 'src/shared/types/Instructor'; -import { CalendarSchedules } from 'src/views/components/calendar/CalendarSchedules/CalendarSchedules'; const meta = { title: 'Components/Calendar/CalendarSchedules', diff --git a/src/stories/injected/CourseCatalogInjectedPopup.stories.ts b/src/stories/injected/CourseCatalogInjectedPopup.stories.ts index 91faa2d7..9307ecaa 100644 --- a/src/stories/injected/CourseCatalogInjectedPopup.stories.ts +++ b/src/stories/injected/CourseCatalogInjectedPopup.stories.ts @@ -1,10 +1,9 @@ +import { Course, Status } from '@shared/types/Course'; +import { CourseMeeting, DAY_MAP } from '@shared/types/CourseMeeting'; +import { CourseSchedule } from '@shared/types/CourseSchedule'; +import Instructor from '@shared/types/Instructor'; import type { Meta, StoryObj } from '@storybook/react'; -import { Course, Status } from 'src/shared/types/Course'; -import { CourseMeeting, DAY_MAP } from 'src/shared/types/CourseMeeting'; -import { CourseSchedule } from 'src/shared/types/CourseSchedule'; -import Instructor from 'src/shared/types/Instructor'; - -import CourseCatalogInjectedPopup from 'src/views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup'; +import CourseCatalogInjectedPopup from '@views/components/injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup'; const exampleCourse: Course = new Course({ uniqueId: 50805, diff --git a/src/stories/injected/CoursePopup.stories.ts b/src/stories/injected/CoursePopup.stories.ts index 83377319..58fc7fd0 100644 --- a/src/stories/injected/CoursePopup.stories.ts +++ b/src/stories/injected/CoursePopup.stories.ts @@ -1,9 +1,9 @@ -import { Course, Status } from 'src/shared/types/Course'; -import { CourseMeeting } from 'src/shared/types/CourseMeeting'; -import { UserSchedule } from 'src/shared/types/UserSchedule'; -import CoursePopup from 'src/views/components/injected/CoursePopupOld/CoursePopup'; +import { Course, Status } from '@shared/types/Course'; +import { CourseMeeting } from '@shared/types/CourseMeeting'; +import Instructor from '@shared/types/Instructor'; +import { UserSchedule } from '@shared/types/UserSchedule'; import type { Meta, StoryObj } from '@storybook/react'; -import Instructor from 'src/shared/types/Instructor'; +import CoursePopup from '@views/components/injected/CoursePopupOld/CoursePopup'; const exampleCourse: Course = new Course({ courseName: 'ELEMS OF COMPTRS/PROGRAMMNG-WB', diff --git a/src/tsconfig.json b/src/tsconfig.json index 2cdcaead..21fd3d37 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -2,13 +2,7 @@ "extends": "../tsconfig.json", "compilerOptions": { "composite": true, - "lib": [ - "DOM", - "es2021" - ], - "types": [ - "chrome", - "node", - ], - }, + "lib": ["DOM", "es2021"], + "types": ["chrome", "node"] + } } diff --git a/src/views/components/CourseCatalogMain.tsx b/src/views/components/CourseCatalogMain.tsx index 065d39bb..6fd944e6 100644 --- a/src/views/components/CourseCatalogMain.tsx +++ b/src/views/components/CourseCatalogMain.tsx @@ -1,19 +1,20 @@ -import { Course, ScrapedRow } from '@shared/types/Course'; +import type { Course, ScrapedRow } from '@shared/types/Course'; import React, { useEffect, useState } from 'react'; + import { useKeyPress } from '../hooks/useKeyPress'; import useSchedules from '../hooks/useSchedules'; import { CourseCatalogScraper } from '../lib/CourseCatalogScraper'; import getCourseTableRows from '../lib/getCourseTableRows'; -import { SiteSupport } from '../lib/getSiteSupport'; +import type { SiteSupport } from '../lib/getSiteSupport'; import { populateSearchInputs } from '../lib/populateSearchInputs'; import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot'; import AutoLoad from './injected/AutoLoad/AutoLoad'; +import CourseCatalogInjectedPopup from './injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup'; import CoursePopup from './injected/CoursePopupOld/CoursePopup'; import RecruitmentBanner from './injected/RecruitmentBanner/RecruitmentBanner'; import TableHead from './injected/TableHead'; import TableRow from './injected/TableRow/TableRow'; import TableSubheading from './injected/TableSubheading/TableSubheading'; -import CourseCatalogInjectedPopup from './injected/CourseCatalogInjectedPopup/CourseCatalogInjectedPopup'; interface Props { support: SiteSupport.COURSE_CATALOG_DETAILS | SiteSupport.COURSE_CATALOG_LIST; diff --git a/src/views/components/PopupMain.tsx b/src/views/components/PopupMain.tsx index f441ca85..808aa0f7 100644 --- a/src/views/components/PopupMain.tsx +++ b/src/views/components/PopupMain.tsx @@ -1,97 +1,158 @@ +import logoImage from '@assets/logo.png'; // Adjust the path as necessary +import { Status } from '@shared/types/Course'; +import { StatusIcon } from '@shared/util/icons'; +import Divider from '@views/components/common/Divider/Divider'; +import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'; +import List from '@views/components/common/List/List'; // Ensure this path is correctly pointing to your List component +import PopupCourseBlock from '@views/components/common/PopupCourseBlock/PopupCourseBlock'; +import Text from '@views/components/common/Text/Text'; +import { handleOpenCalendar } from '@views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions'; +import useSchedules from '@views/hooks/useSchedules'; +import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript'; import React from 'react'; import { FaCalendarAlt, FaCog, FaRedo } from 'react-icons/fa'; // Added FaRedo for the refresh icon -import { StatusIcon } from '@shared/util/icons'; -import { Status } from 'src/shared/types/Course'; -import { test_colors } from 'src/stories/components/PopupCourseBlock.stories'; -import ExtensionRoot from './common/ExtensionRoot/ExtensionRoot'; -import PopupCourseBlock from './common/PopupCourseBlock/PopupCourseBlock'; -import Text from './common/Text/Text'; -import Divider from './common/Divider/Divider'; -import logoImage from '../../assets/logo.png'; // Adjust the path as necessary -import List from './common/List/List'; // Ensure this path is correctly pointing to your List component -import useSchedules from '../hooks/useSchedules'; -import { handleOpenCalendar } from './injected/CourseCatalogInjectedPopup/HeadingAndActions'; -import { openTabFromContentScript } from '../lib/openNewTabFromContentScript'; - +import { TestColors } from 'src/stories/components/PopupCourseBlock.stories'; +/** + * Renders the main popup component. + * This component displays the main schedule, courses, and options buttons. + */ export default function PopupMain() { - const [activeSchedule] = useSchedules(); + const [activeSchedule] = useSchedules(); - - const draggableElements = activeSchedule?.courses.map((course, i) => ( - + const draggableElements = activeSchedule?.courses.map((course, i) => ( + )); - const handleOpenOptions = async () => { // Not sure if it's bad practice to export this + const handleOpenOptions = async () => { + // Not sure if it's bad practice to export this const url = chrome.runtime.getURL('/src/pages/options/index.html'); await openTabFromContentScript(url); }; return ( -
-
-
- Logo +
+
+
+ Logo
- UT Registration - Plus + + UT Registration + + + Plus +
-
- -
- -
- MAIN SCHEDULE: + +
+ + MAIN SCHEDULE: +
- 22 HOURS - 8 Courses + + 22 HOURS + + + 8 Courses +
{/* Integrate the List component here */} - {activeSchedule ? : null} -
-
-
- + {activeSchedule ? ( + + ) : null} +
+
+
+
- WAITLISTED + + WAITLISTED +
-
-
- +
+
+
- CLOSED + + CLOSED +
-
-
- +
+
+
- CANCELLED + + CANCELLED +
-
-
- DATA UPDATED ON: 12:00 AM 02/01/2024 - +
+
+ + DATA UPDATED ON: 12:00 AM 02/01/2024 + +
diff --git a/src/views/components/calendar/Calendar/Calendar.tsx b/src/views/components/calendar/Calendar/Calendar.tsx index bc641188..99061967 100644 --- a/src/views/components/calendar/Calendar/Calendar.tsx +++ b/src/views/components/calendar/Calendar/Calendar.tsx @@ -1,9 +1,10 @@ import React from 'react'; import CalendarHeader from 'src/views/components/calendar/CalendarHeader/CalenderHeader'; + import { CalendarBottomBar } from '../CalendarBottomBar/CalendarBottomBar'; +import CalendarGrid from '../CalendarGrid/CalendarGrid'; import { CalendarSchedules } from '../CalendarSchedules/CalendarSchedules'; import ImportantLinks from '../ImportantLinks'; -import CalendarGrid from '../CalendarGrid/CalendarGrid'; export const flags = ['WR', 'QR', 'GC', 'CD', 'E', 'II']; diff --git a/src/views/components/calendar/CalendarBottomBar/CalendarBottomBar.tsx b/src/views/components/calendar/CalendarBottomBar/CalendarBottomBar.tsx index d80aa3d7..4a71968a 100644 --- a/src/views/components/calendar/CalendarBottomBar/CalendarBottomBar.tsx +++ b/src/views/components/calendar/CalendarBottomBar/CalendarBottomBar.tsx @@ -1,10 +1,13 @@ -import React from 'react'; import clsx from 'clsx'; -import Text from '../../common/Text/Text'; -import CalendarCourseBlock, { CalendarCourseCellProps } from '../CalendarCourseCell/CalendarCourseCell'; -import { Button } from '../../common/Button/Button'; -import ImageIcon from '~icons/material-symbols/image'; +import React from 'react'; + import CalendarMonthIcon from '~icons/material-symbols/calendar-month'; +import ImageIcon from '~icons/material-symbols/image'; + +import { Button } from '../../common/Button/Button'; +import Text from '../../common/Text/Text'; +import type { CalendarCourseCellProps } from '../CalendarCourseCell/CalendarCourseCell'; +import CalendarCourseBlock from '../CalendarCourseCell/CalendarCourseCell'; type CalendarBottomBarProps = { courses?: CalendarCourseCellProps[]; diff --git a/src/views/components/calendar/CalendarCourseBlock/CalendarCourseMeeting.tsx b/src/views/components/calendar/CalendarCourseBlock/CalendarCourseMeeting.tsx index dc651ca9..52128e8c 100644 --- a/src/views/components/calendar/CalendarCourseBlock/CalendarCourseMeeting.tsx +++ b/src/views/components/calendar/CalendarCourseBlock/CalendarCourseMeeting.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Course } from 'src/shared/types/Course'; -import { CourseMeeting } from 'src/shared/types/CourseMeeting'; +import type { Course } from 'src/shared/types/Course'; +import type { CourseMeeting } from 'src/shared/types/CourseMeeting'; + import styles from './CalendarCourseMeeting.module.scss'; /** @@ -26,6 +27,8 @@ export interface CalendarCourseMeetingProps { const CalendarCourseMeeting: React.FC = ({ course, meetingIdx, + color, + rightIcon, }: CalendarCourseMeetingProps) => { let meeting: CourseMeeting | null = meetingIdx !== undefined ? course.schedule.meetings[meetingIdx] : null; return ( diff --git a/src/views/components/calendar/CalendarCourseCell/CalendarCourseCell.tsx b/src/views/components/calendar/CalendarCourseCell/CalendarCourseCell.tsx index 0e6a03b3..840aea30 100644 --- a/src/views/components/calendar/CalendarCourseCell/CalendarCourseCell.tsx +++ b/src/views/components/calendar/CalendarCourseCell/CalendarCourseCell.tsx @@ -1,12 +1,17 @@ import { Status } from '@shared/types/Course'; +import Text from '@views/components/common/Text/Text'; import clsx from 'clsx'; import React from 'react'; -import { CourseColors, pickFontColor } from 'src/shared/util/colors'; +import type { CourseColors } from 'src/shared/util/colors'; +import { pickFontColor } from 'src/shared/util/colors'; + import ClosedIcon from '~icons/material-symbols/lock'; import WaitlistIcon from '~icons/material-symbols/timelapse'; import CancelledIcon from '~icons/material-symbols/warning'; -import Text from '../../common/Text/Text'; +/** + * Props for the CalendarCourseCell component. + */ export interface CalendarCourseCellProps { courseDeptAndInstr: string; timeAndLocation?: string; @@ -15,6 +20,18 @@ export interface CalendarCourseCellProps { className?: string; } +/** + * Renders a cell for a calendar course. + * + * @component + * @param {CalendarCourseCellProps} props - The component props. + * @param {string} props.courseDeptAndInstr - The course department and instructor. + * @param {string} props.timeAndLocation - The time and location of the course. + * @param {Status} props.status - The status of the course. + * @param {Colors} props.colors - The colors for styling the cell. + * @param {string} props.className - Additional CSS class name for the cell. + * @returns {JSX.Element} The rendered component. + */ const CalendarCourseCell: React.FC = ({ courseDeptAndInstr, timeAndLocation, diff --git a/src/views/components/calendar/CalendarGrid/CalendarGrid.tsx b/src/views/components/calendar/CalendarGrid/CalendarGrid.tsx index 3635bf9c..2269e7ac 100644 --- a/src/views/components/calendar/CalendarGrid/CalendarGrid.tsx +++ b/src/views/components/calendar/CalendarGrid/CalendarGrid.tsx @@ -1,12 +1,13 @@ -import React, { useState, useRef, useEffect } from 'react'; +import type { CalendarGridCourse } from '@views/hooks/useFlattenedCourseSchedule'; +import React, { useEffect, useRef, useState } from 'react'; // import html2canvas from 'html2canvas'; import { DAY_MAP } from 'src/shared/types/CourseMeeting'; -import { CalendarGridCourse } from 'src/views/hooks/useFlattenedCourseSchedule'; + +import CalendarCourseCell from '../CalendarCourseCell/CalendarCourseCell'; /* import calIcon from 'src/assets/icons/cal.svg'; import pngIcon from 'src/assets/icons/png.svg'; */ import CalendarCell from '../CalendarGridCell/CalendarGridCell'; -import CalendarCourseCell from '../CalendarCourseCell/CalendarCourseCell'; import styles from './CalendarGrid.module.scss'; /* const daysOfWeek = Object.keys(DAY_MAP).filter(key => !['S', 'SU'].includes(key)); @@ -107,7 +108,7 @@ function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren @@ -119,7 +120,7 @@ function CalendarGrid({ courseCells, saturdayClass }: React.PropsWithChildren ))} {grid.map((row, rowIndex) => row)} - {courseCells ? : null} + {courseCells ? : null} {/* courseCells.map((block: CalendarGridCourse) => (
(
@@ -18,9 +23,9 @@ const CalendarHeader = () => (
-
diff --git a/src/views/components/injected/CoursePopupOld/CourseDescription/CourseDescription.tsx b/src/views/components/injected/CoursePopupOld/CourseDescription/CourseDescription.tsx index d84440c4..b4cda305 100644 --- a/src/views/components/injected/CoursePopupOld/CourseDescription/CourseDescription.tsx +++ b/src/views/components/injected/CoursePopupOld/CourseDescription/CourseDescription.tsx @@ -1,29 +1,36 @@ -import { Course } from '@shared/types/Course'; -import clsx from 'clsx'; -import React, { useEffect, useState } from 'react'; +import type { Course } from '@shared/types/Course'; +import Card from '@views/components/common/Card/Card'; import Spinner from '@views/components/common/Spinner/Spinner'; import Text from '@views/components/common/Text/Text'; import { CourseCatalogScraper } from '@views/lib/CourseCatalogScraper'; import { SiteSupport } from '@views/lib/getSiteSupport'; -import Card from '../../../common/Card/Card'; +import clsx from 'clsx'; +import React, { useEffect, useState } from 'react'; + import styles from './CourseDescription.module.scss'; type Props = { course: Course; }; -enum LoadStatus { - LOADING = 'LOADING', - DONE = 'DONE', - ERROR = 'ERROR', -} +const LoadStatus = { + LOADING: 'LOADING', + DONE: 'DONE', + ERROR: 'ERROR', +} as const; + +type LoadStatusType = (typeof LoadStatus)[keyof typeof LoadStatus]; /** + * Renders the course description component. * + * @param {Props} props - The component props. + * @param {Course} props.course - The course object. + * @returns {JSX.Element} The rendered course description component. */ export default function CourseDescription({ course }: Props) { const [description, setDescription] = useState([]); - const [status, setStatus] = useState(LoadStatus.LOADING); + const [status, setStatus] = useState(LoadStatus.LOADING); useEffect(() => { fetchDescription(course) @@ -71,11 +78,7 @@ function DescriptionLine({ line }: LineProps) { [styles.restriction]: lowerCaseLine.includes('restrict'), }); - return ( - - {line} - - ); + return {line}; } async function fetchDescription(course: Course): Promise { diff --git a/src/views/components/injected/CoursePopupOld/CourseHeader/CourseButtons/CourseButtons.tsx b/src/views/components/injected/CoursePopupOld/CourseHeader/CourseButtons/CourseButtons.tsx index 5988ed68..22efadee 100644 --- a/src/views/components/injected/CoursePopupOld/CourseHeader/CourseButtons/CourseButtons.tsx +++ b/src/views/components/injected/CoursePopupOld/CourseHeader/CourseButtons/CourseButtons.tsx @@ -1,11 +1,12 @@ import { background } from '@shared/messages'; -import { Course } from '@shared/types/Course'; -import { UserSchedule } from '@shared/types/UserSchedule'; +import type { Course } from '@shared/types/Course'; +import type { UserSchedule } from '@shared/types/UserSchedule'; import { Button } from '@views/components/common/Button/Button'; import Card from '@views/components/common/Card/Card'; import Icon from '@views/components/common/Icon/Icon'; import Text from '@views/components/common/Text/Text'; import React from 'react'; + import styles from './CourseButtons.module.scss'; type Props = { diff --git a/src/views/components/injected/CoursePopupOld/CourseHeader/CourseHeader.tsx b/src/views/components/injected/CoursePopupOld/CourseHeader/CourseHeader.tsx index 327de7f2..eac9d5f4 100644 --- a/src/views/components/injected/CoursePopupOld/CourseHeader/CourseHeader.tsx +++ b/src/views/components/injected/CoursePopupOld/CourseHeader/CourseHeader.tsx @@ -1,15 +1,17 @@ -import { Course } from '@shared/types/Course'; -import { UserSchedule } from '@shared/types/UserSchedule'; -import React from 'react'; +import type { Course } from '@shared/types/Course'; +import type { UserSchedule } from '@shared/types/UserSchedule'; +import { Button } from '@views/components/common/Button/Button'; import Card from '@views/components/common/Card/Card'; import Icon from '@views/components/common/Icon/Icon'; import Link from '@views/components/common/Link/Link'; import Text from '@views/components/common/Text/Text'; -import { Button } from 'src/views/components/common/Button/Button'; +import React from 'react'; + +import CloseIcon from '~icons/material-symbols/close'; +import CopyIcon from '~icons/material-symbols/content-copy'; + import CourseButtons from './CourseButtons/CourseButtons'; import styles from './CourseHeader.module.scss'; -import CopyIcon from '~icons/material-symbols/content-copy'; -import CloseIcon from '~icons/material-symbols/close'; type Props = { course: Course; @@ -40,7 +42,7 @@ export default function CourseHeader({ course, activeSchedule, onClose }: Props) -
diff --git a/src/views/components/injected/CoursePopupOld/CoursePopup.tsx b/src/views/components/injected/CoursePopupOld/CoursePopup.tsx index 8cc99978..d67475a6 100644 --- a/src/views/components/injected/CoursePopupOld/CoursePopup.tsx +++ b/src/views/components/injected/CoursePopupOld/CoursePopup.tsx @@ -1,6 +1,7 @@ -import { Course } from '@shared/types/Course'; -import { UserSchedule } from '@shared/types/UserSchedule'; +import type { Course } from '@shared/types/Course'; +import type { UserSchedule } from '@shared/types/UserSchedule'; import React from 'react'; + import Popup from '../../common/Popup/Popup'; import CourseDescription from './CourseDescription/CourseDescription'; import CourseHeader from './CourseHeader/CourseHeader'; diff --git a/src/views/components/injected/CoursePopupOld/GradeDistribution/GradeDistribution.tsx b/src/views/components/injected/CoursePopupOld/GradeDistribution/GradeDistribution.tsx index ae4ed2b1..a5bef889 100644 --- a/src/views/components/injected/CoursePopupOld/GradeDistribution/GradeDistribution.tsx +++ b/src/views/components/injected/CoursePopupOld/GradeDistribution/GradeDistribution.tsx @@ -1,9 +1,5 @@ -/* eslint-disable no-nested-ternary */ -import { Course, Semester } from '@shared/types/Course'; -import { Distribution, LetterGrade } from '@shared/types/Distribution'; -import Highcharts from 'highcharts'; -import HighchartsReact from 'highcharts-react-official'; -import React, { useEffect, useRef, useState } from 'react'; +import type { Course, Semester } from '@shared/types/Course'; +import type { Distribution, LetterGrade } from '@shared/types/Distribution'; import Card from '@views/components/common/Card/Card'; import Icon from '@views/components/common/Icon/Icon'; import Spinner from '@views/components/common/Spinner/Spinner'; @@ -14,20 +10,26 @@ import { querySemesterDistribution, } from '@views/lib/database/queryDistribution'; import colors from '@views/styles/colors.module.scss'; +import Highcharts from 'highcharts'; +import HighchartsReact from 'highcharts-react-official'; +import React, { useEffect, useRef, useState } from 'react'; + import styles from './GradeDistribution.module.scss'; -enum DataStatus { - LOADING = 'LOADING', - FOUND = 'FOUND', - NOT_FOUND = 'NOT_FOUND', - ERROR = 'ERROR', -} +const DataStatus = { + LOADING: 'LOADING', + FOUND: 'FOUND', + NOT_FOUND: 'NOT_FOUND', + ERROR: 'ERROR', +} as const; + +type DataStatusType = (typeof DataStatus)[keyof typeof DataStatus]; interface Props { course: Course; } -const GRADE_COLORS: Record = { +const GRADE_COLORS = { A: colors.turtle_pond, 'A-': colors.turtle_pond, 'B+': colors.cactus, @@ -40,7 +42,7 @@ const GRADE_COLORS: Record = { D: colors.tangerine, 'D-': colors.tangerine, F: colors.speedway_brick, -}; +} as const satisfies Record; /** * A chart to fetch and display the grade distribution for a course @@ -51,7 +53,7 @@ export default function GradeDistribution({ course }: Props) { const [semesters, setSemesters] = useState([]); const [selectedSemester, setSelectedSemester] = useState(null); const [distribution, setDistribution] = useState(null); - const [status, setStatus] = useState(DataStatus.LOADING); + const [status, setStatus] = useState(DataStatus.LOADING); const [chartOptions, setChartOptions] = useState({ title: { @@ -206,7 +208,7 @@ export default function GradeDistribution({ course }: Props) { There was an error fetching the grade distribution data - + )} {status === DataStatus.NOT_FOUND && ( diff --git a/src/views/components/injected/RecruitmentBanner/RecruitmentBanner.tsx b/src/views/components/injected/RecruitmentBanner/RecruitmentBanner.tsx index 07d0c923..84715af8 100644 --- a/src/views/components/injected/RecruitmentBanner/RecruitmentBanner.tsx +++ b/src/views/components/injected/RecruitmentBanner/RecruitmentBanner.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; + import Link from '../../common/Link/Link'; import Text from '../../common/Text/Text'; import styles from './RecruitmentBanner.module.scss'; @@ -50,6 +51,10 @@ export default function RecruitmentBanner() { ); } +/** + * Determines if recruitment can be done from the current department. + * @returns {boolean} True if recruitment can be done from the current department, false otherwise. + */ export function canRecruitFrom(): boolean { const params = ['fos_fl', 'fos_cn']; let department = ''; diff --git a/src/views/components/injected/TableHead.tsx b/src/views/components/injected/TableHead.tsx index e187597f..728079b5 100644 --- a/src/views/components/injected/TableHead.tsx +++ b/src/views/components/injected/TableHead.tsx @@ -1,4 +1,5 @@ -import React, { PropsWithChildren, useEffect, useState } from 'react'; +import type { PropsWithChildren } from 'react'; +import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; /** diff --git a/src/views/components/injected/TableRow/TableRow.tsx b/src/views/components/injected/TableRow/TableRow.tsx index a6e82cb1..c29083a0 100644 --- a/src/views/components/injected/TableRow/TableRow.tsx +++ b/src/views/components/injected/TableRow/TableRow.tsx @@ -1,12 +1,14 @@ -import { Course, ScrapedRow } from '@shared/types/Course'; -import { UserSchedule } from '@shared/types/UserSchedule'; +import type { Course, ScrapedRow } from '@shared/types/Course'; +import type { UserSchedule } from '@shared/types/UserSchedule'; import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; -import { Button } from '../../common/Button/Button'; -import styles from './TableRow.module.scss'; -import ConflictsWithWarning from '../../common/ConflictsWithWarning/ConflictsWithWarning'; + import AddIcon from '~icons/material-symbols/add-circle'; +import { Button } from '../../common/Button/Button'; +import ConflictsWithWarning from '../../common/ConflictsWithWarning/ConflictsWithWarning'; +import styles from './TableRow.module.scss'; + interface Props { isSelected: boolean; row: ScrapedRow; diff --git a/src/views/components/injected/TableSubheading/TableSubheading.tsx b/src/views/components/injected/TableSubheading/TableSubheading.tsx index e7a11bc1..a6122ff8 100644 --- a/src/views/components/injected/TableSubheading/TableSubheading.tsx +++ b/src/views/components/injected/TableSubheading/TableSubheading.tsx @@ -1,5 +1,6 @@ -import { ScrapedRow } from '@shared/types/Course'; +import type { ScrapedRow } from '@shared/types/Course'; import { useEffect } from 'react'; + import styles from './TableSubheading.module.scss'; interface Props { diff --git a/src/views/hooks/useFlattenedCourseSchedule.ts b/src/views/hooks/useFlattenedCourseSchedule.ts index f449d7dc..040f3167 100644 --- a/src/views/hooks/useFlattenedCourseSchedule.ts +++ b/src/views/hooks/useFlattenedCourseSchedule.ts @@ -1,4 +1,4 @@ -import type { CalendarCourseCellProps } from 'src/views/components/calendar/CalendarCourseCell/CalendarCourseCell'; +import type { CalendarCourseCellProps } from '@views/components/calendar/CalendarCourseCell/CalendarCourseCell'; import useSchedules from './useSchedules'; @@ -27,7 +27,12 @@ export interface CalendarGridCourse { totalColumns?: number; } -export const convertMinutesToIndex = (minutes: number): number => Math.floor(minutes - 420 / 30); +/** + * Converts minutes to an index value. + * @param minutes The number of minutes. + * @returns The index value. + */ +export const convertMinutesToIndex = (minutes: number): number => Math.floor((minutes - 420) / 30); /** * Get the active schedule, and convert it to be render-able into a calendar. diff --git a/src/views/hooks/useInfiniteScroll.ts b/src/views/hooks/useInfiniteScroll.ts index d143dee3..7d14a493 100644 --- a/src/views/hooks/useInfiniteScroll.ts +++ b/src/views/hooks/useInfiniteScroll.ts @@ -7,24 +7,26 @@ import { useEffect } from 'react'; */ /** + * Custom hook for implementing infinite scrolling behavior. * + * @param callback - The function to be called when scrolling reaches the specified threshold. + * @param deps - Optional dependencies to be passed to the useEffect hook. */ export default function useInfiniteScroll( callback: () => Promise | void, deps?: React.DependencyList | undefined ) { - const isScrolling = () => { - const { innerHeight } = window; - const { scrollTop, offsetHeight } = document.documentElement; - if (innerHeight + scrollTop >= offsetHeight - 650) { - callback(); - } - }; - useEffect(() => { + const isScrolling = () => { + const { innerHeight } = window; + const { scrollTop, offsetHeight } = document.documentElement; + if (innerHeight + scrollTop >= offsetHeight - 650) { + callback(); + } + }; window.addEventListener('scroll', isScrolling, { passive: true, }); return () => window.removeEventListener('scroll', isScrolling); - }, deps); + }, [deps, callback]); } diff --git a/src/views/hooks/useSchedules.ts b/src/views/hooks/useSchedules.ts index 89f6fde0..32f35a46 100644 --- a/src/views/hooks/useSchedules.ts +++ b/src/views/hooks/useSchedules.ts @@ -2,6 +2,10 @@ import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import { UserSchedule } from '@shared/types/UserSchedule'; import { useEffect, useState } from 'react'; +/** + * Custom hook that manages user schedules. + * @returns A tuple containing the active schedule and an array of all schedules. + */ export default function useSchedules(): [active: UserSchedule | null, schedules: UserSchedule[]] { const [schedules, setSchedules] = useState([]); const [activeIndex, setActiveIndex] = useState(0); @@ -30,7 +34,7 @@ export default function useSchedules(): [active: UserSchedule | null, schedules: UserScheduleStore.removeListener(l1); UserScheduleStore.removeListener(l2); }; - }, []); + }, [activeIndex, schedules]); return [activeSchedule, schedules]; } diff --git a/src/views/hooks/useTabMessage.ts b/src/views/hooks/useTabMessage.ts index cca95893..3913f2d0 100644 --- a/src/views/hooks/useTabMessage.ts +++ b/src/views/hooks/useTabMessage.ts @@ -1,4 +1,4 @@ -import TAB_MESSAGES from '@shared/messages/TabMessages'; +import type TAB_MESSAGES from '@shared/messages/TabMessages'; import { createUseMessage } from 'chrome-extension-toolkit'; export const useTabMessage = createUseMessage(); diff --git a/src/views/hooks/useVersion.ts b/src/views/hooks/useVersion.ts index eb5a1292..c934ef79 100644 --- a/src/views/hooks/useVersion.ts +++ b/src/views/hooks/useVersion.ts @@ -1,6 +1,10 @@ import { ExtensionStore } from '@shared/storage/ExtensionStore'; import { useEffect, useState } from 'react'; +/** + * Custom hook that retrieves the version from ExtensionStore and updates it when it changes. + * @returns The current version as a string. + */ export default function useVersion(): string { const [version, setVersion] = useState(''); diff --git a/src/views/index.tsx b/src/views/index.tsx index 549cb930..33480f95 100644 --- a/src/views/index.tsx +++ b/src/views/index.tsx @@ -1,10 +1,10 @@ import { ContextInvalidated, createShadowDOM, onContextInvalidated } from 'chrome-extension-toolkit'; import React from 'react'; -import render from './lib/react'; import CourseCatalogMain from './components/CourseCatalogMain'; import PopupMain from './components/PopupMain'; import getSiteSupport, { SiteSupport } from './lib/getSiteSupport'; +import render from './lib/react'; import colors from './styles/colors.module.scss'; const support = getSiteSupport(window.location.href); diff --git a/src/views/lib/CourseCatalogScraper.ts b/src/views/lib/CourseCatalogScraper.ts index 7409db96..6d881a48 100644 --- a/src/views/lib/CourseCatalogScraper.ts +++ b/src/views/lib/CourseCatalogScraper.ts @@ -1,39 +1,44 @@ -import { Course, InstructionMode, ScrapedRow, Semester, Status } from '@shared/types/Course'; +import type { InstructionMode, ScrapedRow, Semester } from '@shared/types/Course'; +import { Course, Status } from '@shared/types/Course'; import { CourseSchedule } from '@shared/types/CourseSchedule'; import Instructor from '@shared/types/Instructor'; -import { SiteSupport } from '@views/lib/getSiteSupport'; +import type { SiteSupportType } from '@views/lib/getSiteSupport'; /** * The selectors that we use to scrape the course catalog list table (https://utdirect.utexas.edu/apps/registrar/course_schedule/20239/results/?fos_fl=C+S&level=U&search_type_main=FIELD) */ -enum TableDataSelector { - COURSE_HEADER = 'td.course_header', - UNIQUE_ID = 'td[data-th="Unique"]', - REGISTER_URL = 'td[data-th="Add"] a', - INSTRUCTORS = 'td[data-th="Instructor"] span', - INSTRUCTION_MODE = 'td[data-th="Instruction Mode"]', - STATUS = 'td[data-th="Status"]', - SCHEDULE_DAYS = 'td[data-th="Days"]>span', - SCHEDULE_HOURS = 'td[data-th="Hour"]>span', - SCHEDULE_LOCATION = 'td[data-th="Room"]>span', - FLAGS = 'td[data-th="Flags"] ul li', -} +const TableDataSelector = { + COURSE_HEADER: 'td.course_header', + UNIQUE_ID: 'td[data-th="Unique"]', + REGISTER_URL: 'td[data-th="Add"] a', + INSTRUCTORS: 'td[data-th="Instructor"] span', + INSTRUCTION_MODE: 'td[data-th="Instruction Mode"]', + STATUS: 'td[data-th="Status"]', + SCHEDULE_DAYS: 'td[data-th="Days"]>span', + SCHEDULE_HOURS: 'td[data-th="Hour"]>span', + SCHEDULE_LOCATION: 'td[data-th="Room"]>span', + FLAGS: 'td[data-th="Flags"] ul li', +} as const; + +type TableDataSelectorType = (typeof TableDataSelector)[keyof typeof TableDataSelector]; /** * The selectors that we use to scrape the course details page for an individual course (https://utdirect.utexas.edu/apps/registrar/course_schedule/20239/52700/) */ -enum DetailsSelector { - COURSE_NAME = '#details h2', - COURSE_DESCRIPTION = '#details p', -} +const DetailsSelector = { + COURSE_NAME: '#details h2', + COURSE_DESCRIPTION: '#details p', +} as const; + +type DetailsSelectorType = (typeof DetailsSelector)[keyof typeof DetailsSelector]; /** * A class that allows us to scrape information from UT's course catalog to create our internal representation of a course */ export class CourseCatalogScraper { - support: SiteSupport; + support: SiteSupportType; - constructor(support: SiteSupport) { + constructor(support: SiteSupportType) { this.support = support; } diff --git a/src/views/lib/database/initializeDB.ts b/src/views/lib/database/initializeDB.ts index c6e2388c..c7cd7a8f 100644 --- a/src/views/lib/database/initializeDB.ts +++ b/src/views/lib/database/initializeDB.ts @@ -1,6 +1,5 @@ -import initSqlJs from 'sql.js/dist/sql-wasm'; - import DB_FILE_URL from '@public/database/grades.db?url'; +import initSqlJs from 'sql.js/dist/sql-wasm'; import WASM_FILE_URL from 'sql.js/dist/sql-wasm.wasm?url'; // import WASM_FILE_URL from '../../../../public/database/sql-wasm.wasm?url'; diff --git a/src/views/lib/database/queryDistribution.ts b/src/views/lib/database/queryDistribution.ts index 4be79a93..a024d08e 100644 --- a/src/views/lib/database/queryDistribution.ts +++ b/src/views/lib/database/queryDistribution.ts @@ -1,5 +1,6 @@ -import { Course, Semester } from '@shared/types/Course'; -import { CourseSQLRow, Distribution } from '@shared/types/Distribution'; +import type { Course, Semester } from '@shared/types/Course'; +import type { CourseSQLRow, Distribution } from '@shared/types/Distribution'; + import { initializeDB } from './initializeDB'; /** diff --git a/src/views/lib/getSiteSupport.ts b/src/views/lib/getSiteSupport.ts index 74154a0a..12a07d9e 100644 --- a/src/views/lib/getSiteSupport.ts +++ b/src/views/lib/getSiteSupport.ts @@ -4,21 +4,27 @@ import { isExtensionPage, isExtensionPopup } from 'chrome-extension-toolkit'; * An enum that represents the different types of pages that we support * a given url/page can correspond to many of these enum values */ -export enum SiteSupport { - COURSE_CATALOG_LIST = 'COURSE_CATALOG_LIST', - COURSE_CATALOG_DETAILS = 'COURSE_CATALOG_DETAILS', - UT_PLANNER = 'UT_PLANNER', - WAITLIST = 'WAITLIST', - EXTENSION_POPUP = 'EXTENSION_POPUP', - MY_CALENDAR = 'MY_CALENDAR', -} +export const SiteSupport = { + COURSE_CATALOG_LIST: 'COURSE_CATALOG_LIST', + COURSE_CATALOG_DETAILS: 'COURSE_CATALOG_DETAILS', + UT_PLANNER: 'UT_PLANNER', + WAITLIST: 'WAITLIST', + EXTENSION_POPUP: 'EXTENSION_POPUP', + MY_CALENDAR: 'MY_CALENDAR', +} as const; + +/** + * Represents the type of SiteSupport. + * It is a union type that includes all the values of the SiteSupport object. + */ +export type SiteSupportType = (typeof SiteSupport)[keyof typeof SiteSupport]; /** * We use this function to determine what page the user is on, and then we can use that information to determine what to do * @param url the url of the current page * @returns a list of page types that the current page is */ -export default function getSiteSupport(url: string): SiteSupport | null { +export default function getSiteSupport(url: string): SiteSupportType | null { if (isExtensionPopup()) { return SiteSupport.EXTENSION_POPUP; } diff --git a/src/views/lib/loadNextCourseCatalogPage.ts b/src/views/lib/loadNextCourseCatalogPage.ts index ca91e4fa..239748b6 100644 --- a/src/views/lib/loadNextCourseCatalogPage.ts +++ b/src/views/lib/loadNextCourseCatalogPage.ts @@ -2,15 +2,22 @@ import getCourseTableRows from './getCourseTableRows'; const NEXT_PAGE_BUTTON_SELECTOR = '#next_nav_link'; const PREV_PAGE_BUTTON_SELECTOR = '#prev_nav_link'; + /** * Represents all the states that we care about when autoloading the next page of courses */ -export enum AutoLoadStatus { - LOADING = 'LOADING', - IDLE = 'IDLE', - ERROR = 'ERROR', - DONE = 'DONE', -} +export const AutoLoadStatus = { + LOADING: 'LOADING', + IDLE: 'IDLE', + ERROR: 'ERROR', + DONE: 'DONE', +} as const; + +/** + * Represents the type of the auto load status. + * It is a union type that includes all the values of the AutoLoadStatus object. + */ +export type AutoLoadStatusType = (typeof AutoLoadStatus)[keyof typeof AutoLoadStatus]; let isLoading = false; let nextPageURL = getNextButton(document)?.href; @@ -22,7 +29,7 @@ let nextPageURL = getNextButton(document)?.href; * or if there was an error loading the next page) and an array of the table rows from the next page (or an empty array * if we have reached the end of the course catalog */ -export async function loadNextCourseCatalogPage(): Promise<[AutoLoadStatus, HTMLTableRowElement[]]> { +export async function loadNextCourseCatalogPage(): Promise<[AutoLoadStatusType, HTMLTableRowElement[]]> { // if there is no more nextPageURL, then we have reached the end of the course catalog, so we can stop if (!nextPageURL) { return [AutoLoadStatus.DONE, []]; diff --git a/src/views/lib/openNewTabFromContentScript.ts b/src/views/lib/openNewTabFromContentScript.ts index cf48d447..3d0f9e91 100644 --- a/src/views/lib/openNewTabFromContentScript.ts +++ b/src/views/lib/openNewTabFromContentScript.ts @@ -1,25 +1,28 @@ -import { createMessenger } from "chrome-extension-toolkit"; +import { createMessenger } from 'chrome-extension-toolkit'; type MyMessages = { openNewTab: { - data: { url: string }; + data: { url: string }; }; - }; +}; const messenger = createMessenger('background'); -/** - * Content scripts and background scripts are isolated environments. +/** + * Content scripts and background scripts are isolated environments. * Content scripts are where our code interacting with the webpage lives, * whereas the background script is where we can open a tab from. - * This function allows us to open a new tab from the content script by communicating - * with the background script. + * This function allows us to open a new tab from the content script by communicating + * with the background script. */ export async function openTabFromContentScript(url: string) { // @ts-ignore - messenger.openNewTab({ url }).then(() => { - console.log('New tab opened with URL:', url); - }).catch((error) => { - console.error('Error opening new tab:', error); - }); + messenger + .openNewTab({ url }) + .then(() => { + console.log('New tab opened with URL:', url); + }) + .catch(error => { + console.error('Error opening new tab:', error); + }); } diff --git a/src/views/lib/populateSearchInputs.ts b/src/views/lib/populateSearchInputs.ts index 7a34092a..1e32fcb2 100644 --- a/src/views/lib/populateSearchInputs.ts +++ b/src/views/lib/populateSearchInputs.ts @@ -1,4 +1,3 @@ - /** * The course schedule page has a search form that allows users to search for courses by department and course level. * The problem is that once the user triggers a search and refreshes the page, diff --git a/src/views/lib/react/index.tsx b/src/views/lib/react/index.tsx index e8d7f76e..60c75313 100644 --- a/src/views/lib/react/index.tsx +++ b/src/views/lib/react/index.tsx @@ -1,6 +1,12 @@ -import React from 'react'; +import type React from 'react'; import ReactDOM from 'react-dom/client'; +/** + * Renders a React element into a container. + * @param element - The React element to render. + * @param container - The container element where the React element will be rendered. + * @throws Error if the container is null. + */ export default function render(element: React.ReactElement, container: HTMLElement | ShadowRoot | null) { if (!container) { throw new Error('Container is null'); diff --git a/tsconfig.json b/tsconfig.json index f2267ecc..ada9a18a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,43 +6,22 @@ "module": "esnext", "noEmit": true, "jsx": "react", - "typeRoots": [ - "./node_modules/@types", - "./@types/" - ], + "typeRoots": ["./node_modules/@types", "./@types/"], "skipLibCheck": true, "esModuleInterop": true, "resolveJsonModule": true, "moduleResolution": "node", - "types": [ - "vite/client", - "unplugin-icons/types/react", - "node" - ], + "types": ["vite/client", "unplugin-icons/types/react", "node"], "noFallthroughCasesInSwitch": true, "allowSyntheticDefaultImports": true, "paths": { - "src/*": [ - "src/*" - ], - "@assets/*": [ - "src/assets/*" - ], - "@pages/*": [ - "src/pages/*" - ], - "@public/*": [ - "public/*" - ], - "@shared/*": [ - "src/shared/*" - ], - "@background/*": [ - "src/pages/background/*" - ], - "@views/*": [ - "src/views/*" - ], + "src/*": ["src/*"], + "@assets/*": ["src/assets/*"], + "@pages/*": ["src/pages/*"], + "@public/*": ["public/*"], + "@shared/*": ["src/shared/*"], + "@background/*": ["src/pages/background/*"], + "@views/*": ["src/views/*"] } }, "include": [ @@ -55,6 +34,6 @@ ".eslintrc", "postcss.config.cjs", ".storybook", - "unocss.config.ts", + "unocss.config.ts" ] } diff --git a/unocss.config.ts b/unocss.config.ts index 4cf5f36f..17d86b84 100644 --- a/unocss.config.ts +++ b/unocss.config.ts @@ -3,6 +3,7 @@ import presetWebFonts from '@unocss/preset-web-fonts'; import transformerDirectives from '@unocss/transformer-directives'; import transformerVariantGroup from '@unocss/transformer-variant-group'; import { defineConfig } from 'unocss'; + import { colors } from './src/shared/util/themeColors'; export default defineConfig({ diff --git a/vite.config.ts b/vite.config.ts index 7676a4e1..eb86f234 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,8 +3,10 @@ import react from '@vitejs/plugin-react-swc'; import { resolve } from 'path'; import UnoCSS from 'unocss/vite'; import Icons from 'unplugin-icons/vite'; -import { Plugin, ResolvedConfig, ViteDevServer, defineConfig } from 'vite'; +import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite'; +import { defineConfig } from 'vite'; import inspect from 'vite-plugin-inspect'; + import manifest from './src/manifest'; const root = resolve(__dirname, 'src');