diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..4a873e85 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_size = 4 +indent_style = space diff --git a/src/pages/popup/index.html b/src/pages/popup/index.html index b4b032c8..aff96608 100644 --- a/src/pages/popup/index.html +++ b/src/pages/popup/index.html @@ -5,6 +5,14 @@ Popup + diff --git a/src/stories/components/PopupMain.stories.tsx b/src/stories/components/PopupMain.stories.tsx index 61c9197b..046ed9fc 100644 --- a/src/stories/components/PopupMain.stories.tsx +++ b/src/stories/components/PopupMain.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react'; import PopupMain from '@views/components/PopupMain'; +import React from 'react'; const meta = { title: 'Components/Common/PopupMain', @@ -10,6 +11,14 @@ const meta = { tags: ['autodocs'], args: {}, argTypes: {}, + render: () => ( +
+ +
+ ), } satisfies Meta; export default meta; diff --git a/src/views/components/PopupMain.tsx b/src/views/components/PopupMain.tsx index d6b2d88b..d1add0ad 100644 --- a/src/views/components/PopupMain.tsx +++ b/src/views/components/PopupMain.tsx @@ -1,5 +1,3 @@ -import { Status } from '@shared/types/Course'; -import { StatusIcon } from '@shared/util/icons'; import { tailwindColorways } from '@shared/util/storybook'; import Divider from '@views/components/common/Divider/Divider'; import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'; @@ -10,13 +8,16 @@ import { handleOpenCalendar } from '@views/components/injected/CourseCatalogInje import useSchedules, { switchSchedule } from '@views/hooks/useSchedules'; import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript'; import clsx from 'clsx'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useState } from 'react'; import CalendarIcon from '~icons/material-symbols/calendar-month'; import RefreshIcon from '~icons/material-symbols/refresh'; import SettingsIcon from '~icons/material-symbols/settings'; +import CourseStatus from './common/CourseStatus/CourseStatus'; import { LogoIcon } from './common/LogoIcon'; +import ScheduleDropdown from './common/ScheduleDropdown/ScheduleDropdown'; +import ScheduleListItem from './common/ScheduleListItem/ScheduleListItem'; /** * Renders the main popup component. @@ -24,38 +25,7 @@ import { LogoIcon } from './common/LogoIcon'; */ export default function PopupMain(): JSX.Element { const [activeSchedule, schedules] = useSchedules(); - const [isPopupVisible, setIsPopupVisible] = useState(false); - const popupRef = useRef(null); - const toggleRef = useRef(null); - - useEffect(() => { - function handleClickOutside(event) { - if (!popupRef.current?.contains(event.target) && !toggleRef.current?.contains(event.target)) { - setIsPopupVisible(false); - } - } - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); - - const handleClick = () => { - setIsPopupVisible(prev => !prev); - }; - - if (!activeSchedule || schedules.length === 0) { - return No active schedule available.; - } - - const selectSchedule = async selectedSchedule => { - await switchSchedule(selectedSchedule.name); - handleClick(); - }; - - const nonActiveSchedules = schedules.filter(s => s.name !== activeSchedule.name); - - const draggableElements = activeSchedule?.courses.map((course, i) => ( - - )); + const [isRefreshing, setIsRefreshing] = useState(false); const handleOpenOptions = async () => { const url = chrome.runtime.getURL('/options.html'); @@ -64,108 +34,79 @@ export default function PopupMain(): JSX.Element { return ( -
-
-
- -
- - UT Registration - - - Plus - -
-
-
- - -
-
- -
-
- - {`${activeSchedule.name}`}: - -
- {activeSchedule.hours} HOURS - {activeSchedule.courses.length} Courses -
-
-
-
- {isPopupVisible && ( -
- {nonActiveSchedules.map(schedule => ( -
selectSchedule(schedule)} - > - - {schedule.name}: - -
- {schedule.hours} HOURS - {schedule.courses.length} Courses -
+
+
+
+
+ +
+ + UT Registration +
+
+ Plus
- ))} -
- )} - {!isPopupVisible && ( - ( - - ))} - gap={12} - /> - )} -
-
-
-
- - WAITLISTED - -
-
-
- +
+ +
- - CLOSED - -
-
-
- -
- - CANCELLED -
-
-
- DATA UPDATED ON: 12:00 AM 02/01/2024 - + +
+ + ( + { + switchSchedule(schedule.name); + }} + /> + ))} + gap={10} + /> + +
+
+ {activeSchedule?.courses?.length > 0 && ( + ( + + ))} + gap={10} + /> + )} +
+ +
+
+ + + +
+
+ + DATA UPDATED ON: 12:00 AM 02/01/2024 + +
diff --git a/src/views/components/common/ExtensionRoot/ExtensionRoot.module.scss b/src/views/components/common/ExtensionRoot/ExtensionRoot.module.scss index bc3136b0..43ce2c9c 100644 --- a/src/views/components/common/ExtensionRoot/ExtensionRoot.module.scss +++ b/src/views/components/common/ExtensionRoot/ExtensionRoot.module.scss @@ -1,12 +1,6 @@ @import 'src/views/styles/base.module.scss'; .extensionRoot { - @apply font-sans; + @apply font-sans h-full; color: #303030; - -webkit-box-sizing: border-box; - box-sizing: border-box; - font-family: Inter, sans-serif; - font-weight: 300; - font-size: 14px; - line-height: 18px; } diff --git a/src/views/components/common/ScheduleDropdown/ScheduleDropdown.tsx b/src/views/components/common/ScheduleDropdown/ScheduleDropdown.tsx index 14a36a43..f3384021 100644 --- a/src/views/components/common/ScheduleDropdown/ScheduleDropdown.tsx +++ b/src/views/components/common/ScheduleDropdown/ScheduleDropdown.tsx @@ -45,9 +45,10 @@ export default function ScheduleDropdown(props: Props) { diff --git a/src/views/hooks/useSchedules.ts b/src/views/hooks/useSchedules.ts index d8eececd..b215e162 100644 --- a/src/views/hooks/useSchedules.ts +++ b/src/views/hooks/useSchedules.ts @@ -2,52 +2,61 @@ import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import { UserSchedule } from '@shared/types/UserSchedule'; import { useEffect, useState } from 'react'; +let schedulesCache = []; +let activeIndexCache = 0; +let initialLoad = true; + +/** + * Fetches the user schedules from storage and sets the cached state. + */ +async function fetchData() { + const [storedSchedules, storedActiveIndex] = await Promise.all([ + UserScheduleStore.get('schedules'), + UserScheduleStore.get('activeIndex'), + ]); + schedulesCache = storedSchedules.map(s => new UserSchedule(s)); + activeIndexCache = storedActiveIndex; +} + /** * 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 [activeSchedule, setActiveSchedule] = useState(null); + const [schedules, setSchedules] = useState(schedulesCache); + const [activeIndex, setActiveIndex] = useState(activeIndexCache); + const [activeSchedule, setActiveSchedule] = useState(schedules[activeIndex]); + + if (initialLoad) { + initialLoad = false; + + // trigger suspense + // eslint-disable-next-line @typescript-eslint/no-throw-literal + throw new Promise(res => { + fetchData().then(res); + }); + } useEffect(() => { - const fetchData = async () => { - const [storedSchedules, storedActiveIndex] = await Promise.all([ - UserScheduleStore.get('schedules'), - UserScheduleStore.get('activeIndex'), - ]); - setSchedules(storedSchedules.map(s => new UserSchedule(s))); - setActiveSchedule(new UserSchedule(storedSchedules[storedActiveIndex])); + const l1 = UserScheduleStore.listen('schedules', ({ newValue }) => { + setSchedules(newValue.map(s => new UserSchedule(s))); + }); + + const l2 = UserScheduleStore.listen('activeIndex', ({ newValue }) => { + setActiveIndex(newValue); + }); + + return () => { + UserScheduleStore.removeListener(l1); + UserScheduleStore.removeListener(l2); }; - - fetchData(); - - const setupListeners = () => { - const l1 = UserScheduleStore.listen('schedules', ({ newValue }) => { - setSchedules(newValue.map(s => new UserSchedule(s))); - setActiveSchedule(currentActive => { - const newActiveIndex = newValue.findIndex(s => s.name === currentActive?.name); - return new UserSchedule(newValue[newActiveIndex]); - }); - }); - - const l2 = UserScheduleStore.listen('activeIndex', ({ newValue }) => { - setSchedules(currentSchedules => { - setActiveSchedule(new UserSchedule(currentSchedules[newValue])); - return currentSchedules; - }); - }); - - return () => { - UserScheduleStore.removeListener(l1); - UserScheduleStore.removeListener(l2); - }; - }; - - const init = UserScheduleStore.initialize(); - init.then(() => setupListeners()).catch(console.error); }, []); + // recompute active schedule on a schedule/index change + useEffect(() => { + setActiveSchedule(schedules[activeIndex]); + }); + return [activeSchedule, schedules]; } @@ -57,6 +66,7 @@ export default function useSchedules(): [active: UserSchedule | null, schedules: * @returns A promise that resolves when the active schedule has been switched. */ export async function switchSchedule(name: string): Promise { + console.log('Switching schedule...'); const schedules = await UserScheduleStore.get('schedules'); const activeIndex = schedules.findIndex(s => s.name === name); await UserScheduleStore.set('activeIndex', activeIndex); diff --git a/unocss.config.ts b/unocss.config.ts index 17d86b84..2d025e5c 100644 --- a/unocss.config.ts +++ b/unocss.config.ts @@ -36,9 +36,7 @@ export default defineConfig({ presetWebFonts({ provider: 'none', fonts: { - sans: { - name: 'Roboto Flex', - }, + sans: ['Roboto Flex', 'Roboto Flex Local'], }, }), ],