From 27094846f70feb8d83c8d464ab0fa8dcd99b3e71 Mon Sep 17 00:00:00 2001 From: Derek Chen Date: Sat, 16 Mar 2024 15:57:50 -0500 Subject: [PATCH] feat: open an injected course page on course block click in popup main (#146) * feat: Imports to popupcourseblock.tsx * changing the blocks to accept parameters for clicking functionality which may or may not open the calendar * put the click parameter in the div of popupcourseblock * safely calling for onCourseClick in the event it is an undefined function * handled other calls of popupcourseblock with empty functions for now, and i think popupmain opens calendar now when the course block is clicked * feat: Testing out passing params to handleOpenCalendar * url that takes in params to open calendar with params * further work on url params; from popup main to handleopencalendar to calendar using urlsearchparams * feat: small calendar shifting after merge: * fix: merge handling and then references to new click parameter * fix: optional params * feat: split into two functions instead * fix: changing proper usage of handleOpenCalendarWithCourse * feat: show course popup when calendar opened * chore: remove useless commented out code * feat: close popup on calendar nav, fix build errors, remove useless comments/logs * chore: chromatic so dumb fr why aren't you chrome * fix: refactor listeners to build properly * feat: exit early when not in chrome extension * fix: function return type * fix: function return type x2 * fix: generic type for useState * refactor: extract calendar opening on click functions * refactor: chrome runtime mock, omit question mark if no query params, rename calendar event * refactor: move course click event into component directly instead of prop * refactor: removed useless wrapper functions, made popup course block more accessible * fix: i dont wanna talk about it --------- Co-authored-by: Samuel Gunter --- .storybook/preview.tsx | 23 ++++++++++ package.json | 4 +- pnpm-lock.yaml | 8 ++-- src/pages/background/background.ts | 2 + .../handler/calendarBackgroundHandler.ts | 44 +++++++++++++++++++ src/shared/messages/CalendarMessages.ts | 19 ++++++++ src/shared/messages/TabInfoMessages.ts | 15 +++++++ src/shared/messages/TabMessages.ts | 4 -- src/shared/messages/index.ts | 16 +++++-- .../components/PopupCourseBlock.stories.tsx | 4 +- src/views/components/PopupMain.tsx | 9 +++- .../components/calendar/Calendar/Calendar.tsx | 34 +++++++++++++- .../common/ExtensionRoot/ExtensionRoot.tsx | 20 ++++++++- .../PopupCourseBlock/PopupCourseBlock.tsx | 16 +++++-- .../HeadingAndActions.tsx | 16 +++---- src/views/hooks/useTabMessage.ts | 2 +- src/views/index.tsx | 5 +-- 17 files changed, 203 insertions(+), 38 deletions(-) create mode 100644 src/pages/background/handler/calendarBackgroundHandler.ts create mode 100644 src/shared/messages/CalendarMessages.ts create mode 100644 src/shared/messages/TabInfoMessages.ts delete mode 100644 src/shared/messages/TabMessages.ts diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 3e88ae96..ba6bde2c 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -141,6 +141,29 @@ globalThis.chrome = { }, }, }, + runtime: { + id: 'fake-id', + getManifest(): chrome.runtime.Manifest { + return { + manifest_version: 3, + name: 'fake-name', + version: '0.0.0', + }; + }, + onMessage: { + /** + * Registers an event listener callback to an event. + * @param callback Called when an event occurs. The parameters of this function depend on the type of event. + */ + addListener(callback: T) {}, + + /** + * Deregisters an event listener callback from an event. + * @param callback Listener that shall be unregistered. + */ + removeListener(callback: T) {}, + }, + }, } as typeof chrome; export default preview; diff --git a/package.json b/package.json index 945d5bc9..66de1125 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@hello-pangea/dnd": "^16.5.0", "@unocss/vite": "^0.58.6", "@vitejs/plugin-react": "^4.2.1", - "chrome-extension-toolkit": "^0.0.51", + "chrome-extension-toolkit": "^0.0.54", "clsx": "^2.1.0", "highcharts": "^11.3.0", "highcharts-react-official": "^3.2.1", @@ -120,4 +120,4 @@ "es-module-lexer": "^1.4.1" } } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4883264..d0dd7648 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,8 +29,8 @@ dependencies: specifier: ^4.2.1 version: 4.2.1(vite@5.1.4) chrome-extension-toolkit: - specifier: ^0.0.51 - version: 0.0.51 + specifier: ^0.0.54 + version: 0.0.54 clsx: specifier: ^2.1.0 version: 2.1.0 @@ -6283,8 +6283,8 @@ packages: optional: true dev: true - /chrome-extension-toolkit@0.0.51: - resolution: {integrity: sha512-XzOOE2+/aYG43bJOwuJT4oWcn80jBJr5mwGyrSzKKFoqALixT15AsPcfZId/UOoc4pIavu2XcHeJga6ng0m1jQ==} + /chrome-extension-toolkit@0.0.54: + resolution: {integrity: sha512-ux8v/PfWQIvO+EBbF+kDYq2z8Rnp5YZ7GwJxYX7R2a9owIEHJxiCUSJ82tOsiMQINF/31+t6QLG9equKNZUOlA==} dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) diff --git a/src/pages/background/background.ts b/src/pages/background/background.ts index 290b4d26..d35d9ab7 100644 --- a/src/pages/background/background.ts +++ b/src/pages/background/background.ts @@ -7,6 +7,7 @@ import onInstall from './events/onInstall'; import onServiceWorkerAlive from './events/onServiceWorkerAlive'; import onUpdate from './events/onUpdate'; import browserActionHandler from './handler/browserActionHandler'; +import calendarBackgroundHandler from './handler/calendarBackgroundHandler'; import CESHandler from './handler/CESHandler'; import tabManagementHandler from './handler/tabManagementHandler'; import userScheduleHandler from './handler/userScheduleHandler'; @@ -36,6 +37,7 @@ const messageListener = new MessageListener({ ...tabManagementHandler, ...userScheduleHandler, ...CESHandler, + ...calendarBackgroundHandler, }); messageListener.listen(); diff --git a/src/pages/background/handler/calendarBackgroundHandler.ts b/src/pages/background/handler/calendarBackgroundHandler.ts new file mode 100644 index 00000000..a63126a1 --- /dev/null +++ b/src/pages/background/handler/calendarBackgroundHandler.ts @@ -0,0 +1,44 @@ +import openNewTab from '@background/util/openNewTab'; +import { tabs } from '@shared/messages'; +import type { CalendarBackgroundMessages } from '@shared/messages/CalendarMessages'; +import type { MessageHandler } from 'chrome-extension-toolkit'; + +const getAllTabInfos = async () => { + const openTabs = await chrome.tabs.query({}); + const results = await Promise.allSettled(openTabs.map(tab => tabs.getTabInfo(undefined, tab.id))); + return results + .map((result, index) => ({ result, index })) + .filter(({ result }) => result.status === 'fulfilled') + .map(({ result, index }) => { + if (result.status !== 'fulfilled') throw new Error('Will never happen, typescript dumb'); + return { + ...result.value, + tab: openTabs[index], + }; + }); +}; + +const calendarBackgroundHandler: MessageHandler = { + async switchToCalendarTab({ data, sendResponse }) { + const { uniqueId } = data; + const calendarUrl = chrome.runtime.getURL(`calendar.html`); + + const allTabs = await getAllTabInfos(); + + const openCalendarTabInfo = allTabs.find(tab => tab.url.startsWith(calendarUrl)); + + if (openCalendarTabInfo !== undefined) { + chrome.tabs.update(openCalendarTabInfo.tab.id, { active: true }); + if (uniqueId !== undefined) await tabs.openCoursePopup({ uniqueId }, openCalendarTabInfo.tab.id); + sendResponse(openCalendarTabInfo.tab); + } else { + const urlParams = new URLSearchParams(); + if (uniqueId !== undefined) urlParams.set('uniqueId', uniqueId.toString()); + const url = `${calendarUrl}?${urlParams.toString()}`.replace(/\?$/, ''); + const tab = await openNewTab(url); + sendResponse(tab); + } + }, +}; + +export default calendarBackgroundHandler; diff --git a/src/shared/messages/CalendarMessages.ts b/src/shared/messages/CalendarMessages.ts new file mode 100644 index 00000000..2f50f1e4 --- /dev/null +++ b/src/shared/messages/CalendarMessages.ts @@ -0,0 +1,19 @@ +interface CalendarBackgroundMessages { + /** + * Opens the calendar page if it is not already open, focuses the tab, and optionally opens the calendar for a specific course + * + * @param data - The unique id of the course to open the calendar page for (optional) + */ + switchToCalendarTab: (data: { uniqueId?: number }) => chrome.tabs.Tab; +} + +interface CalendarTabMessages { + /** + * Opens a popup for a specific course on the calendar page + * + * @param data - The unique id of the course to open on the calendar page + */ + openCoursePopup: (data: { uniqueId: number }) => chrome.tabs.Tab; +} + +export type { CalendarBackgroundMessages, CalendarTabMessages }; diff --git a/src/shared/messages/TabInfoMessages.ts b/src/shared/messages/TabInfoMessages.ts new file mode 100644 index 00000000..267491ef --- /dev/null +++ b/src/shared/messages/TabInfoMessages.ts @@ -0,0 +1,15 @@ +type TabInfo = { + url: string; + title: string; +}; + +interface TabInfoMessages { + /** + * Gets the info for the tab receiving the message + * + * @returns The info for the tab receiving the message + */ + getTabInfo: () => TabInfo; +} + +export default TabInfoMessages; diff --git a/src/shared/messages/TabMessages.ts b/src/shared/messages/TabMessages.ts deleted file mode 100644 index 8df07e2f..00000000 --- a/src/shared/messages/TabMessages.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * This is a type with all the message definitions that can be sent TO specific tabs - */ -export default interface TAB_MESSAGES {} diff --git a/src/shared/messages/index.ts b/src/shared/messages/index.ts index 12b1c99d..30a3a54e 100644 --- a/src/shared/messages/index.ts +++ b/src/shared/messages/index.ts @@ -1,15 +1,25 @@ import { createMessenger } from 'chrome-extension-toolkit'; import type BrowserActionMessages from './BrowserActionMessages'; +import type { CalendarBackgroundMessages, CalendarTabMessages } from './CalendarMessages'; import type CESMessage from './CESMessage'; +import type TabInfoMessages from './TabInfoMessages'; 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 */ -export type BACKGROUND_MESSAGES = BrowserActionMessages & TabManagementMessages & UserScheduleMessages & CESMessage; +export type BACKGROUND_MESSAGES = BrowserActionMessages & + TabManagementMessages & + UserScheduleMessages & + CESMessage & + CalendarBackgroundMessages; + +/** + * This is a type with all the message definitions that can be sent TO specific tabs + */ +export type TAB_MESSAGES = CalendarTabMessages & TabInfoMessages; /** * A utility object that can be used to send type-safe messages to the background script @@ -19,4 +29,4 @@ export const background = createMessenger('background'); /** * A utility object that can be used to send type-safe messages to specific tabs */ -export const tabs = createMessenger('tab'); +export const tabs = createMessenger('foreground'); diff --git a/src/stories/components/PopupCourseBlock.stories.tsx b/src/stories/components/PopupCourseBlock.stories.tsx index 8b9a9733..5a8e1a0c 100644 --- a/src/stories/components/PopupCourseBlock.stories.tsx +++ b/src/stories/components/PopupCourseBlock.stories.tsx @@ -103,9 +103,9 @@ export const Variants: Story = { }; export const AllColors: Story = { - render: props => ( + render: () => (
- {tailwindColorways.map((color, i) => ( + {tailwindColorways.map(color => ( ))}
diff --git a/src/views/components/PopupMain.tsx b/src/views/components/PopupMain.tsx index e3d5bd16..8b8fb650 100644 --- a/src/views/components/PopupMain.tsx +++ b/src/views/components/PopupMain.tsx @@ -1,10 +1,10 @@ +import { background } from '@shared/messages'; import { UserScheduleStore } from '@shared/storage/UserScheduleStore'; import { tailwindColorways } from '@shared/util/storybook'; import Divider from '@views/components/common/Divider/Divider'; import ExtensionRoot from '@views/components/common/ExtensionRoot/ExtensionRoot'; import List from '@views/components/common/List/List'; import Text from '@views/components/common/Text/Text'; -import { handleOpenCalendar } from '@views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions'; import useSchedules, { getActiveSchedule, replaceSchedule, switchSchedule } from '@views/hooks/useSchedules'; import { getUpdatedAtDateTimeString } from '@views/lib/getUpdatedAtDateTimeString'; import { openTabFromContentScript } from '@views/lib/openNewTabFromContentScript'; @@ -34,6 +34,11 @@ export default function PopupMain(): JSX.Element { await openTabFromContentScript(url); }; + const handleCalendarOpenOnClick = async () => { + await background.switchToCalendarTab({}); + window.close(); + }; + return (
@@ -50,7 +55,7 @@ export default function PopupMain(): JSX.Element {
- ); } diff --git a/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx b/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx index 45d24b2d..fcaf0ba0 100644 --- a/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx +++ b/src/views/components/injected/CourseCatalogInjectedPopup/HeadingAndActions.tsx @@ -29,15 +29,6 @@ interface HeadingAndActionProps { onClose: () => void; } -/** - * Opens the calendar in a new tab. - * @returns {Promise} A promise that resolves when the tab is opened. - */ -export const handleOpenCalendar = async (): Promise => { - const url = chrome.runtime.getURL('calendar.html'); - openNewTab({ url }); -}; - /** * Capitalizes the first letter of a string and converts the rest of the letters to lowercase. * @@ -174,7 +165,12 @@ export default function HeadingAndActions({ course, activeSchedule, onClose }: H
-